	page	240, 132
;ENTRY.ASM	31-Aug-2009	Boreal		loren.blaney@idcomm.com
;Hugi Compo 28: USB
;This displays the descriptor of a USB UHCI device. It finds the UHCI
; controller on the PCI bus, enables the PCI, and gets the base address. It
; sets up the UHCI controller, a stack frame, and gets and displays the
; descriptor of an attached device.
;
;Assemble with:
; tasm
; tlink /t

	.model	tiny
	.code
	.386
	org	100h
start:

; Find the UHCI controller on the PCI bus. Using ports 0CF8h and 0CFCh, we can
; read and write the PCI bus. First write to the 0CF8h port using these bits:
;   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
; Read in a dword at a time, with bits 7:2 detailing which dword offset to read.
; Assume there are at least 2 buses, 32 devices per bus, and 8 functions per
; device.

COMMAND    equ  0		; offsets to UHCI registers
STATUS     equ  2
INTERRUPT  equ  4
FRAME_NUM  equ  6
FRAME_ADDY equ  8
SOF        equ 12
PORT0      equ 16
PORT1	   equ 18

;(bx=0)
;bh = bus = 0, bl = device & function = 0
;top of stack = io_base for UHCI

; read in the ID word (first word in config space)
	dec	bx		; so we start with 0
pci02:	inc	bx		; next function, device, bus
;	mov	ch, 0		; first offset we want is zero
;	call	Read		; read in the word
;	inc	ax		; if value is 0FFFFh, then there is no device
;	je	pci02		;  at this location

	mov	ch, 8		; read in class, subclass and proto
	call	Read
	mov	al, 0
	cmp	eax, 0C030000h
	jne	pci02

; We found a UHCI device, so get the I/O base address.
; Write 0005h to the PCI status register to allow access.
; Also write 8F00h to the Legacy register in the config space.
	mov	ch, 20h		; base4 is at offset 32
	call	Read
	and	al, 0FCh	; clear out the last 2 bits
	push	ax		; save the I/O base port address on the stack

	mov	ax, 0005h	; write 0005h to the access register
	mov	ch, 04h		; at offset 04
	call	Write

;	mov	ax, 8F00h	; write 8F00h to the legacy register
;	mov	ch, 0C0h	; at offset C0h
;	call	Write

; Reset the controller by setting bit 2 (GRESET) in the command register
;  then waiting at least 10ms before reseting the bit
	pop	dx		; io_base + COMMAND
	push	dx

	mov	ax, (1 shl 2)	; bit 2 (GRESET)
	out	dx, ax
	call	delay55ms
	xor	eax, eax
	out	dx, ax

; Set up a frame list aligned on a 4096- (1000h-) byte boundary
; (bp=09xx)
	mov	ax, ds		; align to 100h in our segment
	add	ax, bp		; + 09 = plenty of room for our code
	mov	al, 0
	mov	fs, ax		; fs = frame list segment address
	mov	es, ax
	shl	eax, 4		; <<

	add	dx, FRAME_ADDY
	out	dx, eax

; Mark each frame as 'T'erminated
	xor	di, di
	xor	eax, eax	; eax:= 1
	inc	ax
	mov	cx, 1024
	rep stosd		; es:[di++]:= eax

; Set Interrupt On Complete (IOC) enable bit
; *We must set all four bits to get Bochs to work*
	pop	dx		; io_base
	push	dx
	add	dx, INTERRUPT
	mov	ax, 000Fh
	out	dx, ax

; Start the UHCI controller
	pop	dx		; io_base + COMMAND
	push	dx
	mov	al, 01h		; ax = 0001h
	out	dx, ax

; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Find a port with the ConnectChange bit set, clear it, see if a connection is
;  made, and get the descriptor.
; bp = port number offset = 10h or 12h

	mov	bp, 10h		; Port 1 status/control registr = 10h
main_loop: 			; set up the address in dx
	pop	dx		; io_base
	push	dx
	add	dx, bp		; add port

	in	ax, dx		; test the connect status change bit
	test	al, (1 shl 1)
	je	l90x		; jump if no device attached

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

	in	ax, dx		; test the connect status bit
	test	al, (1 shl 0)
l90x:	je	l90		; jump if no device attached

; There is a connection. Reset and send the packet. First reset the port.
	mov	ax, (1 shl 9)	; reset
	out	dx, ax
	call	delay55ms
	in	ax, dx		; clear the reset
	and	ah, 0FDh
	out	dx, ax
	call	delay55ms

	in	ax, dx		; enable the device
	or	al, (1 shl 2)
	out	dx, ax

; We may assume an LS device	; C_ERR or LS or STATUS
	mov	ecx, (3 shl 27) or (1 shl 26) or (80h shl 16)

; Set up our frame Transfer Descriptors (TDs)
	xor	edx, edx	; get our code segment address
	mov	dx, cs		;  converted to a 32-bit address
	shl	edx, 4		; <<

	push	ds
	pop	es
	mov	di, offset Setup_TD

; Setup_TD
	lea	eax, [edx+TD0]	; Setup_TD:= physical address of TD0
	stosd			; es:[di++]:= eax
	mov	eax, ecx	; Setup_TD+4:= C_ERR or LS or STATUS
	stosd
	mov	eax, (7 shl 21) or (0 shl 19) or 2Dh	; Len = 8, Data0, Setup
	stosd			; Setup_TD+8:= Len = 8, Data0, Setup
	lea	eax, [edx+Setup_Packet]
	stosd			; Setup_TD+12:= physical addr of Setup_Packet
	add	di, 16
; TD0
	lea	eax, [edx+Status_TD]	; TD0:= physical addr of Status_TD
	stosd			; es:[di++]:= eax
	mov	eax, ecx	; TD0+4:= C_ERR or LS or STATUS
	stosd
	mov	eax, (17 shl 21) or (1 shl 19) or 69h
	stosd			; TD0+8:= Len = 18, Data1, In
	lea	eax, [edx+Device_Descriptor]
	stosd			; TD0+12:= physical addr of Device_Descriptor
	add	di, 16
; Status_TD
	xor	eax, eax	; terminate
	inc	ax
	stosd			; Status_TD:= 00000001h
	mov	Queue, eax	; Queue:= 00000001h
	mov	eax, ecx	; Status_TD+4:= IOC or C_ERR or LS or STATUS
	or	eax, (1 shl 24)	; IOC
	stosd
	mov	eax, (7FFh shl 21) or (1 shl 19) or 0E1h
	stosd			; Status_TD+8:= Len = 0, Data1, Out

; Set up the Queue Head. The first dword is called the HORZontal pointer.
;  It points to another Queue or maybe to a TD. The second dword is the
;  VERTical pointer. It points to a set of TDs 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 into 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.

	lea	eax, [edx+Setup_TD]
	mov	Queue+4, eax

; Point to the first frame in the list to our queue
	add	edx, offset Queue	; edx still = 'base address'
	or	edx, (1 shl 1)		; is a queue
	mov	fs:[0], edx

; Wait for the interrupt to occur
	pop	dx		; io_base + STATUS
	push	dx
	inc	dx
	inc	dx
l30:	call	delay55ms
	in	ax, dx
	test	al, 1
	je	l30

; Mark the frame pointer as terminated
	xor	eax, eax	; eax:= 00000001h
	inc	ax
	mov	fs:[0], eax

; Display the 18 descriptor bytes
	mov	si, offset Device_Descriptor
	xor	dx, dx
l40:	test	dl, 0Fh
	jne	l50

	mov	al, 0Dh		; CR
	int	29h
	mov	al, 0Ah		; LF
	jmp	l60
l50:
	mov	al, ' '
	cmp	dl, 7
	jne	l60
	 mov	al, '-'
l60:	int	29h

	lodsb			; al:= ds:[si++]
	mov	cx, 2
l65:	rol	al, 4
	push	ax
	and	al, 00Fh
	daa
	add	al, 0F0h
	adc	al, 40h
	int	29h
	pop	ax
	loop	l65

	inc	dx
	cmp	dl, 18
	jne	l40

; Loop until we find a connect status change bit set again.
; Move to next port and loop.
l90:
	add	bp, 0002h	; port
	cmp	bp, 0012h
	jbe	main_loop

; Stop the controller
	pop	dx		; io_base + COMMAND
	xor	ax, ax
	out	dx, ax
	ret

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; Wait for the BIOS time_stamp to increment then wait for it again. This will
; delay at least 1/18.2 of a second, or 55ms, and at most 1/9.1 of a second, or
; 109ms.
;
delay55ms:
	call	delay

delay:	push	ds
	push	40h
	pop	ds

	mov	al, ds:[6Ch]	; wait the first time for the actual tick
del10:	cmp	al, ds:[6Ch]
	je	del10

	pop	ds
	ret

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; Read from the PCI config space
; On entry:
;  bh = bus
;  bl = device & function
;  ch = offset in bytes (0, 1, 2, ,255)
; On return:
; eax = read value
;  ch and dx are altered
;
Read:	mov	ah, 80h
	mov	al, bh
	shl	eax, 16
	mov	ah, bl
	mov	al, ch
	and	al, 0FCh

	mov	dx, 0CF8h
	out	dx, eax

	and	ch, 03h		; dx = 0CFCh + last 2 bits of offset
	add	ch, 4
	add	dl, ch

	in	eax, dx
	ret

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; Write to the PCI config space
; On entry:
;  bh = bus
;  bl = device & function
;  ch = offset in bytes (0, 1, 2, ,255)
;  ax = value to write
;  dx = destroyed
; On return:
;  ch and dx are altered
;
Write:	push	ax
	call	Read
	pop	ax
	out	dx, eax
	ret

;-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

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


; must be paragraph aligned
	org	320h

Queue	dd	?
	dd	?


; must be paragraph aligned
	org	330h

Setup_TD dd	?
	dd	?
	dd	?
	dd	?
	dd	4 dup(?)

TD0	dd	?
	dd	?
	dd	?
	dd	?
	dd	4 dup(?)

Status_TD dd	?
	dd	?
	dd	?
	dd	?

Device_Descriptor	db	18 dup (?)
	end	start
