; standard driver stuff
format MS COFF

DEBUG = 1

; this is for DEBUGF macro from 'fdo.inc'
__DEBUG__ = 1
__DEBUG_LEVEL__ = 1

include 'proc32.inc'
include 'imports.inc'
include 'fdo.inc'

public START
public version

; USB constants
DEVICE_DESCR_TYPE = 1
CONFIG_DESCR_TYPE = 2
STRING_DESCR_TYPE = 3
INTERFACE_DESCR_TYPE = 4
ENDPOINT_DESCR_TYPE = 5
DEVICE_QUALIFIER_DESCR_TYPE = 6

CONTROL_PIPE = 0
ISOCHRONOUS_PIPE = 1
BULK_PIPE = 2
INTERRUPT_PIPE = 3

; USB structures
virtual at 0
config_descr:
.bLength                db      ?
.bDescriptorType        db      ?
.wTotalLength           dw      ?
.bNumInterfaces         db      ?
.bConfigurationValue    db      ?
.iConfiguration         db      ?
.bmAttributes           db      ?
.bMaxPower              db      ?
.sizeof:
end virtual

virtual at 0
interface_descr:
.bLength                db      ?
.bDescriptorType        db      ?
.bInterfaceNumber       db      ?
.bAlternateSetting      db      ?
.bNumEndpoints          db      ?
.bInterfaceClass        db      ?
.bInterfaceSubClass     db      ?
.bInterfaceProtocol     db      ?
.iInterface             db      ?
.sizeof:
end virtual

virtual at 0
endpoint_descr:
.bLength                db      ?
.bDescriptorType        db      ?
.bEndpointAddress       db      ?
.bmAttributes           db      ?
.wMaxPacketSize         dw      ?
.bInterval              db      ?
.sizeof:
end virtual

; Driver data for all devices
virtual at 0
device_data:
.type           dd      ?       ; 1 = keyboard, 2 = mouse
.intpipe        dd      ?       ; interrupt pipe handle
.packetsize     dd      ?
.packet         rb      8       ; packet with data from device
.control        rb      8       ; control packet to device
.sizeof:
end virtual

; Driver data for mouse
virtual at device_data.sizeof
mouse_data:
; no additional data
.sizeof:
end virtual

; Driver data for keyboard
virtual at device_data.sizeof
keyboard_data:
.handle         dd      ?       ; keyboard handle from RegKeyboard
.configpipe     dd      ?       ; config pipe handle
.prevpacket     rb      8       ; previous packet with data from device
.timer          dd      ?       ; auto-repeat timer handle
.repeatkey      db      ?       ; auto-repeat key code
.ledstate       db      ?       ; state of LEDs
                align 4
.sizeof:
end virtual

section '.flat' code readable align 16
; The start procedure.
START:
; 1. Test whether the procedure is called with the argument DRV_ENTRY.
; If not, return 0.
        xor     eax, eax        ; initialize return value
        cmp     dword [esp+4], 1        ; compare the argument
        jnz     .nothing
; 2. Register self as a USB driver.
; The name is my_driver = 'usbhid'; IOCTL interface is not supported;
; usb_functions is an offset of a structure with callback functions.
        stdcall RegUSBDriver, my_driver, eax, usb_functions
; 3. Return the returned value of RegUSBDriver.
.nothing:
        ret     4

; This procedure is called when new HID device is detected.
; It initializes the device.
AddDevice:
; Arguments are addressed through esp. In this point of the function,
; [esp+4] = a handle of the config pipe, [esp+8] points to config_descr
; structure, [esp+12] points to interface_descr structure.
; 1. Check device type. Currently only mice and keyboards with
; boot protocol are supported.
; 1a. Get the subclass and the protocol. Since bInterfaceSubClass and
; bInterfaceProtocol are subsequent in interface_descr, just one
; memory reference is used for both.
        mov     edx, [esp+12]
        push    ebx     ; save used register to be stdcall
        mov     cx, word [edx+interface_descr.bInterfaceSubClass]
; 1b. For boot protocol, subclass must be 1 and protocol must be either 1 for
; a keyboard or 2 for a mouse. Check.
        cmp     cx, 0x0101
        jz      .keyboard
        cmp     cx, 0x0201
        jz      .mouse
; 1c. If the device is neither a keyboard nor a mouse, print a message and
; go to 6c.
        DEBUGF 1,'K : unknown HID device\n'
        jmp     .nothing
; 1d. If the device is a keyboard or a mouse, print a message and continue
; configuring.
.keyboard:
        DEBUGF 1,'K : USB keyboard detected\n'
        push    keyboard_data.sizeof
        jmp     .common
.mouse:
        DEBUGF 1,'K : USB mouse detected\n'
        push    mouse_data.sizeof
.common:
; 2. Allocate memory for device data.
        pop     eax     ; get size of device data
; 2a. Call the kernel, saving and restoring register edx.
        push    edx
        call    Kmalloc
        pop     edx
; 2b. Check result. If failed, say a message and go to 6c.
        test    eax, eax
        jnz     @f
        DEBUGF 1,'K : no memory\n'
        jmp     .nothing
@@:
        xchg    eax, ebx
; HID devices use one IN interrupt endpoint for polling the device
; and an optional OUT interrupt endpoint. We do not use the later,
; but must locate the first. Look for the IN interrupt endpoint.
; 3. Get the upper bound of all descriptors' data.
        mov     eax, [esp+8+4]  ; configuration descriptor
        movzx   ecx, [eax+config_descr.wTotalLength]
        add     eax, ecx
; 4. Loop over all descriptors until
; either end-of-data reached - this is fail
; or interface descriptor found - this is fail, all further data
;    correspond to that interface
; or endpoint descriptor found.
; 4a. Loop start: eax points to the interface descriptor.
.lookep:
; 4b. Get next descriptor.
        movzx   ecx, byte [edx] ; the first byte of all descriptors is length
        add     edx, ecx
; 4c. Check that at least two bytes are readable. The opposite is an error.
        inc     edx
        cmp     edx, eax
        jae     .errorep
        dec     edx
; 4d. Check that this descriptor is not interface descriptor. The opposite is
; an error.
        cmp     byte [edx+endpoint_descr.bDescriptorType], INTERFACE_DESCR_TYPE
        jz      .errorep
; 4e. Test whether this descriptor is an endpoint descriptor. If not, continue
; the loop.
        cmp     byte [edx+endpoint_descr.bDescriptorType], ENDPOINT_DESCR_TYPE
        jnz     .lookep
; 5. Check that the descriptor contains all required data and all data are
; readable. If so, proceed to 7.
        cmp     byte [edx+endpoint_descr.bLength], endpoint_descr.sizeof
        jb      .errorep
        sub     eax, endpoint_descr.sizeof
        cmp     edx, eax
        jbe     @f
; 6. An error occured during processing endpoint descriptor.
.errorep:
; 6a. Print a message.
        DEBUGF 1,'K : error: invalid endpoint descriptor\n'
; 6b. Free memory allocated for device data.
.free:
        xchg    eax, ebx
        call    Kfree
.nothing:
; 6c. Return an error.
        xor     eax, eax
        pop     ebx
        ret     12
@@:
; 7. Check that the endpoint is IN interrupt endpoint. If not, go to 6.
        test    [edx+endpoint_descr.bEndpointAddress], 80h
        jz      .errorep
        mov     cl, [edx+endpoint_descr.bmAttributes]
        and     cl, 3
        cmp     cl, INTERRUPT_PIPE
        jnz     .errorep
; 8. Open pipe for the endpoint.
; 8a. Load parameters from the descriptor.
        movzx   ecx, [edx+endpoint_descr.bEndpointAddress]
        movzx   eax, [edx+endpoint_descr.bInterval]
        movzx   edx, [edx+endpoint_descr.wMaxPacketSize]
; 8b. Call the kernel, saving and restoring edx.
        push    edx
        stdcall USBOpenPipe, [esp+4+24], ecx, edx, INTERRUPT_PIPE, eax
        pop     edx
; 8c. Check result. If failed, go to 6b.
        test    eax, eax
        jz      .free
; We use 12 bytes for device type, interrupt pipe and interrupt packet size,
; 8 bytes for a packet and 8 bytes for previous packet, used by a keyboard.
; 9. Initialize device data.
        mov     [ebx+device_data.intpipe], eax
        push    8
        pop     ecx
        cmp     edx, ecx
        jb      @f
        mov     edx, ecx
@@:
        xor     eax, eax
        mov     [ebx+device_data.packetsize], edx
        mov     dword [ebx+device_data.packet], eax
        mov     dword [ebx+device_data.packet+4], eax
        mov     edx, [esp+12+4] ; interface descriptor
        movzx   ecx, [edx+interface_descr.bInterfaceProtocol]
        mov     [ebx+device_data.type], ecx
        cmp     ecx, 1
        jnz     @f
        mov     [ebx+keyboard_data.handle], eax
        mov     [ebx+keyboard_data.timer], eax
        mov     [ebx+keyboard_data.repeatkey], al
        mov     dword [ebx+keyboard_data.prevpacket], eax
        mov     dword [ebx+keyboard_data.prevpacket+4], eax
        mov     eax, [esp+4+4]
        mov     [ebx+keyboard_data.configpipe], eax
@@:
; 10. Send the control packet SET_PROTOCOL(Boot Protocol) to the interface.
        lea     eax, [ebx+device_data.control]
        mov     dword [eax], 21h + (0Bh shl 8) + (0 shl 16)     ; class request to interface + SET_PROTOCOL + Boot protocol
        and     dword [eax+4], 0
        mov     dl, [edx+interface_descr.bInterfaceNumber]
        mov     [eax+4], dl
; Callback function is mouse_configured for mice and keyboard_configured1 for keyboards.
        mov     edx, keyboard_configured1
        cmp     ecx, 1
        jz      @f
        mov     edx, mouse_configured
@@:
        stdcall USBControlTransferAsync, [esp+4+28], eax, 0, 0, edx, ebx, 0
; 11. Return with pointer to device data as returned value.
        xchg    eax, ebx
        pop     ebx
        ret     12

; This function is called when SET_PROTOCOL command for keyboard is done,
; either successful or unsuccessful.
keyboard_configured1:
        xor     edx, edx
; 1. Check the status of the transfer.
; If the transfer was failed, go to the common error handler.
        cmp     dword [esp+8], edx      ; status is zero?
        jnz     keyboard_data_ready.error
; 2. Send the control packet SET_IDLE(infinity). HID auto-repeat is not useful.
        mov     eax, [esp+20]
        push    edx     ; flags for USBControlTransferAsync
        push    eax     ; userdata for USBControlTransferAsync
        add     eax, device_data.control
        mov     dword [eax], 21h + (0Ah shl 8) + (0 shl 24)     ; class request to interface + SET_IDLE + no autorepeat
        stdcall USBControlTransferAsync, dword [eax+keyboard_data.configpipe-device_data.control], \
                eax, edx, edx, keyboard_configured2; , <userdata>, <flags>
; 3. Return.
        ret     20

; This function is called when SET_IDLE command for keyboard is done,
; either successful or unsuccessful.
keyboard_configured2:
; Check the status of the transfer and go to the corresponding label
; in the main handler.
        cmp     dword [esp+8], 0
        jnz     keyboard_data_ready.error
        mov     edx, [esp+20]
        push    edx
        stdcall RegKeyboard, usbkbd_functions, edx
        pop     edx
        mov     [edx+keyboard_data.handle], eax
        jmp     keyboard_data_ready.next

; This function is called when another interrupt packet arrives,
; processed either successfully or unsuccessfully.
; It should parse the packet and initiate another transfer with
; the same callback function.
keyboard_data_ready:
; 1. Check the status of the transfer.
        mov     eax, [esp+8]
        test    eax, eax
        jnz     .error
; Parse the packet, comparing with the previous packet.
; For boot protocol, USB keyboard packet consists of the first byte
; with status keys that are currently pressed. The second byte should
; be ignored, and other 5 bytes denote keys that are currently pressed.
        push    esi ebx         ; save used registers to be stdcall
; 2. Process control keys.
; 2a. Initialize before loop for control keys. edx = mask for control bits
; that were changed.
        mov     ebx, [esp+20+8]
        movzx   edx, byte [ebx+device_data.packet]      ; get state of control keys
        xor     dl, byte [ebx+keyboard_data.prevpacket] ; compare with previous state
; 2b. If state of control keys has not changed, advance to 3.
        jz      .nocontrol
; 2c. Otherwise, loop over control keys; esi = bit number.
        xor     esi, esi
.controlloop:
; 2d. Skip bits that have not changed.
        bt      edx, esi
        jnc     .controlnext
        push    edx     ; save register which is possibly modified by API
; The state of the current control key has changed.
; 2e. For extended control keys, send the prefix 0xE0.
        mov     al, [control_keys+esi]
        test    al, al
        jns     @f
        push    eax
        mov     ecx, 0xE0
        call    SetKeyboardData
        pop     eax
        and     al, 0x7F
@@:
; 2f. If the current state of the control key is "pressed", send normal
; scancode. Otherwise, the key is released, so set the high bit in scancode.
        movzx   ecx, al
        bt      dword [ebx+device_data.packet], esi
        jc      @f
        or      cl, 0x80
@@:
        call    SetKeyboardData
        pop     edx     ; restore register which was possibly modified by API
.controlnext:
; 2g. We have 8 control keys.
        inc     esi
        cmp     esi, 8
        jb      .controlloop
.nocontrol:
; 3. Initialize before loop for normal keys. esi = index.
        push    2
        pop     esi
.normalloop:
; 4. Process one key which was pressed in the previous packet.
; 4a. Get the next pressed key from the previous packet.
        movzx   eax, byte [ebx+esi+keyboard_data.prevpacket]
; 4b. Ignore special codes.
        cmp     al, 3
        jbe     .normalnext1
; 4c. Ignore keys that are still pressed in the current packet.
        lea     ecx, [ebx+device_data.packet]
        call    haskey
        jz      .normalnext1
; 4d. Say warning about keys with strange codes.
        cmp     eax, normal_keys_number
        jae     .badkey1
        movzx   ecx, [normal_keys+eax]
        jecxz   .badkey1
; 4e. For extended keys, send the prefix 0xE0.
        push    ecx     ; save keycode
        test    cl, cl
        jns     @f
        push    ecx
        mov     ecx, 0xE0
        call    SetKeyboardData
        pop     ecx
@@:
; 4f. Send the release event.
        or      cl, 0x80
        call    SetKeyboardData
; 4g. If this key is autorepeating, stop the timer.
        pop     ecx     ; restore keycode
        cmp     cl, [ebx+keyboard_data.repeatkey]
        jnz     .normalnext1
        mov     eax, [ebx+keyboard_data.timer]
        test    eax, eax
        jz      .normalnext1
        stdcall CancelTimerHS, eax
        and     [ebx+keyboard_data.timer], 0
        jmp     .normalnext1
.badkey1:
        DEBUGF 1,'K : unknown keycode: %x\n',al
.normalnext1:
; 5. Process one key which is pressed in the current packet.
; 5a. Get the next pressed key from the current packet.
        movzx   eax, byte [ebx+esi+device_data.packet]
; 5b. Ignore special codes.
        cmp     al, 3
        jbe     .normalnext2
; 5c. Ignore keys that were already pressed in the previous packet.
        lea     ecx, [ebx+keyboard_data.prevpacket]
        call    haskey
        jz      .normalnext2
; 5d. Say warning about keys with strange codes.
        cmp     eax, normal_keys_number
        jae     .badkey2
        movzx   ecx, [normal_keys+eax]
        jecxz   .badkey2
; 5e. For extended keys, send the prefix 0xE0.
        push    ecx     ; save keycode
        test    cl, cl
        jns     @f
        push    ecx
        mov     ecx, 0xE0
        call    SetKeyboardData
        pop     ecx
@@:
; 5f. Send the press event.
        and     cl, not 0x80
        call    SetKeyboardData
; 5g. Stop the current auto-repeat timer, if present.
        mov     eax, [ebx+keyboard_data.timer]
        test    eax, eax
        jz      @f
        stdcall CancelTimerHS, eax
@@:
; 5h. Start the auto-repeat timer.
        pop     ecx     ; restore keycode
        mov     [ebx+keyboard_data.repeatkey], cl
        stdcall TimerHS, 25, 5, autorepeat_timer, ebx
        mov     [ebx+keyboard_data.timer], eax
        jmp     .normalnext2
.badkey2:
        DEBUGF 1,'K : unknown keycode: %x\n',al
.normalnext2:
; 6. Advance to next key.
        inc     esi
        cmp     esi, 8
        jb      .normalloop
; 7. Save the packet data for future reference.
        mov     eax, dword [ebx+device_data.packet]
        mov     dword [ebx+keyboard_data.prevpacket], eax
        mov     eax, dword [ebx+device_data.packet+4]
        mov     dword [ebx+keyboard_data.prevpacket+4], eax
        pop     ebx esi ; restore registers to be stdcall
.next:
; 8. Initiate transfer on the interrupt pipe.
        mov     eax, [esp+20]
        push    1       ; flags for USBNormalTransferAsync
        push    eax     ; userdata for USBNormalTransferAsync
        add     eax, device_data.packet
        stdcall USBNormalTransferAsync, dword [eax+device_data.intpipe-device_data.packet], \
                eax, dword [eax+device_data.packetsize-device_data.packet], \
                keyboard_data_ready;, <userdata>, <flags>
; 9. Return.
.nothing:
        ret     20
.error:
; An error has occured.
; 10. If an error is caused by the disconnect, do nothing, it is handled
; in DeviceDisconnected. Otherwise, say a message.
        cmp     eax, 16
        jz      @f
        push    esi
        mov     esi, errormsgkbd
        call    SysMsgBoardStr
        pop     esi
@@:
        ret     20

; Auxiliary procedure for keyboard_data_ready.
haskey:
        push    2
        pop     edx
@@:
        cmp     byte [ecx+edx], al
        jz      @f
        inc     edx
        cmp     edx, 7
        jbe     @b
@@:
        ret

; Timer function for auto-repeat.
autorepeat_timer:
        mov     eax, [esp+4]
        movzx   ecx, [eax+keyboard_data.repeatkey]
        test    cl, cl
        jns     @f
        push    ecx
        mov     ecx, 0xE0
        call    SetKeyboardData
        pop     ecx
        and     cl, not 0x80
@@:
        call    SetKeyboardData
        ret     4

; This function is called to update LED state on the keyboard.
SetKeyboardLights:
        mov     eax, [esp+4]
        add     eax, device_data.control
        mov     dword [eax], 21h + (9 shl 8) + (2 shl 24)
                ; class request to interface + SET_REPORT + Output zero report
        mov     byte [eax+6], 1
        mov     edx, [esp+8]
        shr     dl, 1
        jnc     @f
        or      dl, 4
@@:
        lea     ecx, [eax+keyboard_data.ledstate-device_data.control]
        mov     [ecx], dl
        stdcall USBControlTransferAsync, dword [eax+keyboard_data.configpipe-device_data.control], \
                eax, ecx, 1, keyboard_data_ready.nothing, 0, 0
        ret     8

; This function is called when it is safe to free keyboard data.
CloseKeyboard:
        mov     eax, [esp+4]
        push    ebx
        call    Kfree
        pop     ebx
        ret     4

; This function is called when SET_PROTOCOL command for mouse is done,
; either successful or unsuccessful.
mouse_configured:
; Check the status of the transfer and go to the corresponding label
; in the main handler.
        cmp     dword [esp+8], 0
        jnz     mouse_data_ready.error
        mov     eax, [esp+20]
        add     eax, device_data.packet
        jmp     mouse_data_ready.next

; This function is called when another interrupt packet arrives,
; processed either successfully or unsuccessfully.
; It should parse the packet and initiate another transfer with
; the same callback function.
mouse_data_ready:
; 1. Check the status of the transfer.
        mov     eax, [esp+8]
        test    eax, eax
        jnz     .error
        mov     edx, [esp+16]
; 2. Parse the packet.
; For boot protocol, USB mouse packet consists of at least 3 bytes.
; The first byte is state of mouse buttons, the next two bytes are
; x and y movements.
; Normal mice do not distinguish between boot protocol and report protocol;
; in this case, scroll data are also present. Advanced mice, however,
; support two different protocols, boot protocol is used for compatibility
; and does not contain extended buttons or scroll data.
        mov     eax, [esp+12]   ; buffer
        push    eax
        xor     ecx, ecx
        cmp     edx, 4
        jbe     @f
        movsx   ecx, byte [eax+4]
@@:
        push    ecx
        xor     ecx, ecx
        cmp     edx, 3
        jbe     @f
        movsx   ecx, byte [eax+3]
        neg     ecx
@@:
        push    ecx
        xor     ecx, ecx
        cmp     edx, 2
        jbe     @f
        movsx   ecx, byte [eax+2]
        neg     ecx
@@:
        push    ecx
        movsx   ecx, byte [eax+1]
        push    ecx
        movzx   ecx, byte [eax]
        push    ecx
        call    SetMouseData
        pop     eax
.next:
; 3. Initiate transfer on the interrupt pipe.
        stdcall USBNormalTransferAsync, dword [eax+device_data.intpipe-device_data.packet], \
                eax, dword [eax+device_data.packetsize-device_data.packet], mouse_data_ready, eax, 1
; 4. Return.
        ret     20
.error:
; An error has occured.
; 5. If an error is caused by the disconnect, do nothing, it is handled
; in DeviceDisconnected. Otherwise, say a message.
        cmp     eax, 16
        jz      @f
        push    esi
        mov     esi, errormsgmouse
        call    SysMsgBoardStr
        pop     esi
@@:
        ret     20

; This function is called when the device is disconnected.
DeviceDisconnected:
        push    ebx     ; save used register to be stdcall
; 1. Say a message. Use different messages for keyboards and mice.
        mov     ebx, [esp+4+4]
        push    esi
        mov     esi, disconnectmsgk
        cmp     byte [ebx+device_data.type], 1
        jz      @f
        mov     esi, disconnectmsgm
@@:
        stdcall SysMsgBoardStr
        pop     esi
; 2. If device is keyboard, then we must unregister it as a keyboard and
; possibly stop the auto-repeat timer.
        cmp     byte [ebx+device_data.type], 1
        jnz     .nokbd
        mov     eax, [ebx+keyboard_data.timer]
        test    eax, eax
        jz      @f
        stdcall CancelTimerHS, eax
@@:
        mov     ecx, [ebx+keyboard_data.handle]
        jecxz   .nokbd
        stdcall DelKeyboard, ecx
; If keyboard is registered, then we should free data in CloseKeyboard, not here.
        jmp     .nothing
.nokbd:
; 3. Free the device data.
        xchg    eax, ebx
        call    Kfree
; 4. Return.
.nothing:
        pop     ebx     ; restore used register to be stdcall
        ret     4       ; purge one dword argument to be stdcall

; strings
my_driver       db      'usbhid',0
errormsgmouse   db      'K : USB transfer error, disabling mouse',10,0
errormsgkbd     db      'K : USB transfer error, disabling keyboard',10,0
disconnectmsgm  db      'K : USB mouse disconnected',10,0
disconnectmsgk  db      'K : USB keyboard disconnected',10,0

; data for keyboard: correspondence between HID usage keys and PS/2 scancodes.
EX = 80h
label control_keys byte
        db      1Dh, 2Ah, 38h, 5Bh+EX, 1Dh+EX, 36h, 38h+EX, 5Ch+EX
label normal_keys byte
        db      00h, 00h, 00h, 00h, 1Eh, 30h, 2Eh, 20h, 12h, 21h, 22h, 23h, 17h, 24h, 25h, 26h  ; 0x
        db      32h, 31h, 18h, 19h, 10h, 13h, 1Fh, 14h, 16h, 2Fh, 11h, 2Dh, 15h, 2Ch, 02h, 03h  ; 1x
        db      04h, 05h, 06h, 07h, 08h, 09h, 0Ah, 0Bh, 1Ch, 01h, 0Eh, 0Fh, 39h, 0Ch, 0Dh, 1Ah  ; 2x
        db      1Bh, 2Bh, 2Bh, 27h, 28h, 29h, 33h, 34h, 35h, 3Ah, 3Bh, 3Ch, 3Dh, 3Eh, 3Fh, 40h  ; 3x
        db      41h, 42h, 43h, 44h, 57h, 58h,37h+EX,46h,0,52h+EX,47h+EX,49h+EX,53h+EX,4Fh+EX,51h+EX,4Dh+EX      ; 4x
        db      4Bh+EX,50h+EX,48h+EX,45h,35h+EX,37h,4Ah,4Eh,1Ch+EX,4Fh,50h,51h,4Bh,4Ch,4Dh,47h  ; 5x
        db      48h, 49h, 52h, 53h, 56h,5Dh+EX,5Eh+EX,59h,64h,65h,66h, 67h, 68h, 69h, 6Ah, 6Bh  ; 6x
        db      6Ch, 6Dh, 6Eh, 76h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h  ; 7x
        db      00h, 00h, 00h, 00h, 00h, 7Eh, 00h, 73h, 70h, 7Dh, 79h, 7Bh, 5Ch, 00h, 00h, 00h  ; 8x
        db      0F2h,0F1h,78h, 77h, 76h
normal_keys_number = $ - normal_keys

; Exported variable: kernel API version.
align 4
version dd      50005h
; Structure with callback functions.
usb_functions:
        dd      12
        dd      AddDevice
        dd      DeviceDisconnected

; Structure with callback functions for keyboards.
usbkbd_functions:
        dd      12
        dd      CloseKeyboard
        dd      SetKeyboardLights

; for DEBUGF macro
include_debug_strings

; for uninitialized data
section '.data' data readable writable align 16