; This boot sector demonstrates interrupt redirection for keyboard and some ; keyboard raw operations in real mode. It makes a keyboard self test, and ; if it passes sets up an interrupt routine for IRQ 1 (int 9) which prints ; the keyboard scancodes at any key press/release. ;logical values %define FALSE 0x00 %define TRUE 0x01 ;real-mode segments %define LOADSEG 0x07C0 %define STACKSEG 0x9000 %define VIDEOSEG 0xB800 ;BIOS functions %define BIOS_10_PUTCHAR 0x0E %define BIOS_16_GETKEY 0x00 ;text styles %define PLAIN 0x07 %define EMPH 0x20 ;ports %define KBDATA 0x60 %define KBACK 0x61 ; used only in ancient XT keyboards (I'm not sure) %define KBCTRL 0x64 ;commands %define CMDTEST 0xAA ;bitmasks %define OUTBUF_FULL 0x01 %define INBUF_FULL 0x02 ;result codes %define TESTOK 0x55 [BITS 16] ; the bios starts out in 16-bit real mode [ORG 0] ; data offset = 0 jmp start ;******************************************* ; Data used in the boot-loading process ;******************************************* ;data bootdrv db 0 ;messages bootmsg db 'ONABSE-KB, from Pietro Braione',13,10 db 'V.1.0 may 2003',13,10,0 rebootmsg db 'Press any key to reboot',13,10,0 komsg db 'PANIC: keyboard self test failed.',13,10,0 okmsg db 'Keyboard self test passed.',13,10,0 scancodemsg db "Detected scancode: " scancodebyte db 0,0,13,10,0 ;******************************************* ; Procedures we are going to use ;******************************************* message: ; Dump ds:si to screen. ; Modifies: ax, bx, si + int 0x10 func 0x0E side effects. lodsb ; load byte at ds:si into al or al,al ; test if character is 0 (end) jz .done mov ah,BIOS_10_PUTCHAR ; Put character mov bx,PLAIN ; attribute (plain white on black) int 0x10 ; call BIOS jmp message .done: ret ; ******************************************************************** getkey: ; Waits for a keypress. ; Modifies: ah + int 0x16 func 0 side effects. mov ah,BIOS_16_GETKEY int 0x16 ret ; ******************************************************************** bytetostr: ; Converts the byte in al in a two-character string, and puts it ; at ds:si ; Modifies: ax, bx, flags. Does *not* modify si, so you can call ; message immediately after a call to bytetostr mov ah,al shr al,4 ; now al contains the high nibble of the byte... and ah,0x0F ; ...and ah the low nibble mov bx,0 ; resets the counter .cycle: cmp al,0x09 ; checks if the nibble in al is >=9 ja .above add al,'0' ; if it isn't, adds it to the ASCII code of '0' jmp .keepon .above: sub al,0x0a ; else, subtracts 10... add al,'A' ; ...and adds the ASCII code of 'A' .keepon: mov [bx+si],al ; then stores the obtained character or bx,bx ; loop exit condition jnz .retpoint inc bx ; next cycle: incs the counter... mov al,ah ; ...and loads the low nibble of the byte in al jmp .cycle .retpoint: ret ; ******************************************************************** wordtostr: ; Converts the word in ax in a four-character string, and puts it ; at ds:si ; Modifies: ax, bx, flags. Does *not* modify si, so you can call ; message immediately after a call to bytetostr ; TODO: check if it works! push ax shr ax,8 call bytetostr pop ax push si inc si inc si call bytetostr pop si ret ; ******************************************************************** wait_cmd: ; Waits for the command buffer to be available ; Modifies: al,flags .rest: in al,KBCTRL and al,INBUF_FULL jnz .rest ret ; ******************************************************************** wait_data: ; Waits for some data in the data buffer ; Modifies: al,flags .rest: in al,KBCTRL and al,OUTBUF_FULL jz .rest ret ; ******************************************************************** reboot: ; Reboots computer. Never returns. mov si,rebootmsg ; Be polite, and say we're rebooting... call message call getkey ; ...and even wait for a key :) db 0xEA ; Machine language to jump to FFFF:0000 (reboot) dw 0x0000 ; And this is the target address: offset... dw 0xFFFF ; ...and segment (remember endianess!) ; no ret required; we're rebooting! (Hey, I just saved a byte :) ; ******************************************************************** keyb_int_routine: ; The interrupt routine for the keyboard. Note that the implementation is ; quick and dirty (does not save register content, no need since it's alone) ; and therefore is *not* meant to be used "as is" in practice. in al, KBDATA mov si,scancodebyte call bytetostr mov si,scancodemsg call message mov al,0x20 out 0x20,al ; Tells the PIC we're done so that next hw interrupt can be processed iret ; ******************************************* ; The actual code of our boot loading process ; ******************************************* start: ; Adjust segment registers mov ax,LOADSEG ; BIOS loads bootsector at segment LOADSEG. We set ds accordingly mov ds,ax ; so we don't have to add LOADSEG<<4 to all our data addresses ; Quickly save what drive we booted from mov [bootdrv], dl ; Set up a stack mov ax,STACKSEG mov ss,ax mov sp,0xFFFF ; Let's use the whole segment. Why not? We can :) ; Display our startup message mov si,bootmsg call message ; Sends a command (keyboard self-test) and displays the result call wait_cmd ; it's a good idea to wait for an empty command buffer before even trying... mov al, CMDTEST out KBCTRL, al call wait_cmd ; waits for the command to be accepted ; Reads the result into al call wait_data ; waits for the result to become available in al,KBDATA ; Prints the outcome (reboots if things went wrong) cmp al,TESTOK jz .test_passed mov si,komsg call message jmp reboot .test_passed: mov si,okmsg call message ; Redirection of int 9 (IRQ 1: keyboard) mov ax,0x0000 ; Sets segment 0 mov ds,ax mov bx,keyb_int_routine cli ; Disables interrupts mov [0x0009 * 0x0004],bx ; First sets the offset... mov bx,0xLOADSEG mov [0x0009 * 0x0004 + 0x0002],bx ; ...then the segment of the routine (this code segment!) sti ; Puts interrupts back on mov ax,LOADSEG ; Restores ds mov ds,ax ;NB: in real mode the interrupt vector table starts at linear address 0 ;and contains 4-byte entries. So the vector for int 9 has 9 * 4 as offset. ;Offset is the *low* word of the vector - remember endianess of Intel CPUs jmp $ ; hangs - now control passes to interrupt routine ; ******************************************* ; Epilogue ; ******************************************* ; pads with zero to sector size - 2 times 510-($-$$) db 0 dw 0xAA55 ; marks as boot sector ; pads with zeros to floppy size - use it if necessary ; (it isn't with Bochs and VMware) ;times 1474560-($-$$) db 0