;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
;  Hugi 28: Hugi USB
;
;  The example.asm for Hugi Compo #28
;
;  This example does and tries to explain the process of getting
;   the device descriptor of a USB UHCI device.
;  It first finds the UHCI controller on the PCI bus, enables the
;   PCI, and gets the Base address and the Interrupt number.
;
;  It then, sets up the UHCI controller, a stack frame, and gets
;   the device descriptor of an attached device.
;
;  More detail to this process is within the comments to each particular
;   task below.
;
;         Version: 1.01.00
;            Date: 11 June 2009
;  Assembled with: NBASM 00.26.22  http://www.frontiernet.net/~fys/nbasm.html
;          Author:  Ben Lunt (Sniper)
;
;  This code is intended for the Hugi Compo 28, and/or for those who
;   are learning about the USB and UHCI controller.  This code can not
;   be redistributed or copied for any other reason unless you have
;   direct consent from the author.
;


;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; we are a .com file
.model tiny

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; EQUates

; The PCI Bus IO ports
PCI_ADDR   equ 0CF8h
PCI_DATA   equ 0CFCh

COMMAND    equ  0
STATUS     equ  2
INTERRUPT  equ  4
FRAME_NUM  equ  6
FRAME_ADDY equ  8
SOF        equ 12
PORT0      equ 16
PORT1      equ 18

IS_QUEUE   equ (1<<1)

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; Structs

UHCI_Q     struct
           horz      dword;
           vert      dword;
UHCI_Q     ends

UHCI_TD    struct
           link_ptr  dword;
           stats     dword;
           info      dword;
           buffer    dword;
           resv     dup 16;
UHCI_TD    ends

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; code stars here

.code         ;
.386          ; we will use (and assume) we are a .386 or better
.stack 512    ; leave 512 bytes for the stack

           org 100h  ; .com files start at 100h

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; this is a NBASM directive to add the code
           ;  to free all memory past our code below
           .start

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; First thing would be to find the UHCI controller on the PCI Bus
           ; We will do this using the PCI alone.  We could use the PCI
           ; BIOS services, ax = 0B1xxh, but I prefer to use the PCI itself.
           ; Using ports 0xCF8 and 0xCFC, we can read/write to the PCI Bus.
           ; We first write to the ADDRess port using the following bits:
           ;  Bit(s) 1: 0 reserved (should be written as zeros)
           ;         7: 2 config register number (see #00878 in RBIL)
           ;        10: 8 function number
           ;        15:11 device number
           ;        23:16 bus number
           ;        30:24 reserved (should be written as zeros)
           ;        31    enable configuration space mapping
           ; We must read in a dword at a time, with bits 7:2 detailing
           ;  which dword offset to read.           ;
           ; We assume there are at least 2 bus's, 32 devices per bus,
           ;  and 8 functions per device.
           xor  bx,bx               ; bh = bus, bl = dev
           xor  cl,cl               ; cl = function

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; read in the ID word (first word in config space)
pci_main_loop:                        
           mov  dl,2                ; we want a 16-bit value returned
           xor  ch,ch               ; first offset we want is zero
           call pci_read            ; read the word
           cmp  ax,0FFFFh           ; if value is 0FFFFh, then no device
           je   short pci_no_device ;  at this location

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; if vendor_id is not 0FFFFh, then there is a device there
           ; read in the class
           mov  dl,1
           mov  ch,11
           call pci_read            ; read the word
           cmp  al,0Ch              ;
           jne  short pci_no_device

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; read in the sub_class
           mov  dl,1
           mov  ch,10
           call pci_read            ; read the word
           cmp  al,03h              ;
           jne  short pci_no_device

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; read in the proto
           mov  dl,1
           mov  ch,9
           call pci_read            ; read the word
           cmp  al,00h              ; [1] See note at eof
           jne  short pci_no_device

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; found UHCI device.
           mov  si,offset s_found_uhci_dev
           call prt_string
           jmp  short found_uhci

pci_no_device:
           inc  cl                  ; increment to next function
           cmp  cl,8                ; if it is 8, move to next device
           jb   short pci_main_loop ;
           xor  cl,cl               ; func = 0
           inc  bl                  ; increment to next device
           cmp  bl,32               ; if it is 32, move to next bus
           jb   short pci_main_loop ;
           xor  bl,bl               ; dev = 0
           inc  bh                  ; increment to next bus
           cmp  bh,2                ; if it is 2, then we didn't find the UHCI
           jb   short pci_main_loop ;

           mov  si,offset s_no_uhci_found
           call prt_string

           .exit

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; We found a UHCI device, so get the base address and the int number.
; Get and save the IO Base and Interrupt number
; Write 0005h to the PCI status register to allow access
; Also write 8F00h to the Legacy register in the config space
;
found_uhci:
           mov  dl,2                ; the IO base is 2 bytes in size
           mov  ch,20h              ; base4 is at offset 32
           call pci_read            ; read the word
           and  al,0FCh             ; clear out the last 2 bits
           mov  io_base,ax          ; save the io base

           mov  dl,1                ; the int num is 1 byte in size
           mov  ch,3Ch              ;  and is at offset 60
           call pci_read            ; read the byte
           mov  int_num,al          ; save the int number

           mov  eax,0005h           ; write 0005h to the access register
           mov  dl,2                ; is a word
           mov  ch,04h              ; at offset 04
           call pci_write           ; write it

           mov  eax,8F00h           ; write 8F00h to the legacy register
           mov  dl,2                ; is a word
           mov  ch,0C0h             ; at offset C0h
           call pci_write           ; write it

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; reset the controller by setting bit 2 (GRESET) in the command register
;  waiting at least 10ms, then reseting the bit.
           mov  dx,io_base
           add  dx,COMMAND
           mov  ax,(1<<2)           ; bit 2 (GRESET)
           out  dx,ax

           ; now wait for at least 10ms
           call delay55ms

           ; clear the register (setting it to default values)
           mov  dx,io_base
           add  dx,COMMAND
           xor  ax,ax
           out  dx,ax

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; we now need to set up a frame list.  Per the rules, we need to
;  allocate it using DOS
           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; since the frame address requires alignment of 4096,
           ;  we allocate (4096 * 2) bytes
           mov  ah,48h
           mov  bx,((4096*2)>>4)
           int  21h
           jnc  short @f

           mov  si,offset s_no_memory
           call prt_string
           .exit

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; save the address for the mfree call and
           ; convert to 32-bit and write to the controller
@@:        mov  frame_addr,ax
           and  eax,0FFFFh
           shl  eax,4
           add  eax,4095
           and  eax,(~4095)
           mov  dx,io_base
           add  dx,FRAME_ADDY
           out  dx,eax
           shr  eax,4
           mov  frame_addra,ax

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; mark each frame as 'T'erminated
           push es
           mov  ax,frame_addra
           mov  es,ax
           xor  di,di
           mov  eax,1
           mov  cx,1024
           rep
           stosd
           pop  es

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; Set the IOC interrupt bit enable
           ; *We must set all four bit to get Bochs to work*
           ; (Ben: Fix Bochs)
           mov  dx,io_base
           add  dx,INTERRUPT
           mov  ax,000Fh
           out  dx,ax

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; Start the UHCI controller.
           mov  dx,io_base
           add  dx,COMMAND
           mov  ax,01
           out  dx,ax

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; This is where the main loop starts.  We find a port with the ConnectChange
;  bit set, clear it, see if connection is made, and get the descriptor.

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; A few things to start with.
           mov word port,10h        ; Port = 10h

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
main_loop: ; set up the address in dx
           mov  dx,io_base
           add  dx,port

           ; test the connect status change bit
           in   ax,dx
           test ax,(1<<1)            ; connect status change bit
           jz   no_device_attached

           ; reset the connect status change bit
           mov  ax,(1<<1)
           out  dx,ax

           ; test the connect status bit
           in   ax,dx
           test ax,(1<<0)
           jz   no_device_attached

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; there is a connection, reset, and send the packet
           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; first reset the port
           mov  ax,(1<<9)
           out  dx,ax

           ; now wait for at least 50ms
           call delay55ms

           ; clear the reset
           in   ax,dx
           and  ax,(~(1<<9))
           out  dx,ax

           ; now wait for at least 50ms
           call delay55ms

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; enable the device
           in   ax,dx
           or   ax,(1<<2)
           out  dx,ax

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; we can not assume it is a LS device,
           ;  so get the LS bit in the PORTS[x] register.
           ; we set/clear the bit in ecx for or'ing
           xor  ecx,ecx
           in   ax,dx
           test ax,(1<<8)
           jz   short @f
           or   ecx,(1<<26)
@@:
           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; set up our frame TD's
           ; We already have them hard coded in our data section,
           ;  but need to fix up the addresses.
           xor  eax,eax             ; get the segment address
           mov  ax,cs               ; 
           shl  eax,4               ; convert to 32-bit address

           ; Setup_TD
           mov  edx,eax
           add  edx,offset TD0      ;
           mov  Setup_TD.link_ptr,edx
           or   Setup_TD.stats,ecx  ; set/or don't set LS bit
           mov  edx,offset Setup_Packet
           add  edx,eax
           mov  Setup_TD.buffer,edx

           ; TD0
           mov  edx,eax
           add  edx,offset TD1
           mov  TD0.link_ptr,edx
           or   TD0.stats,ecx  ; set/or don't set LS bit
           mov  edx,(Device_Descriptor + 0)
           add  edx,eax
           mov  TD0.buffer,edx

           ; TD1
           mov  edx,eax
           add  edx,offset TD2
           mov  TD1.link_ptr,edx
           or   TD1.stats,ecx  ; set/or don't set LS bit
           mov  edx,(Device_Descriptor + 8)
           add  edx,eax
           mov  TD1.buffer,edx

           ; TD2
           mov  edx,eax
           add  edx,offset Status_TD
           mov  TD2.link_ptr,edx
           or   TD2.stats,ecx  ; set/or don't set LS bit
           mov  edx,(Device_Descriptor + 16)
           add  edx,eax
           mov  TD2.buffer,edx

           ; Status_TD
           or   Status_TD.stats,ecx  ; set/or don't set LS bit

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; Set up the Queue Head
           ; The first dword is called the HORZontal pointer.  It points
           ;  to another Queue or maybe a TD.  The second dword is the
           ;  VERTical pointer.  It points to a set of TD's or maybe a Queue.
           ; The vert pointer is executed first. Once the Host Controller (HC)
           ;  executes the TD pointed to by the Vert pointer, that TD's Link
           ;  pointer (dword 0 in the TD) is copied to the Vert pointer in
           ;  the Queue.  If the Breadth (Vf) bit is set, it continues executing
           ;  the TD pointed to in the Vert pointer (just placed there).
           ; If the Breadth (Vf) bit is clear, the HC moves back up to the
           ;  Horz pointer and executes that pointer.
           ; When the controller reaches the last TD or Queue in this frame,
           ;  it moves to the next frame (FRAME_NUM register is incremented).
           ; When the controller has executed the other 1023 frames (1023ms)
           ;  it comes back to this one.  Since the Vf flag was cleared before
           ;  the next TD in the Vert Queue is executed.
           ; Well enough of that, continue on with the code.
           mov  edx,offset Setup_TD
           add  edx,eax
           mov  dword Queue.horz,1
           mov  Queue.vert,edx

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; point the first frame in the list to our queue.
           push es
           mov  es,frame_addra
           add  eax,offset Queue   ; eax still = 'base address'
           or   eax,IS_QUEUE       ; is a queue
           mov  es:[0],eax
           pop  es

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; wait for the interrupt to happen
           mov  dx,io_base
           add  dx,2
@@:        call delay55ms
           in   ax,dx
           test ax,1
           jz   short @b

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; print that the interrupt fired
           mov  si,offset s_fired
           call prt_string

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; now mark the frame pointer as terminated
           push es
           mov  es,frame_addra
           mov  eax,00000001
           mov  es:[0],eax
           pop  es

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; now print it out.
           mov  si,offset crlf
           call prt_string

           mov  si,offset Device_Descriptor
           mov  cx,7
@@:        lodsb
           call prt_byte
           mov  ah,02
           mov  dl,' '
           int  21h
           loop @b

           lodsb
           call prt_byte
           mov  ah,02
           mov  dl,'-'
           int  21h

           mov  cx,8
@@:        lodsb
           call prt_byte
           mov  ah,02
           mov  dl,' '
           int  21h
           loop @b

           push si
           mov  si,offset crlf
           call prt_string
           pop  si

           mov  cx,2
@@:        lodsb
           call prt_byte
           mov  ah,02
           mov  dl,' '
           int  21h
           loop @b

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; we are done with this one, so loop until
           ;  we find a connect status change bit set again.

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; move to next port and loop
no_device_attached:
           add  word port,02h
           cmp  word port,12h
           jbe  main_loop

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; start at the first port again.
           mov  word port,10h

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; if we are now past the forth port,
           ;  check for a key press. If key press,
           ;  then end, else reset to the first port,
           ;  and loop again.
           mov  ah,01h
           int  16h
           jz   main_loop

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; user pressed a key, so do the clean up and exit

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; stop the controller
           mov  dx,io_base
           add  dx,COMMAND
           xor  ax,ax
           out  dx,ax

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; free the address we used for the frame_addr
           push es
           mov  ah,49h
           mov  es,frame_addr
           int  21h
           pop  es

           ; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
           ; done, so exit cleanly
           .exit

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; if we wait for the BIOS time_stamp to increment at least
;  once, we have waited at least 1/18.2 of a second or 55ms
;  and at most 1/9.1 of a second, or 109ms
delay55ms  proc near uses es
           xor  ax,ax
           mov  es,ax
           mov  ax,es:[046Ch]   ; wait the first time for the actual tick
@@:        cmp  ax,es:[046Ch]
           je   short @b
           mov  ax,es:[046Ch]   ; wait the second time for another tick
@@:        cmp  ax,es:[046Ch]
           je   short @b
           ret
delay55ms  endp


;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; read from the PCI config space
; on entry:
;  bh = bus
;  bl = dev
;  cl = func
;  ch = offset in bytes (0, 1, 2, ,255)
;  dl = size in bytes to return
; on return
;  eax = read value in (dl) bytes
pci_read   proc near uses cx edx
           push dx                  ; save size wanted in dl
           mov  eax,8000h
           mov  al,bh               ; bus
           shl  eax,5
           or   al,bl               ; dev
           shl  eax,3
           or   al,cl               ; func
           shl  eax,6
           push cx                  ;
           shr  ch,2                ; make dword based
           or   al,ch               ; offset
           pop  cx                  ; 
           shl  eax,2               ; last two bits are zero.
           mov  dx,PCI_ADDR
           out  dx,eax
           mov  dl,ch               ; last 2 bits of offset
           and  dx,0003h
           add  dx,PCI_DATA
           in   eax,dx
           pop  dx                  ; restore size wanted in dl
           mov  cl,4
           sub  cl,dl
           shl  cl,3
           mov  edx,0FFFFFFFFh
           shr  edx,cl
           and  eax,edx
           ret
pci_read   endp

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; write to the PCI config space
; on entry:
;  bh = bus
;  bl = dev
;  cl = func
;  ch = offset in bytes (0, 1, 2, ,255)
;  dl = size in bytes
; eax = value to write
; on return
;  nothing
pci_write  proc near uses cx ebx edx
           push eax                 ; save value to write
           push dx                  ; save size wanted in dl
           mov  eax,8000h
           mov  al,bh               ; bus
           shl  eax,5
           or   al,bl               ; dev
           shl  eax,3
           or   al,cl               ; func
           shl  eax,6
           push cx                  ;
           shr  ch,2                ; make dword based
           or   al,ch               ; offset
           pop  cx                  ; 
           shl  eax,2               ; last two bits are zero.
           mov  dx,PCI_ADDR
           out  dx,eax
           mov  dl,ch               ; last 2 bits of offset
           and  dx,0003h
           add  dx,PCI_DATA
           in   eax,dx
           pop  cx                  ; restore size wanted in cl
           shl  cl,3
           mov  ebx,0FFFFFFFFh
           shl  ebx,cl
           and  eax,ebx
           pop  ebx
           or   eax,ebx
           out  dx,eax
           ret
pci_write  endp

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; print a string
; on entry:
;  ds:si -> asciiz string
; on return:
;  nothing
prt_string proc near uses ax dx si
           mov  ah,02                   ; DOS print char service
@@:        lodsb                        ; Get character & point to next one
           or   al,al                   ; End of string?
           jz   short @f                ; Yes, so exit
           mov  dl,al                   ; 
           int  21h                     ; Output a character
           jmp  short @b                ; Keep doing it
@@:        ret
prt_string endp

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; print a byte in hex format
; on entry:
;  al = value
; on return:
;  nothing
prt_byte   proc near uses ax cx dx
           mov  cx,02
@@:        rol  al,04          ;
           push ax             ;
           and  al,0Fh         ;
           daa                 ;
           add  al,0F0h        ;
           adc  al,40h         ;
           mov  ah,02          ;
           mov  dl,al          ;
           int  21h            ;
           pop  ax             ;
           loop @b             ;
           ret
prt_byte   endp

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; data

io_base    dw 0000h    ; the io base for the UHCI
int_num    db 00h      ; interrupt number to use

frame_addr  dw 0000h   ; segment address of allocated memory
frame_addra dw 0000h   ; segment address of aligned memory

port       dw  0000h   ; port number offset 0x10 or 0x12

.para ; must be paragraph aligned
Queue      st  UHCI_Q

.para
Setup_TD   st  UHCI_TD uses  000000000h                \ ; calc'd at run time
                             ((3<<27) | (80h<<16))     \ ; C_ERR | STATUS
                             ((7<<21) | (0<<19) | 2Dh) \ ; Len = 8, Data0, Setup
                             000000000h                  ; calc'd at run time

TD0        st  UHCI_TD uses  000000000h                \ ; calc'd at run time
                             ((3<<27) | (80h<<16))     \ ; C_ERR | STATUS
                             ((7<<21) | (1<<19) | 69h) \ ; Len = 8, Data1, In
                             000000000h                  ; calc'd at run time

TD1        st  UHCI_TD uses  000000000h                \ ; calc'd at run time
                             ((3<<27) | (80h<<16))     \ ; C_ERR | STATUS
                             ((7<<21) | (0<<19) | 69h) \ ; Len = 8, Data0, In
                             000000000h                  ; calc'd at run time

TD2        st  UHCI_TD uses  000000000h                \ ; calc'd at run time
                             ((3<<27) | (80h<<16))     \ ; C_ERR | STATUS
                             ((1<<21) | (1<<19) | 69h) \ ; Len = 2, Data1, In
                             000000000h                  ; calc'd at run time

Status_TD  st  UHCI_TD uses  000000001h                \ ; terminate
                             ((3<<27) | (1<<24) | (80h<<16)) \ ; C_ERR | IOC | STATUS
                             ((7FFh<<21) | (1<<19) | 0E1h) \ ; Len = 0, Data1, Out
                             000000000h                  ; calc'd at run time

Device_Descriptor  dup 18,0

Setup_Packet  db 80h    ; dev->host, type=standard, recipient=device
              db  6     ; get descriptor
              db  0     ; index = 0
              db  1     ;  type = device
              dw  0     ; value = 0 (not used)
              dw  18    ; 18 bytes

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; strings
s_no_uhci_found  db 13,10,"We didn't find a UHCI, Ben",0
s_found_uhci_dev db 13,10,'Found a UHCI device: ',0
s_no_memory      db 13,10,'No memory found.',0
s_fired          db 13,10,'Interrupt fired.',0

crlf  db 13,10,0


.end


[1] The UHCI has a proto code of 0x00, while the OHCI has 0x10 and
    the EHCI has a code of 0x20.  Simply change this byte, and the
    code will find that respected controller.  However, if you
    change it to anything but UHCI, the code following will not
    work as you expected :-)



A small explaination of what the controller tries to do, assuming
no errors occore.

Frame list:  1024 dwords.
 _____________________________
  ... | ... |  n  | ... | ...
 -----------------------------

When the controller is started, it waits for it's SOF (start of frame)
signal which happens every 1ms (or what ever is in the SOF register).
At every 1ms, it grabs the value from FRAME_NUMBER, ANDs by 3FFh and
gets the dword from the byte address:

   FRAME_LIST_ADDRESS + (FRAME_NUMBER * 4)

The value has a 16 byte aligned 32-bit physical address of a queue
(or a TD, but always should be a queue).  The bottom four bits are
used for control.  If bit 1 is set, the item pointed to by the
address pointer points to a queue.  If clear it points to a TD.
If bit 0 is set, the pointer is invalid.  Don't do anything, but wait
for the next SOF signal.  If the bit is clear, the pointer is valid.
The other two bits (bit 3:2) are reserved and should be zeros.

For the purpose of this demonstration, we will assume the frame_list
pointer above always points to a queue.

A queue is 8 bytes in size and has two 32-bit pointers and must be
16 byte aligned so that the frame list pointer above has the bottom
four bits clear for the control bits.

    ----------
    |  horz  |
    ----------
    |  vert  |
    ----------


The first pointer is the HORZontal pointer.  It points to another Queue.
It may point to a TD, but usually to a Queue.  The second pointer is the
VERTical pointer.  It pointer to a set of TD's, though could point to
another Queue.

The controller has a Queue Stack, hence the term "USB stack".  It takes
the dword in the HORZ pointer, places it in the stack, increments to
the next stack location and reads in the VERT pointers dword value.

It then starts to 'execute' the dword on the top of the stack.
If it points to a queue, the process is repeated.  It places the
value of the new queues HORZ pointer in the current stack location,
then increments the stack, then loads the Vert pointer of the new
queue on to the stack.  It continues this process until the pointers
bit 0 is set or it finds a TD pointed to by the VERT pointer.

A Transfer Descriptor (TD) is 32 bytes in length and must be 16-byte
aligned:

   -------------------------------------------------------
   |  32 - bit Link Pointer to next TD  | 0 | Vf | Q | T |
   -------------------------------------------------------
   | reply/status bits  (see page 21-23 in the specs)    |
   -------------------------------------------------------
   | info bits (see page 21 & 24 in the specs)           |
   -------------------------------------------------------
   | buffer pointer (doesn't need to be aligned)         |
   -------------------------------------------------------
   |                                                     |
   |   4 dwords reserved for software use                |
   |    the controller will not modify these dwords      |
   |                                                     |
   -------------------------------------------------------

If it finds a pointer to a TD, (bit 1 clear), then it loads the TD
(32 bytes) and executes the TD.  When completed (ignoring any errors
for the purpose of this demo), it copies the Link Pointer of the
TD into the top most dword of the stack.

The controller then checks the Vf bit (bit 2) of the top most dword
in the stack.  If it is set, it loads the TD (or Queue) pointed by
the top most dword of the stack and continues.  If the Vf bit is
clear it decrements the stack pointer to the next most dword in the
stack and executes it.

How about a crude illistration:

A standard get descriptor (we use this in the code above)

 _____________________________
  ... | ... |  n  | ... | ...    [n] = physical pointer to Queue
 -----------------------------         with bit 3:2 = 0 (reserved)
               |                            bit 1   = 1 (is a queue)
               |                            bit 0   = 0 (is valid pointer)
        /------/
        |
        V
    ----------
    |  horz  |----> value = 0xrrrrrrr1  (bit 0 = not a valid pointer)
    ----------                          (rrrrrrr = doesn't matter)
    |  vert  |
    ----------
        |
        V
   ------------------------------------------------------
   |  32 - bit Link Pointer to next TD  | 0 | 0 | 0 | 0 |------\
   ------------------------------------------------------      |
   | CERR = 3, LS = 1, STATUS = 0x80                    |      |
   ------------------------------------------------------      |
   | MAXLEN = 7, D = 0, EndPt = 0, Addr = 0, PID = Setup|      |
   ------------------------------------------------------      |
   | pointer to the Setup Packet                        |      |
   ------------------------------------------------------      |
   |   four 0x00000000's                                |      |
   ------------------------------------------------------      |
                                                               |
        /------------------------------------------------------/
        |
        V
   ------------------------------------------------------
   |  32 - bit Link Pointer to next TD  | 0 | 0 | 0 | 0 |------\
   ------------------------------------------------------      |
   | CERR = 3, LS = 1, STATUS = 0x80                    |      |
   ------------------------------------------------------      |
   | MAXLEN = 7, D = 1, EndPt = 0, Addr = 0, PID = IN   |      |
   ------------------------------------------------------      |
   | pointer to the buffer to hold the data (8 bytes)   |      |
   ------------------------------------------------------      |
   |   four 0x00000000's                                |      |
   ------------------------------------------------------      |
                                                               |
        /------------------------------------------------------/
        |
        V
   ------------------------------------------------------
   |  32 - bit Link Pointer to next TD  | 0 | 0 | 0 | 0 |------\
   ------------------------------------------------------      |
   | CERR = 3, LS = 1, STATUS = 0x80                    |      |
   ------------------------------------------------------      |
   | MAXLEN = 7, D = 0, EndPt = 0, Addr = 0, PID = IN   |      |
   ------------------------------------------------------      |
   | pointer to the buffer to hold the data (8 bytes)   |      |
   ------------------------------------------------------      |
   |   four 0x00000000's                                |      |
   ------------------------------------------------------      |
                                                               |
        /------------------------------------------------------/
        |
        V
   ------------------------------------------------------
   |  32 - bit Link Pointer to next TD  | 0 | 0 | 0 | 0 |------\
   ------------------------------------------------------      |
   | CERR = 3, LS = 1, STATUS = 0x80                    |      |
   ------------------------------------------------------      |
   | MAXLEN = 1, D = 0, EndPt = 0, Addr = 0, PID = IN   |      |
   ------------------------------------------------------      |
   | pointer to the buffer to hold the data (2 bytes)   |      |
   ------------------------------------------------------      |
   |   four 0x00000000's                                |      |
   ------------------------------------------------------      |
                                                               |
        /------------------------------------------------------/
        |
        V
   ------------------------------------------------------
   |                                    | 0 | 0 | 0 | 1 |
   ------------------------------------------------------
   | CERR = 3, LS = 1, STATUS = 0x80, IOC = 1           |
   ------------------------------------------------------
   | MAXLEN=7FFh, D = 1, EndPt = 0, Addr = 0, PID = OUT |
   ------------------------------------------------------
   |                                                    |
   ------------------------------------------------------
   |   four 0x00000000's                                |
   ------------------------------------------------------


Controller does the following:

  1) retrieve the value at BASE_ADDRESS+FRAME_NUMBER
     if bit 0 set, goes to #10 below
     if bit 0 clear, continue
  2) if bit 1 above is set
     a) loads pointer->horz onto stack
     b) loads pointer->vert onto stack
  3) a) if bit 0 of top_of_stack is set,
         decrement stack and go to #3a      <------------\
     b) if bit 1 of top_of_stack is clear,               |
         go to #2a                                       |
     c) if bit 1 of top_of_stack is set,                 |
         load a TD at the address (top_of_stack & ~0x0F) |
  4) execute TD                                          |
  5) copy TD->dword0 to top_of_stack                     |
  6) if TD.Vf is set go to #3a                           |
     if TD.Vf is clear go to ----------------------------/

Page 27, Figure 9, of the specs has a much better illistration.

