USB in DOS

Please see this page for more information.

The following code is included with and used for the example code for the Hugi Compo #28. However, it will explain how to find the PCI UHCI controller without using the BIOS, set up the controller, detect the ports, detect a device on the port, reset the device, and retrieve the device descriptor. It is a rough draft, but works at the moment. Please note, the code is purposely bloated since the object of the hugi compo is to create the same program in as few bytes as possible.

Let me know if you have any questions or comments about this code.

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
;  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 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 https://www.fysnet.net/newbasic.htm
;          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 concent 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 then 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
           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...",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, the EHCI 
    has a code of 0x20, and the xHCI has a code of 0x30.  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 :-)