0% found this document useful (0 votes)
7 views22 pages

USB Assembly Tutorial

This document is a comprehensive tutorial on creating a USB UHCI driver using x86 assembly language, aimed at beginners. It covers assembly fundamentals, the USB and UHCI hardware architecture, and provides detailed explanations of registers, instructions, and the driver initialization process. The tutorial includes annotated code walkthroughs and essential instructions for effective driver development.

Uploaded by

khilafatkingwing
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views22 pages

USB Assembly Tutorial

This document is a comprehensive tutorial on creating a USB UHCI driver using x86 assembly language, aimed at beginners. It covers assembly fundamentals, the USB and UHCI hardware architecture, and provides detailed explanations of registers, instructions, and the driver initialization process. The tutorial includes annotated code walkthroughs and essential instructions for effective driver development.

Uploaded by

khilafatkingwing
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

USB UHCI Driver

x86 Assembly — Complete Beginner's Tutorial


From zero knowledge to a working low-level USB host controller driver
Registers · Instructions · PCI Bus · UHCI Protocol · TDs · Enumeration

Cha
Topic Page
pter

1 Assembly Fundamentals — Registers, Instructions, Memory 2

2 x86 I/O, Stack & Calling Convention 4

3 USB & UHCI Hardware Architecture 6

4 Driver Init — usb_driver_init() Branch Tree 8

5 PCI Bus Scanning — pci_find_uhci() Branch Tree 10

6 Controller Reset — usb_reset_controller() Branch Tree 12

7 Port Checking & Enumeration Branch Trees 14

8 Control Transfer & TD Allocation 16

9 Complete Annotated Code Walkthrough 18

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 2

Chapter 1: Assembly Fundamentals


What is Assembly?
Assembly (ASM) is the lowest-level human-readable programming language. Every
line maps almost directly to one machine instruction the CPU executes. There
is no compiler — an assembler (NASM here) translates mnemonics like MOV, ADD,
JMP into raw binary opcodes the processor fetches and runs. You have total
control of the hardware — and total responsibility for every byte.

1.1 How a CPU Works — The Fetch-Decode-Execute Loop


The CPU endlessly repeats three steps: (1) Fetch the next instruction bytes from memory at the address in the
EIP (instruction pointer) register. (2) Decode the opcode to know which operation to perform. (3) Execute —
carry out the operation, update registers and flags, then advance EIP to the next instruction.

CPU Fetch-Decode-Execute Cycle

CPU

1. FETCH 2. DECODE 3. EXECUTE


Read bytes at EIP Identify opcode Perform operation

read operands compute

loop advance
Memory Registers ALU
RAM/Cache EAX..EBP Arithmetic unit

EIP++
Next instruction

Figure 1.1 — The CPU cycle that executes every assembly instruction

1.2 x86 General-Purpose Registers


In 32-bit (IA-32) mode the CPU has eight 32-bit general-purpose registers. Each can be accessed in full (32-bit
E__ form), lower 16 bits, or the lowest two 8-bit halves. The USB driver uses all of them for specific jobs:

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 3

EAX EBX ECX EDX ESI EDI ESP EBP

AX BX CX DX SI DI SP BP

AH AL BH BL CH CL DH DL

Accumulator Base Counter Data Source Idx Dest Idx Stack Ptr Base Ptr
Return values, arithmetic Base pointer / Loop counter, shifts I/O port addr, Source string pointerDestination string pointer Top of stack Stack frame base
general mul/div

Register Bit Layout (32-bit mode)


31 23 15 7 0

EFLAGS Register (key bits used in conditional jumps):

CF ZF SF OF DF IF PF
Carry Zero Sign Overflow Direction Interrupt Parity

1 = set 1 = set 1 = set 1 = set 1 = set 1 = set 1 = set


0 = clear 0 = clear 0 = clear 0 = clear 0 = clear 0 = clear 0 = clear

Figure 1.2 — x86 register layout and EFLAGS bits

Register USB Driver Usage

EAX I/O port r/w (in ax,dx / out dx,ax), return values, PCI address building

EBX USB device address during enumeration

ECX Busy-wait loop counter (LOOP instruction), timeout counters

EDX I/O port address (UHCI registers), PCI bus/device scan

ESI String pointer (print_string), setup_packet pointer (control_transfer)

EDI Allocated TD pointer, frame_list fill destination (stosd)

ESP Stack pointer (managed automatically by PUSH/POP/CALL/RET)

EBP Saved stack frame base (push ebp / mov ebp,esp pattern)
Table 1.1 — How each register is used in the USB driver

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 4

1.3 Essential Instructions Reference


These are the core instructions used throughout the driver. Learning these ~25 instructions will let you read the
entire codebase:

Instruction Syntax What it does Flags affected


MOV mov dst, src Copy src into dst (no flags changed) None

PUSH push reg/imm Decrement ESP by 4, write to [ESP] None

POP pop reg Read from [ESP], increment ESP by 4 None

ADD add dst, src dst = dst + src CF ZF SF OF PF

SUB sub dst, src dst = dst - src CF ZF SF OF PF

AND and dst, src Bitwise AND (clear bits) ZF SF PF; CF=OF=0

OR or dst, src Bitwise OR (set bits) ZF SF PF; CF=OF=0

XOR xor dst, src Bitwise XOR; xor eax,eax → eax=0 ZF SF PF; CF=OF=0

TEST test dst, src AND without storing; sets ZF if result=0 ZF SF PF; CF=OF=0

CMP cmp dst, src SUB without storing; sets flags for jcc CF ZF SF OF PF

JMP jmp label Unconditional jump None

JZ/JE jz label Jump if ZF=1 (result zero / equal) None

JNZ/JNE jnz label Jump if ZF=0 (not zero / not equal) None

JC jc label Jump if CF=1 (carry set) None

LOOP loop label Decrement ECX; jump if ECX≠0 None

CALL call label PUSH return addr then JMP to label None

RET ret POP return address and jump to it None

IN in ax, dx Read from I/O port DX into AX None

OUT out dx, ax Write AX to I/O port DX None

SHL shl dst, n Shift left n bits (multiply by 2^n) CF ZF SF

SHR shr dst, n Shift right n bits (divide by 2^n, unsigned) CF ZF SF

LEA lea dst, [src+off] Load effective address (no memory access) None

STOSD stosd Store EAX to [EDI], increment EDI by 4 None

REP rep stosd Repeat STOSD ECX times None

INT int 0x10 Software interrupt (BIOS call here) Varies

Table 1.2 — x86 instruction quick reference

1.4 NASM Syntax Crash Course

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 5

NASM Syntax Basics

; Semicolon = comment (ignored by assembler)


section .data ; Read/write initialised data
my_val dd 0x1234 ; 'dd' = define dword (32-bit), hex value
my_str db 'Hi',0 ; 'db' = define byte; 0 = null terminator

section .bss ; Uninitialised data (zeroed at startup)


buffer resb 64 ; Reserve 64 bytes

section .text ; Executable code


global my_func ; Export symbol to linker

my_func: ; Label = address marker


push ebp ; Save caller's base pointer
mov ebp, esp ; Set our own frame
mov eax, [my_val] ; Load memory → register (square brackets = dereference)
add eax, 1 ; eax = eax + 1
mov [my_val], eax ; Store register → memory
pop ebp ; Restore
ret ; Return to caller

Key NASM Rule


Square brackets [ ] mean 'the value AT this address' (like a pointer
dereference in C). Without brackets you get the address itself. mov eax,
my_val → eax = address of my_val. mov eax, [my_val] → eax = value stored at
my_val.

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 6

Chapter 2: x86 Stack, Calling Convention & I/O


Ports
2.1 The Stack
The stack is a region of memory that grows downward (toward lower addresses). ESP always points to the
topmost used slot. PUSH subtracts 4 from ESP then writes; POP reads then adds 4. The stack is used for return
addresses, saved registers, and local variables.

Stack Frame Layout (push ebp / mov ebp,esp)


; Standard function prologue
High address my_func:
push ebp ; save caller EBP
Caller param 2 EBP+12 mov ebp, esp ; new frame base
sub esp, 8 ; 2 local vars
Caller param 1 EBP+8 push ebx ; callee-saved

Return address EBP+4 ; Access parameters


mov eax, [ebp+8] ; param 1
Saved EBP ■■■ EBP+0 mov ecx, [ebp+12] ; param 2

Local var 1 EBP-4 ; Standard epilogue


pop ebx ; restore saved
Local var 2 EBP-8 mov esp, ebp ; free locals
pop ebp ; restore frame
← ESP (top) Low ret ; return

Figure 2.1 — Stack frame layout (left) and assembly prologue/epilogue (right)

2.2 cdecl Calling Convention (used in this driver)


Rule Detail

Parameter passing Pushed right-to-left onto stack before CALL

Return value Returned in EAX (32-bit) or EDX:EAX (64-bit)

Caller-saved regs EAX, ECX, EDX — caller must save if needed

Callee-saved regs EBX, ESI, EDI, EBP — function must preserve

Stack cleanup Caller pops parameters after call returns

2.3 x86 I/O Ports — The Gateway to Hardware


x86 has a separate 64 KB I/O address space (distinct from memory). The IN and OUT instructions read/write it.
This is how the USB driver talks to the UHCI controller — the controller's registers are mapped into I/O port
space starting at a base address found in PCI config space.

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 7

I/O Port Instructions

; Write to I/O port


mov dx, 0x3F8 ; DX = port address
mov al, 'A' ; AL = byte to send
out dx, al ; write AL to port DX

; Read from I/O port


mov dx, 0x3F8 ; DX = port address
in al, dx ; read from port DX into AL

; In the USB driver (16-bit register, use AX):


movzx edx, word [io_base] ; load I/O base (zero-extend to 32-bit)
add edx, 0x02 ; + USBSTS offset
in ax, dx ; read status register
test ax, 0x0020 ; check HC Halted bit

MOVZX — Zero-Extension
movzx edx, word [io_base] copies a 16-bit value into the lower 16 bits of EDX
and CLEARS the upper 16 bits to zero. This prevents garbage in the high bits
from corrupting the I/O port address calculation. Always use MOVZX when
widening a value.

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 8

Chapter 3: USB & UHCI Hardware Architecture


3.1 The USB Stack in One Picture
Universal Serial Bus (USB) is a host-controlled, polled bus. The Host Controller (HC) — UHCI here — is the
hardware that generates all bus traffic. Software tells it what to do by writing Transfer Descriptors into memory;
the HC reads them and sends/receives packets on the physical wires at 12 Mbit/s (Full Speed) or 1.5 Mbit/s
(Low Speed).

USB Software & Hardware Stack

Application

URB

USB Core / Stack

submit

HCD (our driver)

TDs/QHs

UHCI Controller

signals

USB PHY (chip)

bits
USB Device
packets

D+ / D- Wires

Figure 3.1 — Layers from application to physical wire

3.2 UHCI Memory-Mapped Schedule


UHCI uses a software-visible schedule in main RAM. The driver writes a 4KB-aligned Frame List of 1024
pointers. Every millisecond, the HC reads the pointer for the current frame, follows it to a Queue Head, then
walks the linked list of Transfer Descriptors to execute transactions.

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 9

Frame List
UHCI Frame List → Queue Head → Transfer Descriptor Chain
(1024 × 4B)

Frame[0] → Queue Head (QH)


SETUP TD DATA TD STATUS TD
HorizontalPtr →
Frame[1] → (8 bytes) (varies) (0 bytes)
VerticalPtr ↓ LinkPtr LinkPtr LinkPtr T
(16-byte align) Control/Sts Control/Sts Control/Sts (Terminate)
Frame[2] → Token | Buf Token | Buf Token | Buf
32-byte aligned

...

Frame[1023]→

Polled every 1ms

Figure 3.2 — Frame List → Queue Head → Transfer Descriptor chain

3.3 UHCI I/O Registers


Offset Name Size Purpose

0x00 USBCMD 16-bit Run/Stop, Reset, Configure flag

0x02 USBSTS 16-bit Interrupt status, Halted flag

0x04 USBINTR 16-bit Interrupt enable mask

0x06 FRNUM 16-bit Current frame number (0-1023)

0x08 FRBASEADD 32-bit Physical address of 1024-entry frame list

0x0C SOFMOD 8-bit Start-of-Frame modifier (default 0x40)

0x10 PORTSC1 16-bit Port 1 status & control

0x12 PORTSC2 16-bit Port 2 status & control


Table 3.1 — UHCI register map (base address from PCI BAR4)

3.4 Transfer Descriptor (TD) Internals


Each TD describes one USB transaction (SETUP, IN, or OUT). It is 32 bytes (four 32-bit dwords) and must be
32-byte aligned. The HC reads it, sends the packet, and writes back the status.

Transfer Descriptor (TD) — 32-byte Structure


DW0: Link Pointer
Next TD/QH addr [31:4] D… QH T 0

DW1: Control & Status


SPD|Err Cnt|LS|IOS|IOC Status bits (Active/Stall/DBE… ActLen [10:0]

DW2: Token
MaxLen [20:10] T… Endpoint [6:3] Addr [13:7] PID [7:0]

DW3: Buffer Pointer


Physical address of data buffer

Figure 3.3 — TD 32-byte layout with bit-field breakdown

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 10

Chapter 4: Driver Entry Point — usb_driver_init()


This is the top-level function that orchestrates the entire driver startup sequence. Every other function is called
from here, directly or indirectly.

usb_driver_init() Flow

usb_driver_init
entry point

print_string
'Initializing…'

Init variables
td_index=0,dev=1

pci_find_uhci()
scan PCI bus

EAX?

Found? Reset OK?

0=fail ok 0=fail EAX?

ERROR exit usb_reset_controller ok


EAX = -1 global+HC reset

uhci_init_framelist Start HC
fill 1024 entries USBCMD=RS|CF|MAXP

usb_check_ports()
Port1 & Port2

Figure 4.1 — Complete usb_driver_init() branch tree

4.1 Annotated Code — usb_driver_init

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 11

usb_driver_init() — Fully Annotated

usb_driver_init:
push ebp
mov ebp, esp ; standard prologue — save & set frame
push ebx ; callee-saved registers we will use
push esi
push edi

mov dword [td_index], 0 ; reset TD pool index


mov byte [dev_addr], 1 ; first device gets address 1

call pci_find_uhci ; ← returns I/O base in EAX


test eax, eax ; is EAX zero?
jz .error ; yes → not found, jump to error
mov [io_base], ax ; save the 16-bit I/O base address

call usb_reset_controller
test eax, eax
jz .error ; reset failed

call uhci_init_framelist ; fill 1024 entries with 'Terminate'

; Tell controller WHERE the frame list lives (physical address)


movzx edx, word [io_base]
add edx, UHCI_FRBASEADD ; offset 0x08
mov eax, frame_list ; address of our 4KB-aligned array
out dx, eax ; write to FRBASEADD register

; Start the controller: Run=1, Configure=1, MaxPacket=64


movzx edx, word [io_base]
add edx, UHCI_USBCMD
mov ax, (UHCI_CMD_RS | UHCI_CMD_CF | UHCI_CMD_MAXP)
out dx, ax

call usb_check_ports ; check Port 1 & Port 2


xor eax, eax ; return 0 = success
jmp .done

.error:
mov eax, -1 ; return -1 = failure
.done:
pop edi
pop esi
pop ebx
pop ebp
ret

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 12

Chapter 5: PCI Bus Scanning — pci_find_uhci()


Before we can touch a single USB register, we must find the UHCI controller on the PCI bus. x86 systems
expose PCI configuration space through two I/O ports: 0xCF8 (address) and 0xCFC (data). We iterate every
bus/device combination and check the class code.

PCI Configuration Space Access (Port 0xCF8 / 0xCFC)

En Rsv Bus Dev Fn Reg 00


1b 7b 8b 5b 3b 6b 2b

Write to port 0xCF8 → Read result from port 0xCFC


Bus (8b): 0-255 Device (5b): 0-31 Function (3b): 0-7 Register (6b): 0-63

addr = 0x80000000 | (bus<<16) | (device<<11) | (func<<8) | reg


For UHCI: class=0x0C03 at offset 0x08, BAR4 (I/O base) at offset 0x20

Figure 5.1 — PCI config address format and access method

pci_find_uhci() Branch Tree

pci_find_uhci
bus=0

bus loop
bus 0..255

device loop
dev 0..31

Build CF8 addr


0x80000000|…

port 0xCF8

OUT 0xCF8
write address

port 0xCFC

IN 0xCFC shr>>16 next bus


read class code class==0x0C03? ebx++

yes no ecx>=32

Readebx>=256
BAR4 next device
offset 0x20 ecx++

AND 0xFFE0

Return 0 Return EAX


not found I/O base addr

Figure 5.2 — PCI scan branch tree (nested bus/device loops)

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 13

pci_find_uhci() — Annotated

pci_find_uhci:
push ebx ; bus counter
push ecx ; device counter
push edx
xor ebx, ebx ; bus = 0

.bus_loop:
xor ecx, ecx ; device = 0

.dev_loop:
; Build PCI address for class-code register (offset 0x08)
mov eax, 0x80000000 ; Enable bit (bit 31 = 1)
mov edx, ebx ; copy bus
shl edx, 16 ; move to bits 23:16
or eax, edx ; OR bus into address
mov edx, ecx ; copy device
shl edx, 11 ; move to bits 15:11
or eax, edx ; OR device into address
or eax, 0x08 ; register = 0x08 (class+subclass+prog IF)

mov dx, 0xCF8 ; PCI address port


out dx, eax ; write the address we want to read
mov dx, 0xCFC ; PCI data port
in eax, dx ; read 32-bit register

shr eax, 16 ; shift class:subclass down to bits 15:0


cmp ax, 0x0C03 ; USB (0x0C) / UHCI (0x03)?
je .found_device ; yes — jump to found

inc ecx ; next device


cmp ecx, 32
jl .dev_loop
inc ebx ; next bus
cmp ebx, 256
jl .bus_loop
xor eax, eax ; not found → return 0
jmp .done

.found_device:
; Read BAR4 (Base Address Register 4) = I/O base of UHCI regs
or eax, 0x20 ; offset 0x20 within PCI config space
out dx, eax ; (dx still = 0xCFC after adjustment)
in eax, dx
and eax, 0xFFFFFFE0 ; mask off lower 5 bits (type bits)
; EAX = I/O base address of UHCI controller
.done:
pop edx
pop ecx
pop ebx
ret

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 14

Chapter 6: Controller Reset —


usb_reset_controller()
Hardware reset is mandatory before configuring any registers. UHCI requires two distinct resets: a Global Reset
(holds for ~10ms to reset attached devices too) and a Host Controller Reset (resets the HC state machine,
verified by polling the HCRESET bit).

usb_reset_controller() Branch Tree

reset_controller

USBCMD |= GRESET
bit 2 = global reset

busy-wait 10ms
loop 100000×

USBCMD = 0
clear global reset

wait 5ms
loop 50000×

USBCMD |= HCRESET
bit 1 = HC reset

Poll HCRESET bit in ax,dx


loop 10000× HCRESET==0?

cleared ECX=0

Clear USBSTS
return 1 (ok) timeout → return 0
write 0xFF
failure

Figure 6.1 — Two-phase reset with polling loop

Why Two Resets?


The Global Reset (GRESET) sends a USB reset signal to all connected devices —
it puts them back to address 0 and default configuration. The HC Reset
(HCRESET) resets only the host controller's internal state machine, frame
counter, and register defaults. You must always do GRESET first, then
HCRESET.

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 15

usb_reset_controller() — Annotated

usb_reset_controller:
push ecx
push edx

; ■■ Step 1: Global Reset ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


movzx edx, word [io_base]
add edx, UHCI_USBCMD ; offset 0x00
mov ax, UHCI_CMD_GRESET ; bit 2 = 0x0004
out dx, ax ; assert global reset

mov ecx, 100000 ; ~10ms busy wait


.delay1:
loop .delay1 ; decrement ECX; jump if ≠ 0

; ■■ Step 2: Clear Global Reset ■■■■■■■■■■■■■■■■■■■■■■■■■■■


xor ax, ax
out dx, ax ; write 0 → deassert GRESET

mov ecx, 50000 ; 5ms wait


.delay2:
loop .delay2

; ■■ Step 3: HC Reset ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


movzx edx, word [io_base]
add edx, UHCI_USBCMD
mov ax, UHCI_CMD_HCRESET ; bit 1 = 0x0002
out dx, ax

; ■■ Step 4: Poll until HC clears the bit ■■■■■■■■■■■■■■■■■


mov ecx, 10000
.reset_wait:
in ax, dx ; read USBCMD
test ax, UHCI_CMD_HCRESET ; is HCRESET still set?
jz .reset_done ; zero flag set → bit cleared → done
loop .reset_wait ; else keep polling

xor eax, eax ; timeout — return failure (0)


jmp .done

.reset_done:
; ■■ Step 5: Clear pending status bits ■■■■■■■■■■■■■■■■■■■■
movzx edx, word [io_base]
add edx, UHCI_USBSTS
mov ax, 0x00FF ; write 1 to clear each bit
out dx, ax
mov eax, 1 ; return success
.done:
pop edx
pop ecx
ret

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 16

Chapter 7: Port Checking & Device Enumeration


After the controller starts, we probe each port's status register. Bit 0 (CCS — Current Connect Status) tells us if
a device is plugged in. If so, we reset the port to bring the device to a known state, then enumerate it.

usb_check_ports() Branch Tree

usb_check_ports

Read PORTSC1
in ax,[io_base+0x10]

TEST bit0

CCS bit set?

ZF=1 ZF=0

uhci_reset_port()
'No device' assert PR bit 50ms

usb_enumerate_device
Port 1

Read PORTSC2
in ax,[io_base+0x12]

TEST bit0

CCS bit set?

ZF=1 ZF=0

uhci_reset_port() usb_enumerate_device
'No device' Port 2 Port 2

Figure 7.1 — Port scan and conditional device enumeration

7.1 USB Control Transfer — Enumeration Protocol


Enumeration is the process of assigning an address to a newly connected device and reading its descriptor. It
always uses Control Transfers — the most complex but most reliable transfer type with a mandatory
three-phase handshake.

USB Control Transfer — 3 Phases

SETUP Stage DATA Stage STATUS Stage


8-byte packet 0 or more DATA Handshake
bmRequestType packets IN/OUT ACK / NAK
bRequest (optional) STALL
wValue/Index/Length

SETUP PID DATA0/DATA1 IN PID (0x69)


(0x2D) PID toggle zero length

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 17

Figure 7.2 — Control transfer phases (SETUP → DATA → STATUS)

usb_enumerate_device() Branch Tree

usb_enumerate_device

Build SETUP packet


GET_DESCRIPTOR(8B)

uhci_control_transfer EAX?
addr=0, 8 bytes Success?

0
Build SETUP packet
SET_ADDRESS(new_addr)

return 0 (fail)
uhci_control_transfer
addr=0 → assign

dev_addr++
next address slot

Build SETUP packet


GET_DESCRIPTOR(18B)

uhci_control_transfer
full descriptor

return 1 (ok)

Figure 7.3 — Three-step enumeration: partial descriptor → set address → full descriptor

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 18

Chapter 8: Control Transfer & alloc_td()


uhci_control_transfer() is the most complex function. It allocates TDs, fills their fields with the correct
PID/address/token values, links them together, inserts them into the frame list, then busy-polls the HC until it
marks the TDs inactive.

uhci_control_transfer() Branch Tree

uhci_control_transfer

alloc_td() alloc_td()
→ SETUP TD ptr → STATUS TD ptr

Fill SETUP TD Fill STATUS TD


PID=0x2D token+buf PID=0x69 IOC set

Link SETUP→STATUS
[edi]=STATUS_addr

Insert into frame[0]


[frame_list]=SETUP

Poll SETUP TD Poll STATUS TD


test ACTIVE bit test ACTIVE bit

in [edi+4] cleared in [edi+4]

Active? Active?

ECX=0 cleared ECX=0

timeout fail timeout fail


return 1

Figure 8.1 — Control transfer: allocate, link, insert, poll

8.1 alloc_td() — The TD Pool Allocator


Rather than calling malloc, the driver uses a simple pool allocator. It keeps an index (td_index) into a
pre-allocated 4KB block. Each call returns the next 32-byte slot, zeroes it, and increments the index.

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 19

alloc_td() — Pool Allocator

alloc_td:
push ecx
mov eax, [td_index] ; current index (0, 1, 2, …)
mov ecx, eax
shl ecx, 5 ; multiply by 32 (each TD = 32 bytes)
add ecx, td_pool ; add pool base address
mov eax, ecx ; EAX = address of this TD

; Zero-fill the 32-byte TD (REP STOSD writes ECX dwords)


push edi
push ecx
mov edi, eax ; destination = TD address
mov ecx, 8 ; 8 × 4 bytes = 32 bytes
xor eax, eax ; value to write = 0
rep stosd ; store EAX to [EDI], EDI+=4, ECX-- until 0
pop ecx
pop edi

mov eax, ecx ; restore TD address as return value


inc dword [td_index] ; advance pool index
pop ecx
ret

; SHL trick explained:


; index=0 → 0*32=0 → td_pool+0
; index=1 → 1*32=32 → td_pool+32
; index=2 → 2*32=64 → td_pool+64 (each exactly 32-byte aligned)

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 20

Chapter 9: Key Patterns — Complete Annotated


Walkthrough
9.1 The print_string Function — BIOS INT 10h
print_string() — BIOS character output

print_string: ; Input: ESI = pointer to null-terminated string


push eax
push edx
.loop:
mov al, [esi] ; load next character byte
test al, al ; is it zero? (null terminator)
jz .done ; yes → stop
mov dl, al ; copy char (some BIOSes want it in DL)
mov ah, 0x0E ; BIOS 'teletype output' sub-function
int 0x10 ; BIOS video interrupt
inc esi ; advance pointer to next character
jmp .loop
.done:
pop edx
pop eax
ret

9.2 The LOOP Instruction — Busy-Wait Pattern


LOOP is a single instruction that combines: decrement ECX, then jump if ECX ≠ 0. It is the idiomatic x86 way to
repeat a block a fixed number of times — used extensively in the driver for timing delays and polling loops.

LOOP — delay and polling patterns

; Pattern 1: Fixed delay (don't do useful work)


mov ecx, 100000 ; iteration count
.delay:
loop .delay ; ECX--; jump to .delay if ECX≠0

; Pattern 2: Polling with timeout


mov ecx, 10000
.poll:
in ax, dx ; read hardware register
test ax, BIT_MASK ; check the bit we're waiting for
jz .bit_cleared ; ZF=1 means bit is 0 = done
loop .poll ; not done yet: decrement ECX and retry
; if we reach here: timeout (ECX hit 0 without success)
xor eax, eax
ret ; return failure
.bit_cleared:
mov eax, 1
ret ; return success

9.3 Bit Manipulation — Setting and Testing Hardware Bits

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 21

Bit manipulation patterns used in the USB driver

; ■■ SET a bit (OR mask) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


in ax, dx ; read current register value
or ax, 0x0200 ; set bit 9 (Port Reset)
out dx, ax ; write back

; ■■ CLEAR a bit (AND with inverted mask) ■■■■■■■■■■■■■■■■■■■■


in ax, dx
and ax, ~0x0200 ; NASM computes bitwise NOT at assemble time
out dx, ax ; bit 9 = 0 now

; ■■ TEST a bit (non-destructive AND) ■■■■■■■■■■■■■■■■■■■■■■■■


in ax, dx
test ax, 0x0001 ; AND result discarded, only flags set
jz .no_device ; ZF=1 → bit was 0 → no device
jnz .device_found ; ZF=0 → bit was 1 → device present

; ■■ Build multi-field value with shifts ■■■■■■■■■■■■■■■■■■■■■■


mov eax, USB_PID_SETUP ; 0x2D in bits 7:0
shl ebx, 8 ; device address to bits 15:8
or eax, ebx ; combine
or eax, (7 << 21) ; max_len=7 in bits 27:21
; Result: complete Token dword for a SETUP transaction

9.4 uhci_init_framelist — REP STOSD Pattern


uhci_init_framelist() — REP STOSD bulk fill

uhci_init_framelist:
push ecx
push edi

mov edi, frame_list ; destination = start of frame list


mov ecx, 1024 ; 1024 entries to fill
mov eax, 0x00000001 ; value: Terminate bit set (bit 0 = 1 = invalid/empty)
rep stosd ; store EAX to [EDI], EDI+=4, ECX-- × 1024

; Reset frame number register to 0


movzx edx, word [io_base]
add edx, UHCI_FRNUM ; offset 0x06
xor ax, ax
out dx, ax ; frame counter = 0

pop edi
pop ecx
ret

; REP STOSD in one line does what this C does in a loop:


; for (int i=0; i<1024; i++) frame_list[i] = 0x00000001;
; The Terminate bit (bit 0 = 1) tells the HC: skip this entry.

Chapter 9.5 Full Driver Function Summary


Function Purpose Key Instructions Used Returns

usb_driver_init Top-level init orchestrator CALL, TEST, JZ, OUT 0/−1

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol


USB UHCI Driver — x86 Assembly Tutorial Page 22

Function Purpose Key Instructions Used Returns

Scan PCI bus for UHCI class


pci_find_uhci SHL, OR, OUT, IN, CMP I/O base
0x0C03

usb_reset_controller Two-phase hardware reset OUT, IN, LOOP, TEST 1/0

Fill 1024 frame entries =


uhci_init_framelist REP STOSD, MOV, OUT void
Terminate

uhci_wait_ready Poll HC Halted flag until running IN, TEST, JZ, LOOP 1/0

usb_check_ports Read PORTSC1/2, detect devices IN, TEST, CALL void

uhci_reset_port Assert/deassert Port Reset bit IN, OR, AND, OUT, LOOP void

GET_DESC → SET_ADDR →
usb_enumerate_device MOV, CALL, TEST 1/0
GET_DESC

Build+execute SETUP+STATUS
uhci_control_transfer CALL, SHL, OR, MOV 1/0
TDs

alloc_td Pool allocator — next 32-byte TD SHL, ADD, REP STOSD TD ptr

print_string BIOS teletype output loop MOV, TEST, JZ, INT void
Table 9.1 — Complete function reference

What to Study Next


1) OSDev Wiki ([Link]) — UHCI, USB, PCI articles. 2) Intel IA-32
Software Developer's Manual — the ultimate instruction reference. 3) USB 2.0
Specification ([Link]) — full protocol details. 4) NASM Manual ([Link]) —
complete assembler syntax. 5) Write a keyboard or mouse driver next — they
use USB HID over interrupt transfers.

© Educational Material — x86 NASM Assembly & USB/UHCI Protocol

You might also like