Desarrollando un OS con ventanas para una computadora casera Am29000
por Oscar Toledo G. 30-jul-2026
Erase una vez en las misteriosas tierras de México, comenzando un otoño de 1996 y hasta la primavera de 1997, codifiqué un sistema operativo de 32 bits en código máquina, cabía en un disco flexible, y salté de emoción cuando arrancó.
Por supuesto, sabía que era algo grande porque no podías arrancar Windows 3.1 o GEM de un disco flexible, pero estaba muy lejos de saber cuan importante era. Desafortunadamente, no era algo que pudiera dar a todos para que vieran, estaba escrito para una computadora casera basada en el procesador Am29000.
Volvamos en el tiempo con mis recuerdos, algunas notas, y la emoción de descubrir de nuevo lo que olvidé.
El procesador más rápido del mundo
Si has estado leyendo mi
serie de artículos sobre el transputer, yo estaba muy satisfecho con mi sistema operativo, y había alcanzado los límites del sistema. Mi padre por otra parte estaba ocupado haciendo un nuevo sistema de computadora basado en un procesador de 32 bits. Bien, en 1994 solo era nuevo para nosotros. El procesador AMD Am29000 salió en 1987, y también fue anunciado como el procesador más rápido del mundo (¿puedes notar un meme aquí?) y podía manejar una instrucción en cada ciclo, así que sí, era muy rápido.
El procesador Am29000 y su sucesor el Am29050 fueron usados en varias tarjetas de lenguaje Postscript para impresoras, tarjetas de video NuBus para Macs Quadra, juegos de arcade como F-15 Strike Eagle y Battle of the Solar System (ambos de Microprose), e incluso leí en Wikipedia que esta en aviónica en tableros con procesador dual por la redundancia.
La velocidad estaba basada en muchos supuestos, como tener una RAM y ROM veloces, o tener muchos chips TTL de alta velocidad. Y esas tres cosas todavía eran muy caras a principio de la década de 1990. Además algunas decisiones de diseño de bus para la familia Am29k como un bus separado para instrucciones y datos lo hacían realmente díficil para interfazar. Al final, hacer cualquier cosa útil con el Am29k requería memoria DRAM, y era lenta, y entonces toda la ventaja se perdía.
Sin embargo, mi padre tenía confianza en la memoria RAM estática y obtuvo docenas de RAM estáticas de 128k con bus de 8 bits. El montaje de superficie estaba apareciendo y varias de estas eran construídas con cuatro chips de RAM de 32k montadas en pequeños tableros verdes.
Así que la primera computadora Am29000 hecha por mi padre corría a 6 mhz, y tenía la increíble capacidad de memoria de 512 KB, junto con 128 KB de EPROM (usando dos chips de 16 bits). Tenía conectores ISA, y para el video se usó una tarjeta con Cirrus Logic GD5429, junto con una tarjeta estándar de controlador de disco/serie/paralelo (o multipuertos), y se reusó el chip de teclado de una vieja PC para interfazar teclados AT. La computadora se bautizó G11.
Fue un trabajo de equipo, él hizo la programación inicial para arrancar el procesador, y yo transladé mi código Z280 para inicializar el VGA de Cirrus Logic, hice el decodificador de teclado y las rutinas de disco. Una vez que esto estuvo estable, seguí trabajando en un programa monitor para listar la memoria, guardar datos, y como el conjunto de instrucciones era complicado, también integé un micro-ensamblador y un micro-desensamblador. Esto significa que podía codificar con nemónicos reales, aunque no se podían usar etiquetas, también mover código era difícil.
Necesitaba volver a la máquina Z280 para programar las EPROM, pero todo iba realmente bien. Entonces me di cuenta de mi oscura realidad, iba a tener que portar todo de la máquina Z280 a esta computadora, eran como 200 programas entre aplicaciones, utilidades y juegos. Además los programas Z280 estaban obsoletos, no quería replicar lo mismo.
Quería una foto de la G11V1, pero no había cámaras digitales antes de 1999 y no se donde está la computadora. Así que esta es una foto de una computadora G11V2, se pueden ver cuatro memorias de 512KBx8 y ya tiene bus PCI, que no existía en la G11V1.
Intermedio
Cuando la computadora G11 se terminó, mi padre la acaparó y comenzó a investigar como funcionaba cada registro especial del procesador o porqué tenía ciertos valores, y escribió documentación al respecto. Yo estaba algo sorprendido, porque yo creía que el procesador ya estaba muy bien documentado en su libro, y no estaba interesado en comportamiento no documentado, pero de todas formas, terminé tecleando todas sus notas en limpio.
Me podía concentrar en el transputer ahora que estaba disponible para mi uso, y cambiaba ocasionalmente a la computadora G11 cuando estaba libre para mejorar el sistema operativo de disco, y comencé a escribir algunos juegos, un editor de texto que manejaba la nueva impresora Canon BJC-600 de burbuja, e incluso un emulador MSX. Fue la primera vez que vi Gradius corriendo, me quedé enganchado.
Estaba volviendo a crear mis programas de Z280, y sentí que iba para atrás cuando ya tenía un sistema operativo avanzado en el transputer. Mientras tanto, Windows 3.1 se veía muy útil con todos sus iconos en colores chillones, un producto de la paleta heredada desde CGA.
Estaba interesado en descubrir como hacer un sistema de ventanas, pero no podía obtener un libro de programación para Windows 3.1. Las librerías locales todavía estaban llenas de libros de Turbo-C, Turbo-Pascal, Quick-BASIC y QBasic. Al fin, encontré un libro en descuento sobre programación de OS/2, y comencé a leerlo. El autor hizo un trabajo excelente introduciendo OS/2, y parecía fácil: abrir una ventana, dibujar en la ventana, cerrar la ventana.
El verdadero descubrimiento fue... *badum-tss* la cola de mensajes. Cada proceso con ventanas tiene una cola de mensajes, y cuando se crea una ventana se le debe asignar un manejador de interfaz (la clase de ventana), y este manejador lee los mensajes y hace las operaciones adecuadas según la interacción del usuario.
Las interacciones del usuario son simples al principio, un mensaje indicando un click del ratón, un mensaje cuando el ratón se mueve sobre la ventana, y entonces las cosas se ponen complicadas con mensajes de doble click. Se pueden crear ventanas dentro de las ventanas, pero estas son controles como botones, barras de desplazamiento, iconos, indicadores, barras de estado, etc. Y estos controles pueden enviar mensajes de vuelta.
La belleza de esto es que se pueden desarrollar interfaces de usuario muy rápido, y cada programa que las use se ve muy bien (bueno, en los 90s no eramos muy exigentes, como puede testificar GEM de Digital Research)
Mientras investigaba para este artículo, descubrí que todavía tenía una caja donde AMD nos mandó dos libros técnicos. Alguien la puso en el correo el 7 de junio de 1990.
El sistema de ventanas
Primero, necesitaba multitarea. Encontré esto espantoso y complicado, y terminé aprendiendo como hacer multitarea cooperativa. Salva el estado actual, carga el nuevo estado, y salta al nuevo lugar. Lo siento diseñadores de sistemas operativos, no lo pensé, solo quería que funcionara.
Segundo, necesitaba ventanas. Me rasqué bastante la cabeza pensando como sobreponar cosas una sobre otra. Entonces mientras leía archivo tras archivo de un nuevo CD-ROM que compró mi padre, descubrí X11R5. En caso de que no lo sepas, X11 es un sistema de ventanas en red diseñado para trabajar en cualquier terminal gráfica, ya que las universidades tienen mucho hardware diferente, y debía trabajar en todas esas computadoras. Por supuesto, esto significa que era torpe para manejar color o juegos.
Lo importante es que el CD tenía el código fuente completo de X11R5. Por supuesto, no tenía uso ya que mi computadora no tenía una red, ni siquiera un compilador de C, en lugar de eso un solo archivo fuente me iluminó:: XRegion.c.
El concepto básico de este algoritmo es que cada ventana tiene una lista de visualización de rectángulos. Si una ventana no tiene nada sobrepuesto entonces el rectángulo cubre la ventana entera. Pero si una ventana tiene algo sobrepuesto, se corta el rectángulo sobrepuesto, y se continua cortando cada cosa sobrepuesta. Esto significa que si un rectángulo se corta en el medio, se crean cuatro rectángulos, y para ilustrar información se debe dibujar en los cuatro rectángulos. Así que una ventana con muchas ventanas sobrepuestas se dibuja más lento. Por cierto, Apple tiene un truco para esto, utiliza un bitmap de recorte, pero es una técnica patentada.
Tercero, inventé una especificación de programación (API) extremadamente simplificada basada en lo que aprendí del libro de OS/2, y esto facilito mi codificación por mucho. Mientras que OS/2 (y Windows 3.1) usan un contexto gráfico para dibujar con tipos de pluma y patrones de relleno, yo consideré esto muy excesivo, y se dibuja directamente en la ventana con unidades de pixel.
La especificación de las primeras funciones de mi sistema de ventanas.
Diseño de interfaz de usuario para el depurador.
Con estas tres piezas comencé a codificar mi sistema de ventanas en código máquina desde el otoño de 1996 hasta la primavera de 1997. Tenía 18 años y nunca pensé que mi micro-ensamblador iba a jugar en contra de mí. Era muy fácil de usar, y podía codificar rutinas muy rápido, y entonces mi micro-desensamblador me mostraba lo mismo que escribí. Solo necesitaba escribir referencias de direcciones en papel, pero desafortunadamente esto no era exactamente código fuente.
Este artículo es posible porque tengo dos discos con esa versión primitiva de código máquina de 1997. Sabía que tenía algo bueno, aunque fuera primitivo, y no había otro sistema operativo de ventanas que arrancara de un disco flexible, pero es imposible experimentarlo si no tienes una computadora con Am29000, así que codifiquemos un emulador.
Los discos de 1997
El directorio principal del disco que arranca luce como esto:
El directorio del disco que arranca. ESPEJO.IMG es el vaciado, y los folders tienen la fecha de restauración.
Dos discos flexibles de 1.44mb de capacidad con mi sistema operativo de ventanas y programas.
Mi primera tarea es escribir el emulador de Am29000. Tengo el manual del usuario en algún lado, pero en lugar de perder tiempo buscándolo, perdí el tiempo buscándolo en Google, y nada disponible. Me sorprendió porque estaba disponible hace unos años, de cualquier forma fui a
archive.org y lo obtuve fácilmente tecleando el número del chip.
Considero que las instrucciones de aserción relacional el mayor desperdicio de espacio de silicio en la serie de procesadores Am29k. Supongo que los diseñadores tenían un caso de uso con los militares que requerían el lenguaje Ada, pero solo estoy divagando.
Otra idea curiosa es el resultado de los operadores de comparación, un valor booleano de 0 y 0x80000000, y los saltos condicionales solo checan el bit alto para saltar, así que se necesita un corrimiento de 31 bits a la derecha para tener el valor booleano del lenguaje C.
Las instrucciones de suma y resta también traen equipaje extra. En este caso, el procesador genera una trampa si el resultado excede un valor sin signo o con signo. Esto significa que existe ADD y también ADDU, y ADDS. Si consideramos la bandera de acarreo, se incrementa a otras 3 instrucciones: ADDC, ADDCU, y ADDCS.
La instrucción de resta tiene un conjunto con operandos intercambiados, así que tenemos SUB, SUBU, SUBS, SUBC, SUBCU, SUBCS, pero también SUBR, SUBRU, SUBRS, SUBRC, SUBRCU, SUBRCS. Creo que no soy inteligente para encontrar un uso para SUBRCU.
¿Dónde está todo?
Solía creer que podía recordar todo, pero parece que mi cerebro está en desacuerdo. Tengo estas dos imágenes de disco, pero ¿dónde se cargan? Por suerte, puse un mensaje que dice "Arrancando", y el código apunta a este mensaje con estas dos instrucciones:
CONST gr68,0x0180
CONSTH gr68,0xbfff
Esto significa que el arranque se carga en 0xbfff0000. No significa que tuviera tanta memoria (la RAM comienza en 0x80000000), solo usa la característica del espejo del mapeador de RAM para siempre poner el código de arranque en los 64KB más altos de la RAM. También pude ver por el orden de bytes de las instrucciones que todo el código esta en formato big-endian. El procesador Am29000 permite escoger el orden de bytes con un bit en el registro de configuración.
Con el emulador en desarrollo, comencé a ejecutarlo con la imagen de disco. Para mi desesperación, descubrí que había creado una bonita pantalla de arranque que requería rutinas de ROM que no tenía a mano (dibujo de cajas, ilustración de icono, lectura del disco, y la configuración de los registros del procesador), así que hice un parche para saltar sobre estos, y el cargador pudo ejecutarse completo hasta saltar al vacío en 0x80000000, porque nada se había cargado.
Hice una ROM fantasma que lee el disco usando una instrucción especial codificada en el emulador, y que ignora servicios sin manejar.
El emulador incompleto se comporto bastante bien y pudo cargar el código inicial para mi sistema de ventanas, y entonces se interrumpió en la instrucción no implementada 0xe2. Me quedé atónito porque no aparecía instrucción alguna en el
Am29000 User’s Manual, hasta que bajé el
manual de Am29050. ¡Qué listo! Es la instrucción
MULTIPLU.
El manual del Am29000 está obsoleto en la definición de instrucciones con trampas. Cuando AMD creó el Am29050 con soporte de punto flotante e instrucciones de multiplicación en hardware, redefinieron el conjunto de instrucciones con trampas. Por ejemplo, el código 0xe4 es CVINTF en el Am29000 User’s Manual, pero es CONVERT en el manual del Am29050 manual, y AMD proveyó una librería de emulación matemático para permitir que los procesadores Am29000 ejecutaran software Am29050 usando estos nuevos códigos. Consideré que era demasiado escribir un emulador Am29050 (tal vez después), así que me limité al Am29000 porque encuentro divertido como implementa la instrucción de multiplicación usando una trampa y 32 instrucciones MUL contiguas (corrimiento de bits todo el camino)
Por cierto, obtuvimos nuestras muestras de procesadores Am29050 directamente desde el representante en México de AMD. Tomé nota de la fecha en mi cuaderno: 8 de abril de 1997. Desafortunadamente, el mismo año que AMD estaba tropezando y abandonó la línea Am29k para hacer el procesador K5, y de esta forma comenzó la guerra de procesadores con Intel.
Después de un breve desacuerdo con mi interpretación de la tabla de vectores, finalmente logré que la instrucción MULTIPLU activara una trampa para emulación, y corrí a implementar la instrucción MUL, y solo logré un bucle infinito de trampas de multiplicación. Sucede que para poder retornar de una trampa ¡debes reconstruir dos registros PC! Actualizar solo uno hacía que mi código se volviera a ejecutar y ponía la trampa en la dirección de retorno incorrecta.
Tuve que corregir estos defectos y cuando lo ejecuté de nuevo, salió de la rutina de multiplicación directamente a una trampa de sobrepasamiento *carita de payaso* y sí había un sobrepasamiento, porque estaba leyendo memoria que no recordaba que contenía cuando arrancaba la máquina. Puse algunos valores adivinados hasta que alcanzó la instrucción IRET, significando que podía retornar de la instrucción emulada.
Funcionó, y luego se detuvo en las instrucciones DIV sin implementar. Ciertamente tenía mis dudas acerca del proceso de bit de las instrucciones, pero lo implementé como decía el manual.
Llegué a un punto del código original donde comenzó a leer bytes de la memoria requiriendo otro bit implementado (para poner el apuntador de byte interno), junto con las instrucciones EXBYTE, EXHW, y EXHWS para extraer bytes, palabras sin signo y palabras con signo.
Pronto también tuve que implementar las instrucciones INBYTE y INHW .
¿Qué es esto?
Por alguna razón leía la puerta 0x90 muy profundo en la inicialización del sistema, peor, esperaba que el bit 7 se activara... ¡No se qué es esto! Solo puse un valor 0x80 en la puerta. Por cierto, leer puertas utiliza un valor de control para LOAD de 0x41. Los normales para memoria son 0x04 y 0x14 (para bytes o media palabras).
PC=0x80020954 CONST gr97,0x0010
PC=0x8002094C CPLT gr96,gr82,0xc8
PC=0x80020950 JMPF
PC=0x80020954 CONST gr97,0x0010
PC=0x8002094C CPLT gr96,gr82,0xc8
PC=0x80020950 JMPF
¡Excelente! Se atoró en un bucle checando un registro que nunca cambia, excepto que obviamente debería cambiar, pero solo puede suceder si las interrupciones se procesan. ¡Por supuesto! Olvidé completamente que hay una referencia de tiempo para el cambio de contexto, el registro Timer interno del procesador Am29000. Esto a la vez me iluminó en el uso temprano de las instrucciones MULTIPLU y DIVIDU, probablemente para obtener el valor de timer para cargar.
Añadí el código para atrapar el acceso a los registros del timer, y obtuve esto:
PC=0x80008F3C DIVREM
PC=0x80008F40 MFSR gr0,0x83
PC=0x80008F44 MTSR 0x83,gr74
PC=0x80008F48 MFSR gr74,0x02
PC=0x80008F4C CONST gr73,0x0400
PC=0x80008F50 OR gr74,gr74,gr73
PC=0x80008F54 MTSR 0x02,gr74
PC=0x80008F58 MTSR 0x84,gr79
PC=0x80008F5C MTSR 0x0c,gr78
PC=0x80008F60 MTSR 0x0b,gr77
PC=0x80008F64 MTSR 0x0a,gr76
PC=0x80008F68 IRET
PC=0x8000C100 CONST gr97,0x0000
PC=0x8000C104 CONSTH gr97,0x0100
PC=0x8000C108 OR gr96,gr96,gr97
PC=0x8000C10C MTSR 0x09,gr96
Catch! Timer counter
gr1=0xbfffefa8
gr64=0x80000000 gr65=0x00000046 gr66=0x005b8d80 gr67=0x00000001
gr68=0x00002710 gr69=0x00000000 gr70=0x00000000 gr71=0x00000000
gr72=0x00000000 gr73=0x00000400 gr74=0x0000057f gr75=0x00000000
gr76=0x8000c0fc gr77=0x8000c100 gr78=0x80002300 gr79=0x00000200
gr80=0x00000000 gr81=0x00000000 gr82=0x00000000 gr83=0x00000000
gr84=0x00000000 gr85=0x000597e8 gr86=0x80021800 gr87=0x00000000
gr88=0x00000000 gr89=0x00000002 gr90=0x00000000 gr91=0xffff0000
gr92=0xffff0000 gr93=0x80004020 gr94=0x80004120 gr95=0x00000000
gr96=0xffffffff gr97=0x01000000 gr98=0x00000000 gr99=0x80022818
Mi instrucción de división no funciona, así que el valor que se va a cargar esta mal (gr96 = 0xffffffff).
Pude ver antes que el valor del reloj probablemente debe estar en ciclos por segundo, porque la división es por 10000.
PC=0x8000C0F4 CONST gr97,0x2710
PC=0x8000C0F8 MTSRIM 0x83,0x0000
PC=0x8000C0FC DIVIDU gr96,gr96,gr97
Tuve que escribir una rutina para vaciar la RAM de medio megabyte y poder examinarla. Sucede que la velocidad de la computadora estaba fija dentro del sistema operativo, 6000000 o 6 mhz. ¿Dónde se perdía el valor? Me metí a depurar por casi tres horas.
No era un error en la rutina, en lugar de eso era una invocación de macro incorrecta en mi emulador. Usé RA en lugar de REG_A, y la macro era válida pero daba un dato incorrecto. Renombre la macro diabólica a _RA para evitar errores en el futuro.
¡Codifiquemos interrupciones!
El timer se decrementa en cada ciclo del procesador, así que es fácil de implementar ya que la mayor parte de las instrucciones son de un ciclo de largo, con excepción de las trampas y las instrucciones LOADM y STOREM.
Implementé la recarga del contador, y lo hice crear una interrupción. Ejecuté de nuevo la emulación y miré como entraba en el primer bucle, y salía correctamente cuando el timer contaba. Ahora entraba en un bucle diferente tratando de leer la puerta 0x0fd0. Recordemos que estaba conectado a un bus ISA, pero no había una forma fácil de decodificar los dos bits de direcciones bajos, en lugar de eso mi padre alambró A2 del procesador a A0 del ISA, y así sucesivamente. Así que 0x0fd0 / 4 = 0x03f4, la puerta del controlador de disco.
PC=0x80020A68 CONST gr97,0x0fd0
PC=0x80020A6C LOAD 0,0x41,gr97,gr97
Reading from port 0x00000fd0
PC=0x80020A70 AND gr97,gr97,0xc0
PC=0x80020A74 CPEQ gr97,gr97,0xc0
PC=0x80020A78 JMPT gr97,
PC=0x80020A7C ASEQ
PC=0x80020A80 ASEQ
PC=0x80020A84 ASEQ
PC=0x80020A88 ASEQ
PC=0x80020A8C ASEQ
PC=0x80020A90 ASEQ
PC=0x80020A94 ASEQ
PC=0x80020A98 JMPFDEC gr98,
PC=0x80020A9C ASEQ
PC=0x80020A68 CONST gr97,0x0fd0
PC=0x80020A6C LOAD 0,0x41,gr97,gr97
Hasta este punto, el desensamblaje generado por el depurador no tenía alguna información, así que agregué las direcciones para los saltos, y las instrucciones ASEQ con registros idénticos como operandos las reemplaza con nemonicos NOP, ya que esta es la forma de crear una instrucción NOP.
Después de detener la depuración en el primer acceso al disco flexible, descubrí que era muy simple. Estaba tratando de encender el motor del disco, y esta es la causa de que estuviera comprobando la cuenta de interrupciones en gr82, para esperar que girara el motor.
No iba a escribir una emulación de chip controlador de disco flexible (aunque estuve cerca porque busqué el datasheet del NEC 765), así que lo parchee para llamar al emulador directamente.
Hasta ahora dos días para escribir y depurar 1500 líneas de código fuente.
El floppy obsoleto
Momento de la trivia. ¿Reconoces los siguientes números de orden en las instrucciones CPEQ?
0xBFFA0868: 0x01ff60fe CONSTN gr96,0xfffe
0xBFFA086C: 0x60618260 CPEQ gr97,lr2,gr96
0xBFFA0870: 0xa4ff61d5 JMPF gr97,0xbffa07c4
0xBFFA0874: 0x70400101 NOP
0xBFFA0878: 0x25010140 SUB gr1,gr1,0x40
0xBFFA087C: 0x5e40017e ASGEU 0x40,gr1,gr126
0xBFFA0880: 0x15810160 ADD lr1,gr1,0x60
0xBFFA0884: 0x16146094 LOAD 0,0x14,gr96,lr20
0xBFFA0888: 0x0b606000 EXBYTE gr96,gr96,0x00
0xBFFA088C: 0x6161601a CPEQ gr97,gr96,0x1a
0xBFFA0890: 0xac0161f1 JMPT gr97,0xbffa1054
0xBFFA0894: 0x61616025 CPEQ gr97,gr96,0x25
0xBFFA0898: 0xac0161be JMPT gr97,0xbffa0f90
0xBFFA089C: 0x61616012 CPEQ gr97,gr96,0x12
0xBFFA08A0: 0xac016186 JMPT gr97,0xbffa0eb8
0xBFFA08A4: 0x61616000 CPEQ gr97,gr96,0x00
0xBFFA08A8: 0xac006112 JMPT gr97,0xbffa08f0
0xBFFA08AC: 0x61616003 CPEQ gr97,gr96,0x03
0xBFFA08B0: 0xac026117 JMPT gr97,0xbffa110c
0xBFFA08B4: 0x61616028 CPEQ gr97,gr96,0x28
0xBFFA08B8: 0xac02612c JMPT gr97,0xbffa1168
0xBFFA08BC: 0x6161602a CPEQ gr97,gr96,0x2a
0xBFFA08C0: 0xac0261be JMPT gr97,0xbffa13b8
0xBFFA08C4: 0x70400101 NOP
0xBFFA08C8: 0x70400101 NOP
0xBFFA08CC: 0x70400101 NOP
0xBFFA08D0: 0x70400101 NOP
0xBFFA08D4: 0x70400101 NOP
0xBFFA08D8: 0x03006002 CONST gr96,0x0002
0xBFFA08DC: 0x03006100 CONST gr97,0x0000
0xBFFA08E0: 0x15010140 ADD gr1,gr1,0x40
0xBFFA08E4: 0x70400101 NOP
0xBFFA08E8: 0xc0000080 JMPI lr0
0xBFFA08EC: 0x5641817f ASLEU 0x41,lr1,gr127
Esta rutina ensamblador codificado a mano maneja la unidad de disco flexible. En lugar de accederlo directamente, lo abstrae para que el sistema operativo maneje todo con la misma interfaz, y estas son órdenes SCSI.
- 0x1a - Mode Sense.
- 0x25 - Read Capacity.
- 0x12 - Inquiry.
- 0x00 - Test Ready.
- 0x03 - Request Sense.
- 0x28 - Read 10.
- 0x2a - Write 10.
Note como la rutina devuelve dos valores de estatus al mismo tiempo. Una cosa que es fácil de hacer en ensamblador, pero no es posible con el lenguaje C.
Este elefante de software esta construído de varios módulos de código máquina. Uno de estos es el controlador de disco flexible, disfrazado como unidad SCSI -2 (esto es correcto, negativo 2) porque las unidades 0 a 7 son para unidades SCSI reales, todo heredado de mi
sistema operativo de transputer. Y a la vez las puertas 0x90 y 0x94 estaban conectadas a un controlador SCSI WD33C93. Me pregunto que pasó con esos discos duros.
Note también que la instrucción extra después de JMPI lr0 es una instrucción de ejecución retrasada. Cada salto o llamada del procesador Am29000 tiene este espacio para una instrucción que se ejecuta antes del salto.
Mañana de domingo
Después de codificar la emulación SCSI del disco flexible, tomando referencia del código máquina, corrí el emulador esperando que comenzara a leer los siguientes archivos para completer el patrón de arranque, pero en lugar de eso solo miraba la capacidad del floppy y fallaba silenciosamente en algún lado.
Es bastante más fácil cuando choca con una instrucción indefinida, pero corría en círculos. Yendo a través del rastreo de depuración pude ver que esperaba un valor 0x42 en la puerta 0x94, así que lo puse, y la ejecución avanzó y crasheó.
¡Ahora inicializaba las puertas serie! En caso de que no recuerdes solíamos usar COM1 a COM4 localizados en las puertas 0x03f8, 0x2f8, 0x3e8 y 0x2e8 respectivamente. Una vez implementado un código para no hacer nada. La siguiente falla ocurría al intentar acceder la unidad para cargar un archivo, rastrée el nombre de archivo a la dirección 0x8000bd20. Este es el volcado:
bff8bd20: 0058003a 002f0053 00690073 00740065 006d0061 002f0043 006f006e 00740072
bff8bd40: 006f006c 00610064 006f0072 00650073 002f0056 00690064 0065006f 002f0043
bff8bd60: 004c002d 00470044 00350034 00320039 00200038 00300030 00780036 00300030
bff8bd80: 00780036 0034006b 0000
Dice "X:/Sistema/Controladores/Video/CL-GD5429 800x600x64k" y puedes ver que cada letra es media palabra, muy audaz para un sistema con 512 KB de RAM, pero entonces estaba seguro de que Unicode era el futuro (y sí, fue una buena predicción) Este es un nombre de controlador de video, y la Cirrus Logic GD5429 era una tarjeta VGA muy popular entonces.
La letra de unidad X era mi letra "mágica" para ser reemplazada por la unidad de arranque. El problema es que estaba situada a J (dirección 0xbffffbf0), y lo rastrée al comienzo del código de arranque y lee la locación 0xbfffffdc sin un valor válido, probé primero cero y la unidad de arranque cambió a C. Después de alguna ingeniería en reversa vi que el valor 0x40 era el inicio correcto para la unidad A.
Tuve que agregar las instrucciones LOADM y STOREM ya que estas manejan el volcado de registros locales a memoria cuando la pila de registros esta llena, y las instrucciones MULU, MULTM, y MULTMU, las últimas dos generan trampas.
La organización de los registros es por recomendación de AMD, usando como "globales" los registros globales gr64-gr95, y para procedimientos los registros globales "locales" gr96-gr127. Además, el cuadro de pila está cacheado en los registros lr0-lr127. Solo estructuras grandes se ponen en memoria usando gr125 como apuntador de pila, así que se tienen apuntadores de pila duales (gr1 y gr125).
La nueva bitácora de depuración me mostró la secuencia de buscar el controlador de video:
Reading sector 0, length 1
floppy_scsi: executing command $28
Reading sector 8, length 8
floppy_scsi: executing command $28
Reading sector 16, length 8
floppy_scsi: executing command $28
Reading sector 24, length 8
floppy_scsi: executing command $28
Reading sector 40, length 8
floppy_scsi: executing command $28
Reading sector 72, length 8
Y luego un crash. Pude ver como abría el archivo, inicializaba los buffers, y entonces llamaba la operación de lectura del sistema de archivo.
260603 PC=0x8000FC7C ADD gr96,lr4,0x20
260604 PC=0x8000FC80 LOAD 0,0x04,gr96,gr96
260605 PC=0x8000FC84 JMPI gr96
260606 PC=0x8000FC88 NOP
260607 PC=0x80010030 ASGEU 0x40,gr1,gr126
260608 PC=0x80010034 ADD lr1,gr1,0x44
Se puede ver que el salto está una palabra adelante de la dirección correcta, así que le falta la instrucción SUB gr1,gr1. Inmediatamente me di cuenta de que el problema era la lectura del registro PC1 del procesador para obtener una dirección relativa de esta forma MFSR gr96,PC1.
Descubrí para mi gran horror que mis registros PC estaban al revés, PC0 debía apuntar a la siguiente instrucción, mientras que PC1 debía apuntar a la instrucción actual. Además la corrección daño mi código de manejo de trampas.
Después de volver sobre este, encontré una forma de hacerlo más elegante y comprensible. Como este es un procesador estático, hay un comportamiento no descrito de valores de antes y después para contadores de programa. Ahora cuando decodifica una instrucción, automáticamente lee la instrucción de PC1 y salva el PC0, PC1 y PC2 actuales en los registros del procesador (los valores correctos para retornar de trampas), y entonces los contadores sombra de PC cambian lugar de forma que PC0 sombra obtiene la dirección nueva para saltos (recordemos que cada salto tiene una instrucción retrasada).
La reescritura del código permitió que la emulación cargara el controlador de video del disco flexible, y llegó más lejos que nunca al intentar borrar la memoria de video:
296854 PC=0x800273A8 MTSR 0x02,lr6
296855 PC=0x800273AC CONST lr2,0x0000
296856 PC=0x800273B0 CONSTH lr2,0x0028
296857 PC=0x800273B4 CONST lr3,0x00ff
296858 PC=0x800273B8 STORE 0,0x00,lr3,lr2
Unhandled memory control 0x1e008382
El valor especial 0x00 instruye al Am29000 a acceder la memoria en el bus ISA (el valor de control es diferente de 0x04 que se usa para memoria interna, o 0x41 para puertas de entrada y salida). ¡Estaba muy cerca de ver algo en la pantalla!
La memoria del humano está en blanco
Tenía un pequeño problema ¡no recordaba como se mapeaba la memoria RAM de video! Y también noté inmediatamente que había rutinas de aceleración específicas para el Cirrus Logic GD5429, en particular el cursor de hardware. Nunca supe que le iba a hacer más difícil el trabajo a mi yo del futuro.
Esta tarjeta en particular tenía 1 MB de RAM de video (un verdadero monstruo de los 90s) permitiendo 800x600 pixeles con color de 16 bits.
Podía tomarme varios días tratar de escribir una pequeña emulación de todo lo que necesitaba. Intenté buscar una emulación de Cirrus Logic GD5429, y encontré que qemu y pcem ya lo hacían. Ambos también son código abierto, pero el código de emulación es increíblemente dependiente de varias otras estructuras requeridas para emulación más simple y rápida. En particular, los archivos incluídos llaman otros archivos totalmente no relacionados, como la estructura de tiempo usada en sus emuladores o el soporte PCI que yo no requería.
Intenté un día entero adaptar uno de estos pero encontré la tarea superdifícil y usando demasiado de mi tiempo disponible. Podía escribir mi propia emulación y el manojo de funciones usado por mi sistema operativo, así que descargué el
CL-GD5429 Technical Reference Manual.
No debía ser demasiado complicado, tomando cualquier acceso al CRTC, Graphic Controller, Attribute Register, y Sequencer Register, guardarlos en un arreglo, y atrapar accesos a la memoria de video, memoria de cursor y registros de aceleración.
También agregué una salida de depuración con los valores de los registros, y tuve un montón de buenos recuerdos leyendo de nuevo el manual técnico (en ese tiempo lo tenía impreso porque los lectores PDF eran lentos) y pude determinar el registro donde el modo RGB 5-6-5 era configurado (valor 0xe1)
Cirrus Logic hizo fácil el acceso a la memoria de video permitiendo escribir ocho pixeles con una sola escritura de un byte. Esto es porque expandía la escritura para tomar datos de pixel de los registros del controlador gráfico extendido, y también se podía poner una mascara para que pudieras dibujar una letra bitmap sin afectar el fondo.
Sin embargo, también permitía cambiar los mapeados en el vuelo a una memoria plana donde se podía depositar directamente pixeles de 16 bits en la memoria RAM. Como la ventana de la RAM es de 128 kb, existe un esquema de registros de bancos.
652102 PC=0x80027DEC CONST gr100,0x0f38
652103 PC=0x80027DF0 CONST gr97,0x000b
652104 PC=0x80027DF4 STORE 0,0x42,gr97,gr100
652105 PC=0x80027DF8 ADD lr4,lr18,0x00
652106 PC=0x80027DFC SUB lr2,lr17,0x02
652107 PC=0x80027E00 SLL gr96,lr15,0x05
652108 PC=0x80027E04 SLL gr98,gr96,0x03
652109 PC=0x80027E08 ADD gr96,gr96,gr98
652110 PC=0x80027E0C ADD gr96,gr96,gr98
652111 PC=0x80027E10 ADD gr96,gr96,gr98
652112 PC=0x80027E14 ADD lr3,gr96,lr14
652113 PC=0x80027E18 CONST gr103,0x0000
652114 PC=0x80027E1C CONSTH gr103,0x0028
652115 PC=0x80027E20 CONST gr105,0x8001
652116 PC=0x80027E24 ADD lr5,lr4,0x00
652117 PC=0x80027E28 SRL gr98,lr3,0x0f
652118 PC=0x80027E2C SLL gr96,lr3,0x11
652119 PC=0x80027E30 SRL gr96,gr96,0x11
652120 PC=0x80027E34 ADD gr99,gr96,lr16
652121 PC=0x80027E38 SUB lr8,gr99,gr105
652122 PC=0x80027E3C SLL gr98,gr98,0x0c
652123 PC=0x80027E40 OR gr102,gr98,0x09
652124 PC=0x80027E44 STORE 0,0x42,gr102,gr100
Y aquí está. Cambiando el modo de escritura a 0x00 (registro gráfico 0x0b), calculando lr15 * 800 + lr14 (dirección de pixel para 800x600), poniendo gr103 para apuntar a la memoria de video en 0x0a0000, extrayendo la página de 64K en gr98 y escribiendo el registro gráfico 0x09 con el valor corrido 4 lugares para cubrir el espacio de dirección de 1 MB.
Tu procesador está malito
Después de que el código básico de soporte del chip de video estaba ahí, la ejecución pudo proceder, y me atoré en el ciclo un millón con una conversión de float a double que fallaba porque la mantisa no era cero. Decidí parchear una bandera para evitar la falla pero no antes de usar todo el día viendo si había una rutina para poner la bandera.
Así como la ejecución avanzaba, pude ver que había unos defectos en las instrucciones MUL y MULL. Sucede que el bit de signo insertado en el resultado debe ser complementado en caso de un sobrepasamiento aritmético. Corrí en círculos por cuatro horas hasta que corregí la detección de sobrepasamiento.
Una vez que todas las instrucciones estuvieron correctas, la ejecución continuó sin parar, y esto significa que el sistema operativo estaba corriendo. Ahora necesitaba ver que aparecía en la pantalla, pero la máquina "pensaba" diferente.
Repentinamente la ejecución se detuvo en 295,433 ciclos con una instrucción inválida. Como el comportamiento de la emulación es determinístico, algo había cambiado, y note que algunas escrituras de disco se habían realizado. Hice una comparación de la imagen de disco original y la nueva, y se había reescrito la tabla de asignación de archivos, así que cualquier acceso futuro de archivos traía basura y fallaba.
Ahora solo tenía que revisar cuatro millones de instrucciones ejecutadas intentando ver que había hecho que el sistema operativo escribiera en el lugar equivocado.
Rastreando el bicho
El error sucedía en una tormenta perfecta. La imagen de disco tenía tres archivos temporales que se borraban al inicio del sistema operativo. Sin embargo, algo corrompía la imagen de disco.
En el primer vistazo (o 12 horas después de esfuerzos de leer el archivo de depuración, bebiendo mucho café, e intentando que el volcado de RAM tuviera sentido) pensé que la actualización de FAT estaba mal, pero después de seguir la lógica de la creación de archivos, descubrí que la búsqueda de bloque libre daba un número incorrecto, cuando intentaba escribir el primer bloque del archivo lo hacía en el bloque 1 (casualmente el mismo bloque de la FAT)
La primera vez escribía datos en el archivo, y buscaba un bloque libre. Desafortunadamente, la rutina 0x8000f20c para obtener el bloque libre era simplemente gr96 = drive[0x70 / 4] esto significa que el valor era precalculado.
Esto me hizo retornar casi a los primeros ciclos en configurar la unidad para lectura (204575 ciclos en ejecución), donde lee la FAT completa para obtener el número de bloques libres. En 0x8000e7f0 pone el contador en lr5, y lr7 a cero (bloques libres). El bucle mismo es 0x8000e7f8 (la subrutina 0x8000e864 lee la FAT).
Si el resultado de la instrucción CPEQ es verdadero, corre el valor verdadero (0x80000000) 31 bits a la derecha, y lo suma al registro lr7. Si encuentra un bloque libre, checa si es la primera vez, y salva el número de bloque (para crear archivos después).
El número total de bloques libres se salva en la estructura de unidad lr18+0x6c y el primer bloque libre en lr18+0x70, y... la rutina salva correctamente el número de bloque.
Sin embargo, ahora tenía una dirección fija (0x80022cb0) que podía observar buscando el valor equivocado. Descubrí que era escrita no solamente con 1 sino también con -1 y 2. Este era un gran error.
Watching address 0x80022cb0 now written with 0x00000123
Watching address 0x80022cb0 now written with 0x00000001
Watching address 0x80022cb0 now written with 0xffffffff
Watching address 0x80022cb0 now written with 0x00000001
Watching address 0x80022cb0 now written with 0xffffffff
Watching address 0x80022cb0 now written with 0x00000001
Watching address 0x80022cb0 now written with 0xffffffff
Watching address 0x80022cb0 now written with 0x00000001
Watching address 0x80022cb0 now written with 0x00000002
Deteniendo la ejecución en el punto donde era escrito con 1, descubrí que era la rutina de escritura de FAT, y trabajaba correctamente para liberar un bloque, y luego cambiaba el primer bloque libre a un número menor. El problema era de donde era llamada.
La rutina base era una operación de borrado de archivos llamada en 0x8000c7b8. El archivo para ser borrado era X:/Sistema/Temporal/00000001.tmp con bloque 0x0123, correctamente anotado en la FAT como enlazado a 0x124, y luego -1 para finalizar la cadena.
Y entonces encontré al culpable, la instrucción EXHWS, extrae media palabra con signo:
if (d >= 0x8000)
d = 0x10000 - d;
¿Puedes ver el bug? Esta pieza de código debe trasladar un valor de 16 bits en un valor con signo extendido de 32 bits. En lugar de eso, nos da el valor absoluto.
La rutina correcta es:
if (d >= 0x8000)
d -= 0x10000;
Así, un error de una sola línea me tomó tres días completos de depuración. Y por supuesto, este error se puede crear en cualquier lenguaje.
Ahora la FAT no era destruída, y podía volver a buscar porque seguí crasheando.
48071124 ciclos
El siguiente crash solo me tomó cuatro horas para solucionarlo. La instrucción MULTIPLY fallaba con valores grandes de entrada. Sin embargo, noté que la trampa estaba desactivada en el procesador Am29050, pero esta bandera no era transferida a la emulación matemática, y se botaba con un sobrepasamiento.
Reemplacé el contenido de gr95 con 0x00040040 (ya era 0x00040000 por la bandera para CONVERT) y ¡et voila! La emulación corrió hasta 48 millones de ciclos y se quedó en un bucle infinito. Estaba esperando que algo pasara, pero en lugar de eso una advertencia de mi Macbook Air, ya que el archivo de depuración era ahora de más de 150 megabytes, llenando muy rápido el SSD del MacBook Air.
Muy bien ¿pero qué estaba haciendo el sistema operativo en el arranque? Descubrí que mi sistema operativo no traía fonts bitmapeados, en lugar de eso creaba tres fonts al vuelo para el sistema. Como dije antes, descubrí como dibujar ventanas con X-Window, pero también descubrí el rasterizador de Type 1 con licencia MIT.
En los buenos viejos días, estabas limitado por los tipos de letra provistos por tu impresora láser. Podía ser Courier 12 puntos, en negrita e itálica. Y si requerías más fonts, tenías que comprar cartuchos ROM con fonts. La gente suertuda podía comprar un cartucho Postscript y obtener tipos ilimitados escalables. Y por cierto había un muy buen chance de que tu Postscript tuviera un Am29050 en el tablero.
Pero yo no era tan suertudo, en lugar de eso descubrí que Type 1 era el formato de font usado por Postscript. X-Window también proveía unos fonts básicos. Los fonts de X-window no tenían trucos para verse tan bien como los fonts de Adobe, yo obtuve los míos de una versión temprana de Acrobat Reader.
¿Adivinen que hice? Como no tenía un compilador de C aún, así que porté el rasterizador de Type 1 en una conversión manual del código fuente en C a código máquina. No recuerdo cuanto tiempo me tomó, pero debo tener en algún lugar una impresión del código fuente anotada con direcciones de código máquina.
Tiempo de decir que no se como tenía tanta energía para hacer estos programas de código máquina y ports manuales.
Yo en 2007 junto con una versión avanzado de mi sistema operativo en pantalla.
libSDL2, es bueno verte
Ahora el cursor debía estar en pantalla, incluso si no podía verlo. El único problema es que no había escrito todavía las rutinas de aceleración ni una forma de poner la RAM de video en pantalla.
Me tomó todo el día descubir porque Xcode no enlazaba libSDL2 al proyecto. Pista: No es en las opciones de proyecto. Finalmente descubrí que solamente debía arrastrar y soltar el folder SDL2.framework en tus frameworks del árbol de proyecto. Unas pocas horas después, descubrí que me faltaba la llamada SDL_UpdateWindowSurface(window) para actualizar la ventana ¡y obtuve basura inteligente en la pantalla!
Esto me dio un empujón de adrenalina y procedí a corregir mi emulación de GD5429 para copiar correctamente bitmaps expandidos (las letras se volvieron legibles), y entonces una emulación mínima requerida de bitblt para llenar el fondo con el patrón del wallpaper, y finalmente la misma función para una copia de patrón de 8x8 y que pudiera llenar los fondos de las ventanas. ¡Fue una sorpresa para mí! Había olvidado como se veía en pantalla. Además tenía sueño porque estaba haciendo esto a la 1am.
La emulación limitada de la Cirrus Logic GD5429 esta codificada en solo 500 líneas de lenguaje C, muy pequeña y legible.
Para la entrada, recordé que usaba un ratón serie a 1200 baudios. Busqué en el código máquina un registro configurado a 1200, porque tiene una librería de driver serie que convierte esto a la frecuencia de reloj del 16550. La subrutina para configurar el ratón está localizada en 0x80011800. ¿Sabías que el ratón te envía una letra indicando su protocolo? Puedes obtener M para protocolo Microsoft, o H para el protocolo de Mouse Systems. Después de decodificar los bytes recibidos por la puerta serie se obtenía los registros gr92 y gr91 con la posición del ratón.
Podía haber hecho la emulación completa de un chip serial asincrono, junto con un ratón, pero en lugar de eso recibí los eventos SDL y cambié directamente los mismos registros que mi código. La coordenada X está en gr92, la coordenada Y en gr91, y el estado de los botones se salva en gr90 (bit 2 = botón izquierdo, bit 1 = botón del medio, bit 0 = botón derecho) Solíamos tener ratones de tres botones antes de que fuera común ver ratones para juegos con 6 o 8 botones.
También necesitaba un interfaz de teclado. El chip en la computadora leía códigos de teclado AT, e interrumpía el procesador para llenar el buffer con estos. Los códigos AT lucen como esto
https://webdocs.cs.ualberta.ca/~amaral/courses/329/labs/scancodes.html
Las locaciones de inicio y final del buffer de teclado están determinadas por 0xbffffa00 y 0xbffffa04. Mientras que 0xbffffa08 es el escritor (donde la interrupción pone el siguiente dato), y 0xbffffa0c es el lector (donde se consumen los datos).
Por suerto, libSDL2 provee de una entrada de texto UTF-8 que podía procesar, y usé exactamente las mismas locaciones para guardar las teclas Unicode, y parchée el controlador de teclado para evitar la decodificación de los códigos de teclado y pasar directamente los caracteres Unicode.
Para el pulido final, quería ver el sprite de mi cursor original. No tuve muchas dificultades esta vez, hice código para dibujar el cursor directamente en la memoria de video, y guardé y restauré el fondo antes de ilustarlo en la pantalla.
Al final el emulador esta compuesto de 3075 líneas de código fuente elaboradas en dos semanas.
Creando la distro
Sería muy fácil publicar los dos discos flexible exactamente como en mis archivos. Desafortunadamente, estos discos fueron reusados varias veces, contienen basura desconocida en sectores sin usar, y varios programas están defectuosos y en varios estados de desarrollo.
Primero, tuve que reemplazar los fonts de Adobe con fonts libres Type 1. Para esto, diseñé la utilidad build_disk.c (esta en mi git, muy parecida a la de mi sistema operativo de transputer) para crear una imagen de disco, luego parchée el sistema operativo para permitir dos unidades de disco abiertas, y pude copiar los nuevos fonts (familia Luxi) simplemente arrastrando y soltando. Parchée manualmente el sistema operativo para usar los nuevos fonts ¡y listo! Funcionó muy bien.
./buildboot -l Fonts -a5 disk2.img "fonts/Luxi Sans" "fonts/Luxi Sans Bold" "fonts/Luxi Serif" "fonts/Luxi Mono"
Segundo, había un driver para una impresora HP LaserJet IIP, pero copié dos drivers extras (Canon BJC-600 y Epson Stylus 600) del segundo disco flexible, y agregué soporte para impresión en el emulador. Esto significa que si tuvieras una de estas impresoras, puedes imprimir texto editado en el programa Editor. Pude mirar un archivo impreso para HP LasertJet IIp usando
https://www.coolutils.com/online/PCLViewer
Tercero, hice un programa para "limpiar" la imagen de disco flexible, borrando bloques sin usar y removiendo datos al final de los archivos (sanitize.c en mi git)
Así que aquí está. Mi sistema operativo para el procesador Am29000 escrito enteramente en código máquina. Un logro que se hace una sola vez en la vida.
Por cierto, tiene un nombre, muy dentro en la imagen binaria dice Windows Fénix. Alguien preguntó el nombre de mi sistema operativo, y había estado viendo X-Men’97 en esa época, y mi personaje preferido era Jean Grey (Phoenix), y de ahí salió.
Descargas
Puedes obtener el emulador de
https://github.com/nanochess/Am29000 y hay releases precompiladas para Windows y macOS. Tal vez necesites instalar libSDL2. Cuando aparezca la ventana azul, debes arrastrar y soltar la imagen de disco
disk.img en la ventana. Si arrastras una segunda imagen de disco, será usada como unidad B.
Si imprimes algo, el archivo printer.txt aparecerá en tu folder de documentos.
Que tenemos aquí
Mueve el cursor con el ratón o el pad. Haz click y sostén en la barra de título para mover ventanas. El botón gris en la parte superior izquierda cerrará las ventanas.
Si haces click derecho en el fondo de la ventana "Arrancador", te dará diferentes esquemas de color.
La ventana "Arrancador" es como un atajo para lanzar un juego de ajedrez, el archivero (puedes arrastra y soltar archivos entre dos ventanas de archivero), edición de color, chequeo de disco y utilidades (puedes arrastra archivos al basurero, o arrastrar un folder para crear un nuevo folder en el archivero)
- Colores: Una forma de editar los colores del sistema operativo.
- Depurador. Un depurador básico que puede desensamblar memoria global y las tareas actuales.
- Editor. Un editor de texto que puede imprimir documentos (muchos bugs)
- Promedio. Uso del procesador y memoria libre.
- Tipografía: Una forma de probar tipos de letra y usarlos en el sistema.
- CDROM: Activa un sistema de archivo ISO-9660, pero no hay soporte de CD en el emulador.
- Terminal: Una terminal serie, pero no hay soporte para puertas serie.
- Circuito impreso: Una demo de gráficos vectoriales.
- El folder Fondos contiene imágenes de fondo, solo dale click para activar una.
- El folder Sistema/Protectores de pantalla contiene un protector de pantalla llamado Acuario, pero no hay chequeo de tiempo para ejecutarlo. Los gráficos están basados en XFishtank.
Lo interno
¿Cómo funciona el sistema operativo? Comienza por inicializar el estado del procesador, pone el paquete de emulación matemática, llama las rutinas de inicialización para cada servicio provisto, borra los archivos temporales del disco, carga el controlador de video, y construye tres tipos de letra para el sistema. Los servicios son invocados cargando el número de función en el registro 121, y haciendo CALL a una instrucción simple ASNEQ 0x42,gr1,gr1. Existen los servicios 0x42-0x4f.
- 0x42 - Sistema de ventanas.
- 0x43 - Controlador de video.
- 0x44 - Controlador de teclado.
- 0x45 - Manejo de memoria.
- 0x46 - Controlador SCSI.
- 0x47 - Controlador de puerta serie.
- 0x48 - Interfaz del sistema de archivos.
- 0x49 - Controlador de ratón.
- 0x4a - Manejador de fonts.
- 0x4b - Ventanas de mensajes.
- 0x4c - Servicios multimedia (no disponible en esta iteración)
- 0x4d - Servicios de configuración.
- 0x4e - Servicios de impresión.
- 0x4f - Servicios de red (no disponible en esta iteración)
Por supuesto, esto esta completamente atado a la arquitectura Am29k.
Creo que obtuve el paquete de emulación matemática de un binario Am29000 que obtuve en alguna parte de Internet. Las rutinas de manejo de la pila para registros locales vienen del manual de usuario del procesador (ASGEU 0x40 y ASLEU 0x41) Los vectores 0x00-0x3f son para las trampas internas del procesador y emulación de instrucciones.
He provisto un desensamblaje completo y un volcado de RAM en mi git para propósitos de referencia. Tal vez lo documente en el futuro.
Aprendí mi lección. Esta fue la última vez que hice algo tan grande en código máquina. Los programas requerían demasiada documentación y comentar direcciones, y cada cambio era un dolor. Algunas mejoras o correcciones no cabían en el mismo espacio, lentamente haciendo el sistema operativo un caos mayor. El editor de texto en particular nunca se corrigió porque era demasiado complejo. Sin embargo, viniendo de hacer lo mismo en la computadora Z280, era un poco díficil abandonar las viejas mañas.
Mientras escribía el emulador y este artículo, note que la fecha estaba fija en el sistema operativo completo, así que todos los archivos dicen 1997, incluso si los mensajes de copyright internos dicen 1998, y uno de mis discos dice 1998 en la etiqueta. Basado en estos datos y mi cuaderno, desarrollé esto entre finales de 1996 y principios de 1998.
En 1997 cambié el orden de bytes a little-endian para la G11V2, y puse el sistema operativo completo en EPROM (tenía un megabyte de memoria EPROM) permitiendo ejecutar programas más grandes.
Fue hasta diciembre de 1997 que finalmente comencé a portar mi compilador de C de mi sistema operativo transputer, y ya funcionaba en junio de 1998, y me ayudó a programar más rápido. Fue importante para crear mi primer navegador de Internet en 1999 para la computadora G11V2, pero por supuesto, eso es para otro artículo.