flat assembler
Message board for the users of flat assembler.

Index > OS Construction > USB HID 6 key Keyboard Example

Thread Post new topic Reply to topic

Joined: 22 Mar 2011
Posts: 272
Location: California
BAiC 16 Sep 2019, 17:39
the algorithm for dealing with USB HID Keyboards is quite complicated so before developing my own I searched the web and couldn't find anything. so... I'm volunteering my own code to help anyone else.

        ;Get the Report here.
        ;the 8byte Report will be in r08
        ;the basic format is:
        ;Byte 0: Modifier Keys
        ;Byte 1: Reserved bits (more modifier keys in my test keyboard)
        ;Byte 2..7: Keyboard Array. each byte is a Keyboard Usage ID byte.
        ;the State Machine is at rsp+.state
        ;the format for the State is:
        ;Byte 0: the number of Keys in the state.
        ;Byte 1..5: the same keys that are provided in the original Report
        ; this code doesn't currently consume the Modifier Keys.
        ;    Section 1
        ;   Check whether the New Data is 
        ; empty. if it is then all keys in
        ; the Old Array were released (Key 
        ; Up event needs to occur)
        ;   Note: this condition can occur
        ; during the inner loop later.
        mov r14,[rsp+.state]
        mzx r13, r14b
        mzx r15, r8w; move the bit field into r15. it's not used.
        xor r09, r09; the New Array will be stored in r09 in the same format as the Old Array.
        shr r08, 16 ; grab the key array.
        jz .empty.NewArray;if the new data is empty then empty the old data set
            ;look for key overflow. ignore the Report until it is resolved.
            mov rcx, 0x010101010101
            cmp r08, rcx
            je  inf.loop
            ;convert the Report Data to the same State Machine data structure as the Old Array. the Old Array is in r14.
            bsr r09, r08;find the top non-zero value.
            shr r09, 3  ;convert it to a byte-unit rather than a bit unit.
            shl r08, 8  ;open up 8 bits in order to store then length byte (the next instruction).
            lea r09,[r08+ r09+ 1];merge the length field into the first byte that was opened up. Refer to Logarithm Mathematics for why the "+1" exists.
            ;determine whether the Old Array was empty. the New Array isn't empty (the jump condition tested that).
            tst r13, r13
            jz .start.EmptyOldArray
            ;don't bother looking if the old state is identical to the new. this is a quick test that is only true when the user is holding down the same key(s). my test keyboard (Microsoft Natural Ergonomic Keyborad 4000) produces this behavior as it updates once every second (roughly) regardless of change.
            cmp r09, r14
            je  inf.loop
                ;    Section 2
                ;   This is a 2-dimensional loop.
                ;   the outer loop enumerates the 
                ; New Array without modifying it
                ;         r09 holds the data in a format
                ;         that is ready to be written to 
                ;         state once everything is finished.
                ;         r08 holds a COPY that is modified
                ;         over the course of the outer loop.
                ;   the inner loop searches the 
                ; Old Array for the Character code
                ; from the Outer Loop.
                ;         if it doesn't find the character
                ;         code then it invokes the "Key
                ;         Down" event handler.
                ;         if it does    find the character
                ;         code then it removes the character
                ;         code from the Old Array without
                ;         executing any handler.
                ;  Note: if the inner loop removals cause
                ; the Old Array to become empty then it 
                ; terminates both loops by jumping to a
                ; program that triggers the "Key Down"
                ; event for all remaining keys in the 
                ; New Array.
                ;both states are non-empty so perform the actual enumeration procedure.
                shr r08, 8;restore the New Array. it was modified a few lines up to integrate the length byte before storing in r09.
                or  rsi,-1
                ;this is a 2-dimensional loop. for every element in the new array perform a search of the old array.
                tst r8b, r8b
                jz .end.inner.loop
                    ;    Section 3
                    ;   Start the inner loop for the 
                    ; current outer loop iteration.
                    ;   initialize the starting pointer
                    ; as well as the loop counter.
                    mzx  rax, r8b
                    lea  rbp,[rsp+.state+1]
                    mov  rcx, r13
                    cmp [rbp], al
                    jne .next.iteration; if it's different then continue the inner loop.
                        ;    Section 4
                        ;   This section moves the last 
                        ; character code in the Old Array
                        ; into the current character code
                        ; position. this causes the Array
                        ; to be reduced by one.
                        ;   this code assumes that Keys
                        ; are unique!
                        ;   since the key was found we
                        ; need to avoid executing a 
                        ; "Key Down" event.
                        ;   Also: we need to check whether
                        ; the Old Array became empty as a
                        ; result of removing the current.
                        ; if it did then skip to a "flush"
                        ; program that executes "Key Down"
                        ; for all of the New Array keys
                        ; that we haven't enumerated yet.
                        mzx  eax, byte [rsp+ r13+.state];grab the one at the end and store it in the current position. reduce the length by one.
                        mov [rbp], al
                        add  r13, rsi
                        jz  .start.EmptyOldArray
                        jnz .end.inner.loop
                    sub  rbp, rsi
                    add  rcx, rsi
                    jnz .continue.inner; continue for each key in the Old Array.
                    cal .key.down;since the inner loop didn't find the current key within the Old Array you need to execute a "Key Down" event for it.
                shr  r08, 8
                jnz .continue.outer
            mov r14,[rsp+.state];re-read the old array. it must have been updated by the prior code.
        tst r13, r13
        jz  inf.loop;if the old state is empty then jump back to the end.
        ;the new state is now the old state. enumerate the previous old state and "empty" it: execute all "Key Up" events.
        shr  r14, 8
        mzx  rax, r14b
        tst  eax, eax
        jz   @f
        cal .key.up
        add  r13,-1
        jnz .continue.dump
    jmp inf.loop
    ;    Function Type Section
    ;   This section executes the "Key
    ; Down" event for all remaining 
    ; keys in the New Array.
    ;   it also stores the unmodified
    ; New Array to state since we're
    ; finished with the Old Array at
    ; this point in code.
    ;this is a terminal branch. it's only executed if the primary code removed all keys from the Old Array (or the Old Array was empty).
    rali ., line
    mov[rsp+.state],r09;store the new keys as the old.
            shr r08, 8
            jz  inf.loop; make sure there are keys left over.
            ;prevent the key down handler from processing a zero.
            mzx eax, r8b
            tst eax, eax
        jz  @b
        lea rcx,[ @b ]
        psh rcx
        jmp .key.down
    rali ., line
        dbg "Key Down event. Key: " dbg.hex rax, .init
    rali ., line
        dbg "Key Up   event. Key: " dbg.hex rax, .init

The USB driver and HID parser have been excluded of course. The code is heavily documented so I'm not going to repeat it here.

my USB software doesn't implement the non-root-hub device so the keyboard must be directly connected to the root port of a controller. I implement a driver for each of the 4 controllers (UHCI/OHCI/EHCI/XHCI) so that's not an issue.

If you're testing the Floppy image in vmWare then you'll need a USB Keyboard and xHCI enabled. vmWare only implements a PS2 keyboard. the same is true for VirtualBox. vmWare only implements one UHCI controller regardless of whether you enable EHCI: one of the UHCI ports is an Emulated USB Mouse (which is implemented). the other port is a USB Hub. vmWare doesn't implement OHCI. the only controller that is usable in vmware at the moment is XHCI.

the zip file includes a screen grab from vmWare after pressing a couple keys.

VESA is required. I moved from the BIOS Text Mode to a custom Text Mode using the VESA Frame Buffer. it's terribly slow in Bochs (tested in 2.6.9) but the OS doesn't do enough to make it an issue at the moment.

edit: I use FASM to rename some instructions for the purpose of formatting in a way that's easier to read. Sorry forgot to mention this.

tst = test
r08 = r8
r09 = r9
mzx = movzx

Filename: keyboardReportCode.zip
Filesize: 185.66 KB
Downloaded: 764 Time(s)

byte me.
Post 16 Sep 2019, 17:39
View user's profile Send private message Visit poster's website Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  

< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum

Copyright © 1999-2023, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.