;==============================================
;Escribir un programa que por cada TIC de reloj
;muestre una letra aleatoria en una posicion
;aleatoria de la pantalla.
;Al apretar una tecla se borra la pantalla
;NOTA:
;1 TIC=55 mseg
;==============================================
;Base=0
;Límite=1MB
DIR_LINEAL_ROM   EQU 0xFFFF0000
BUFFER_VIDEO     EQU 0x000B8000
comienzo_segmento:
gdt:
        DQ 0                   ;Descriptor NULO

;Segmento de código flat: base cero y límite 4 GB.
sel_codigo  EQU $-gdt
        dw 0xFFFF              ;Limite 15-0
        dw 0                   ;Base 0-15
        db 0                   ;Base 16-23
        db 0x9A                ;Byte derechos de acceso
        db 0xCF                ;G=1, D=1, Límite 19-16
        db 0                   ;Base 24-31

;Segmento de datos: base cero y límite 1 MB.
sel_datos EQU $-gdt
        dw 0xFFFF              ;Limite 15-0
        dw 0x0                 ;Base 0-15
        db 0x0                 ;Base 16-23
        db 0x92                ;Byte derechos de acceso
        db 0x4F                ;G=0, D=1, Límite 19-16
        db 0                   ;Base 24-31

gdt_size  EQU $-gdt            ;Tamaño de la GDT.

idt:
        TIMES 0x20 DQ 0        ;Compuertas no usadas para int 0 a 0x19.
; Compuerta de interrupción del timer.
        dw ManejadorTimer      ;Offset 15-0
        dw sel_codigo          ;Selector
        db 0                   ;No usado.
        db 0x8E                ;Compuerta de interrupción.
        dw 0xFFFF              ;Offset 31-16
; Compuerta de interrupción de teclado.
        dw ManejadorTeclado    ;Offset 15-0
        dw sel_codigo          ;Selector.
        db 0                   ;No usado.
        db 0x8E                ;Compuerta de interrupción.
        dw 0xFFFF              ;Offset 31-16
idt_size  EQU $-idt            ;Tamaño de la IDT.

gdtr    DW gdt_size-1          ;Imagen de GDTR.
        DD gdt + DIR_LINEAL_ROM
idtr    DW idt_size-1          ;Imagen de IDTR.
        DD idt + DIR_LINEAL_ROM

var_aleatoria DD 0

        BITS 16                ;El código a coninuación está en
                               ;segmento de código de 16 bits (modo real).
inicio:
        CLI                    ;Deshabilito interrupciones
        %include "init_pci.inc";Inicialización de bus PCI y video
;================================;
; Pasaje a modo protegido        ;
;================================;
        o32 LGDT [cs:gdtr]     ;Cargo GDTR (uso o32 porque la GDT está en ROM)
        o32 LIDT [cs:idtr]     ;Cargo IDTR (uso o32 porque la IDT está en ROM)

        MOV EAX,CR0            ;Entro a modo protegido
        OR AL,1
        MOV CR0,EAX   
xchg bx,bx
        JMP dword sel_codigo:modo_protegido + DIR_LINEAL_ROM   ;Cambio CS e EIP

        BITS 32                   ;Código a continuación en segmento de 32 bits.

;======================================================================;
; Subrutina aleatorio:                                                 ;
; Actualiza la semilla pseudoaleatoria de 32 bits y luego obtiene      ;
; un número entre 0 y EBX - 1, poniendo el resultado en EAX            ;
;======================================================================;
aleatorio:                        ;Calcula número aleatorio emtre 0 y EBX-1.
        ADD dword [var_aleatoria],0x1234567
        MOV EAX,314159265
        MUL dword [var_aleatoria] ;EDX:EAX=producto
        MOV [var_aleatoria],EAX   ;EAX = Semilla de número pseudoaleatorio.
        MUL EBX                   ;EDX:EAX=producto
        MOV EAX,EDX               ;0 <= EDX < EBX. Cargar en EAX el resultado.
        RET                       ;Fin de la subrutina.

;======================================================================;
; Subrutina borrar_pantalla:                                           ;
; Hacer un ciclo de 25*80 caracteres (25 filas por 80 columnas)        ;
; poniendo carácter en blanco y atributo blanco sobre negro.           ;
;======================================================================;
borrar_pantalla:
        MOV ESI,BUFFER_VIDEO ;Puntero al buffer de video.
        MOV ECX,25*80       ;Cantidad de caracteres a borrar.
        MOV AX,0x0720       ;Caracter y atributo de espacio.
ciclo_borrar_pantalla:
        MOV [ESI],AX        ;Espacio en blanco sobre negro.
        ADD ESI,2           ;Apuntar al siguiente carácter.
        LOOP ciclo_borrar_pantalla  ;Seguir ciclo si no terminó.
        RET
;========================================================;
; Inicialización de periféricos en modo protegido        ;
;========================================================;
modo_protegido:
        MOV AX,sel_datos    ;Selector de datos
        MOV DS,AX
        MOV SS,AX	
        MOV ESP,0x8000      ;Puntero de pila en dirección lineal 0x8000.

; Inicializar controlador de teclado.
        MOV AL,0xFF         ;Enviar comando de reset al controlador
        OUT 0x64,AL         ;de teclado
        MOV ECX,256         ;Esperar que rearranque el controlador.
        LOOP $
        MOV ECX,0x10000
ciclo1:
        IN AL,0x60          ;Esperar que termine el reset del controlador.
        TEST AL,1
        LOOPZ ciclo1
        MOV AL,0xF4         ;Habilitar el teclado.
        OUT 0x64,AL
        MOV ECX,0x10000
ciclo2:
        IN AL,0x60          ;Esperar que termine el comando.
        TEST AL,1
        LOOPZ ciclo2
        IN AL,0x60          ;Vaciar el buffer de teclado.
; Inicializar timer para que interrumpa cada 54.9 milisegundos.
        MOV AL,00110100b    ;Canal cero, byte bajo y luego byte alto.
        OUT 0x43,AL
        MOV AL,0            ;Dividir 1193181 Hz por 65536. Eso da 18,2 Hz aprox.
        OUT 0x40,AL         ;Programar byte bajo del timer de 16 bits.
        OUT 0x40,AL         ;Programar byte alto del timer de 16 bits.
; Inicializar ambos PIC usando ICW (Initialization Control Words).
; ICW1 = Indicarle a los PIC que estamos inicializándolo.
        MOV AL,0x11         ;Palabra de inicialización (bit 4=1) indicando que 
                            ;se necesita ICW4 (bit 0=1)
        OUT 0x20,AL         ;Enviar ICW1 al primer PIC.
        OUT 0xA0,AL         ;Enviar ICW1 al segundo PIC.
        
; ICW2 = Indicarle a los PIC cuales son los vectores de interrupciones.
        MOV AL,0x20         ;El primer PIC va a usar los tipos de interr 0x20-0x27.
        OUT 0x21,AL         ;Enviar ICW2 al primer PIC.
        MOV AL,0x28         ;El segundo PIC va a usar los tipos de interr 0x28-0x2F.
        OUT 0xA1,AL         ;Enviar ICW2 al segundo PIC.
        
; ICW3 = Indicarle a los PIC como se conectan como master y slave.        
        MOV AL,4            ;Decirle al primer PIC que hay un PIC esclavo en IRQ2.
        OUT 0x21,AL         ;Enviar ICW3 al primer PIC.
        MOV AL,2            ;Decirle al segundo PIC su ID de cascada (2).
        OUT 0xA1,AL         ;Enviar ICW3 al segundo PIC.
        
; ICW4 = Información adicional sobre el entorno.    
        MOV AL,1            ;Poner el PIC en modo 8086.
        OUT 0x21,AL         ;Enviar ICW4 al primer PIC.
        OUT 0xA1,AL         ;Enviar ICW4 al segundo PIC.

; Indicar cuales son los IRQ habilitados.
        MOV AL,11111100b    ;Activar solo IRQ0 e IRQ1 poniendo estos bits a cero.
        OUT 0x21,AL         ;Enviar máscara al primer PIC.
        STI                 ;Habilitar interrupciones

ciclo_principal:
        HLT
        JMP ciclo_principal
;======================================================================;
; Manejador de interrupciones del timer tick                           ;
; Genera un carácter aleatorio en un posición aleatoria de la pantalla ;
;======================================================================;
ManejadorTimer:
        PUSHAD              ;Salvo los registros de uso general.
                            ;Obtengo la posición aleatoria
        MOV EBX,25*80       ;Cargo el parámetro en EBX
                            ;Rango de posiciones
        CALL aleatorio      ;Resultado en EAX
        LEA ESI,[BUFFER_VIDEO+2*EAX]; Obtener dirección efectiva (puntero)
                                    ; del carácter a mostrar.
                            ;Obtengo la letra aleatoria
        MOV EBX,"Z"-"A"+1   ;Cargo EBX con rango de letras (26)
        CALL aleatorio      ;EAX = Número entre 0 y 25.
        ADD AL,"A"          ;Convertir 0 - 25 a A - Z.
;xchg bx,bx
        MOV [ESI],AL        ;Escribir el código ASCII del carácter.
        MOV EBX,16
        CALL aleatorio      ;Obtener color aleatorio de frente.
        MOV [ESI+1],AL      ;Escribir el atributo.
        MOV AL,0x20         ;Envío End of Interrupt al PIC.
        OUT 0x20,AL

        POPAD               ;Restauro registros de uso general.
        IRET                ;Fin de la interrupción.

;======================================================================;
; Manejador de interrupciones del teclado                              ;
; Borra la pantalla                                                    ;
;======================================================================;
ManejadorTeclado:
        PUSHAD              ;Salvo los registros de uso general.

        IN AL,0x60          ;Leer tecla del buffer de teclado
        AND AL,AL
        JS fin_int9         ;Terminar si se suelta la tecla
        call borrar_pantalla
fin_int9:
        MOV AL,0x20         ;Envío End of Interrupt al PIC.
        OUT 0x20,AL
        POPAD               ;Restauro registros de uso general.
        IRET                ;Fin de la interrupción.
        
        TIMES 0xFFF0 - ($ - comienzo_segmento)  db 0   ;Primer relleno.
     
        BITS 16             ;El código a continuación va en 
                            ;segmento de código de 16 bits.
                             
reset:                      ;Dirección de arranque del procesador.
        JMP inicio - $$     ;Saltar al programa principal.
     
        TIMES 0x10 - ($ -reset) db 0      ;Segundo relleno.

