//----------------------------------------------------------------- // ehciboot.s // // The purpose of this program is to show us what is going on // with the EHCI controller(s) at boot time (i.e., before any // operating system has been launched). From its dynamically // updated display of each controller's operational registers // we can see which ports have USB devices attached, assuming // the ROM-BIOS startup code has initialized the controllers. // (Press the -key to terminate the dynamic display.) // // to assemble: $ as ehciboot.s -o ehciboot.o // and to link: $ ld ehciboot.o -T ldscript -o ehciboot.b // and install: $ dd if=ehciboot.b of=/dev/sda4 seek=1 // // Reference: See "EHCI Controller Registers (Chapter 17)", // of "Intel I/O Controller Hub 9 (ICH9) Family Datasheet". // // NOTE: This code begins execution with CS:IP = 1000:0002. // // programmer: ALLAN CRUSE // written on: 04 MAR 2010 // revised on: 29 JUL 2009 -- to improve the screen-display //----------------------------------------------------------------- .equ CLASS_EHCI, 0x0C0320 # PCI Class-Code for EHCI .equ MAX_COUNT, 3 # maximum supported hosts .section .text #------------------------------------------------------------------ .short 0xABCD # application's signature #------------------------------------------------------------------ main: .code16 # for real-mode execution mov %sp, %cs:ipltos+0 # save loader's SP value mov %ss, %cs:ipltos+2 # save loader's SS value mov %cs, %ax # address program arena mov %ax, %ds # using DS register mov %ax, %es # and ES register mov %ax, %ss # and SS register lea tos, %sp # establish local stack call erase_vga_display call detect_ehci_hosts call wait_for_keypress call enable_4GB_access call dynamic_host_view call wait_for_keypress lss %cs:ipltos, %sp # recover loader's stack lret # and exit to the loader #------------------------------------------------------------------ ipltos: .short 0, 0 # holds loader's SP and SS #------------------------------------------------------------------ #------------------------------------------------------------------ progID: .ascii " ehciinit 7/29/2010 " # aids in file recognition #------------------------------------------------------------------ #------------------------------------------------------------------ #---- Helper Routines for Input/Output and Numeric Conversions ---- #------------------------------------------------------------------ wait_for_keypress: mov $0x00, %ah # get keyboard input int $0x16 # invoke BIOS service ret #------------------------------------------------------------------ erase_vga_display: mov $0x0003, %ax # set standard 80x25 text int $0x10 # invoke BIOS service mov $0x1111, %ax # use 8x14 character-font xor %bl, %bl # vga block-number zero int $0x10 # invoke BIOS service ret #------------------------------------------------------------------ draw_message_text: pushal # save caller's registers push %cx # save message count push %bx # and attribute byte mov $0x0F, %ah # get_display_state int $0x10 # invoke BIOS service mov $0x03, %ah # get_cursor_location int $0x10 # invoke BIOS service pop %cx # recover attribute byte mov %cl, %bl # and place it into BL pop %cx # recover message count mov $0x1301, %ax # write_string function int $0x10 # invoke BIOS service popal # restore saved registers ret #------------------------------------------------------------------ hide_video_cursor: mov $0x03, %ah # get_cuursor_size int $0x10 # invoke BIOS service or $0x20, %ch # set bit #5 in CH mov $0x01, %ah # set_cursor_size int $0x10 # invoke BIOS service ret #------------------------------------------------------------------ show_video_cursor: mov $0x03, %ah # get_cuursor_size int $0x10 # invoke BIOS service and $0xDF, %ch # clear bit #5 in CH mov $0x01, %ah # set_cursor_size int $0x10 # invoke BIOS service ret #------------------------------------------------------------------ place: .byte 0, 0, 0 # for column, row, page #------------------------------------------------------------------ save_cursor_place: mov $0x0F, %ah # get_display_state int $0x10 # invoke BIOS service mov $0x03, %ah # get_cursor_position int $0x10 # invoke BIOS service mov %dx, place+0 # save column and row mov %bh, place+2 # and the page-number ret #------------------------------------------------------------------ load_cursor_place: mov place+0, %dx # load column and row mov place+2, %bh # and the page-number mov $0x02, %ah # set_cursor_position int $0x10 # invoke BIOS service ret #------------------------------------------------------------------ ten: .int 10 # radix for decimal system #----------------------------------------------------------------- cvtud: # convert EAX to unsigned decimal digit-string at DS:EDI pushal xor %ecx, %ecx # initialize digit-count nxdiv: xor %edx, %edx # extend EAX for division divl ten # divide by decimal radix push %edx # preserve the remainder inc %ecx # and increment counter or %eax, %eax # test: was quotient zero? jnz nxdiv # no, do another division nxdgt: pop %edx # else recover remainder or $'0', %dl # convert binary to ascii mov %dl, (%edi) # store numeral in buffer inc %edi # advance buffer pointer loop nxdgt # again for next remainder popal ret #------------------------------------------------------------------ hex: .ascii "0123456789ABCDEF" # list of the hex numerals #------------------------------------------------------------------ eax2hex: # convert EAX to hexadecimal digit-string at DS:EDI pushal mov $8, %ecx # number of nybbles nxnyb: rol $4, %eax # next nybble into AL mov %al, %bl # copy nybble into BL and $0xF, %bx # extend nybble to BX mov hex(%bx), %dl # lookup its numeral mov %dl, (%edi) # store numeral in buffer inc %edi # advance buffer pointer loop nxnyb # again for next nybble popal ret #------------------------------------------------------------------ tmp: .ascii "xxxxxxxx" # temporary output buffer #------------------------------------------------------------------ ax2hex: # convert AX to a hexaddecimal digit-string at DS:EDI push %edx # save scratch register push %edi # and destination address lea tmp, %edi # point to temporary buffer call eax2hex # convert EAX to hex-string pop %edi # recover saved destination mov tmp+4, %edx # copy lowest four numerals mov %edx, (%edi) # into the caller's buffer pop %edx # restore scratch register ret #------------------------------------------------------------------ al2hex: # convert AL to a hexaddecimal digit-string at DS:EDI push %edx # save scratch register push %edi # and destination address lea tmp, %edi # point to temporary buffer call eax2hex # convert EAX to hex-string pop %edi # recover saved destination mov tmp+6, %dx # copy lowest two numerals mov %dx, (%edi) # into the caller's buffer pop %edx # restore scratch register ret #------------------------------------------------------------------ #================================================================== #------------------------------------------------------------------ # This table describes the boot-time mapping of IRQs to vectors irqmap: .byte 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F .byte 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77 #------------------------------------------------------------------ n_hubs: .long 0 # number of visible hosts busdev: .word 0, 0, 0 # PCI bus/device/function mfg_id: .word 0, 0, 0 # controller's 'vendorID' dev_id: .word 0, 0, 0 # controller's 'deviceID' mmbase: .quad 0, 0, 0 # registers' base-address irq_id: .byte 0, 0, 0 # controller's IRQ-number int_id: .byte 0, 0, 0 # interrupt vector-number #------------------------------------------------------------------ msg0: .ascii "\r\nNumber of visible EHCI Controllers = " buf0: .ascii " \r\n" len0: .short . - msg0 # length of message text att0: .byte 0x07 # text-display attribute #------------------------------------------------------------------ msg1: .ascii " #" hub$: .ascii " bus=" bus$: .ascii "x dev=" dev$: .ascii "x fnc=" fnc$: .ascii "x Mfg=" vid$: .ascii "xxxx Dev=" did$: .ascii "xxxx MMIO=" iob$: .ascii "xxxxxxxx IRQ=0x" irq$: .ascii "xx INT=0x" int$: .ascii "xx\r\n" len1: .short . - msg1 # length of message text att1: .byte 0x07 # text-display attribute #------------------------------------------------------------------ detect_ehci_hosts: xor %esi, %esi # initialize array-index nxhub: mov $0xB103, %ax # pci_get_class function mov $CLASS_EHCI, %ecx # specify the class-code int $0x1A # invoke BIOS service cmp $0x86, %ah # test: device was found? je findx # no, search is finishd mov %bx, busdev(, %esi, 2) # store bus-address into mov $0xB109, %ax # pci_read_config_word mov $0x00, %edi # register-offset of VenID int $0x1A # invoke BIOS service mov %cx, mfg_id(, %esi, 2) # store the PCI Deviced-ID mov $0xB109, %ax # pci_read_config_word mov $0x02, %edi # register-offset of DevID int $0x1A # invoke BIOS service mov %cx, dev_id(, %esi, 2) # store the PCI Deviced-ID mov $0xB10A, %ax # pci_read_config_dword mov $0x10, %edi # register-offset of BAR0 int $0x1A # invoke BIOS service mov %ecx, mmbase(, %esi, 8) # store the Base-Address mov $0xB108, %ax # pci_read_config_byte mov $0x3C, %edi # register-offset of IRQ_LN int $0x1A # invoke BIOS service mov %cl, irq_id(, %esi, 1) # store the IRQ number movzx %cl, %ecx # extend IRQ to 32-bits mov irqmap(%ecx), %dl # lookup its interrupt ID mov %dl, int_id(, %esi, 1) # store the vector number inc %esi # advance search index cmp $MAX_COUNT, %esi # maximum reached yet? jb nxhub findx: mov %esi, n_hubs # save the hub count mov n_hubs, %eax # number of hubs found lea buf0, %edi # point to output field call cvtud # convert to digit-string lea msg0, %bp # offset of text-message mov len0, %cx # length of text-message mov att0, %bl # text's color-attribute call draw_message_text # write message onscreen # sort hubs' records in order of their 'busdev' fields # for conformity with the numbering scheme used by Linux bubb0: mov $0, %esi # ESI = 1-st array-index mov $1, %edi # EDI = 2-nd array-index bubb1: cmp n_hubs, %edi # index still within bounds? jae bubb3 # no, compares are done mov busdev(, %esi, 2), %cx # fetch 'busdev' value mov busdev(, %edi, 2), %dx # fetch 'busdev' value cmp %cx, %dx # latter value is larger? jae bubb2 # yes, retain this order # swap all the fields, then start over mov busdev(, %esi, 2), %cx mov busdev(, %edi, 2), %dx mov %dx, busdev(, %esi, 2) mov %cx, busdev(, %edi, 2) mov mfg_id(, %esi, 2), %cx mov mfg_id(, %edi, 2), %dx mov %dx, mfg_id(, %esi, 2) mov %cx, mfg_id(, %edi, 2) mov dev_id(, %esi, 2), %cx mov dev_id(, %edi, 2), %dx mov %dx, dev_id(, %esi, 2) mov %cx, dev_id(, %edi, 2) mov mmbase(, %esi, 8), %ecx mov mmbase(, %edi, 8), %edx mov %edx, mmbase(, %esi, 8) mov %ecx, mmbase(, %edi, 8) mov irq_id(, %esi, 1), %cl mov irq_id(, %edi, 1), %dl mov %dl, irq_id(, %esi, 1) mov %cl, irq_id(, %edi, 1) mov int_id(, %esi, 1), %cl mov int_id(, %edi, 1), %dl mov %dl, int_id(, %esi, 1) mov %cl, int_id(, %edi, 1) jmp bubb0 # start again from the beginning bubb2: inc %esi # advance earlier index inc %edi # and successor index jmp bubb1 bubb3: # loop to display each OHCI controller's parameters xor %esi, %esi # initialize array-index nxmsg: # format parameters for display mov %esi, %eax inc %eax lea hub$, %edi call cvtud mov mfg_id(, %esi, 2), %ax lea vid$, %edi call ax2hex mov dev_id(, %esi, 2), %ax lea did$, %edi call ax2hex mov busdev(, %esi, 2), %ax shr $8, %ax and $0xFF, %eax lea bus$, %edi call cvtud mov busdev(, %esi, 2), %ax shr $3, %ax and $0x1F, %eax lea dev$, %edi movb $' ', 1(%edi) call cvtud mov busdev(, %esi, 2), %ax shr $0, %ax and $0x07, %eax lea fnc$, %edi call cvtud mov mmbase(, %esi, 8), %eax lea iob$, %edi call eax2hex mov irq_id(, %esi, 1), %al lea irq$, %edi call al2hex mov int_id(, %esi, 1), %al lea int$, %edi call al2hex lea msg1, %bp # offset of text-message mov len1, %cx # length of text-message mov att1, %bl # text's color-attribute call draw_message_text # write message onscreen inc %esi # increment array-index cmp n_hubs, %esi # still within range? jb nxmsg # yes, show next message ret #------------------------------------------------------------------ #================================================================== #------------------------------------------------------------------ theGDT: .quad 0, 0x008F92000000FFFF # Global Descriptor Table .equ limGDT, (.-theGDT)-1 # out GDT's segment-limit regGDT: .short limGDT, theGDT, 0x0001 # image for register GDTR #------------------------------------------------------------------ enable_4GB_access: lgdt %cs:regGDT # initialize register GDTR cli # do not accept interrupts mov %cr0, %eax # current 'machine status' bts $0, %eax # set image of the PE-bit mov %eax, %cr0 # enter protected mode mov $0x0008, %ax # selector for 4GB-data mov %ax, %fs # into FS register mov %ax, %gs # also GS register mov %cr0, %eax # current 'machine status' btr $0, %eax # reset image of PE-bit mov %eax, %cr0 # leave protected mode sti # interrupts allowed now in $0x92, %al # input system control or $0x02, %al # fast A20-line enable out %al, $0x92 # disables memory wrap ret #------------------------------------------------------------------ #================================================================== #------------------------------------------------------------------ dynamic_host_view: call save_cursor_place # remember cursor location call hide_video_cursor # make movements invisible nxview: call load_cursor_place # draw from saved location call display_hubs_view # show controllers' state # check for ,ESCAPE>-key mov $0x01, %ah # peek at keyboard queue int $0x16 # invoke BIOS service jz nxview # nothing new? continue mov $0x00, %ah # else get keyboard data int $0x16 # invoke BIOS service cmp $0x1B, %al # was pressed? je fini # yes, viewing finished jmp nxview # else continue viewing fini: call show_video_cursor ret #------------------------------------------------------------------ display_hubs_view: xor %esi, %esi nxhost: call exhibit_host_state inc %esi cmp n_hubs, %esi jb nxhost ret #------------------------------------------------------------------ msg2: .ascii "\r\n" .ascii " USB Enhanced Host Controller #" hcn$: .ascii "x " .ascii " vendorID=0x" vid$$: .ascii "xxxx " .ascii " deviceID=0x" did$$: .ascii "xxxx" .ascii "\r\n 0x00: " buf2a: .ascii "xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx " .ascii "xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx " .ascii "\r\n 0x20: " buf2b: .ascii "xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx " .ascii "xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx " .ascii "\r\n 0x40: " buf2c: .ascii "xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx " .ascii "xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx " .ascii "\r\n 0x60: " buf2d: .ascii "xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx " .ascii "xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx " .ascii "\r\n 0x80: " buf2e: .ascii "xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx " .ascii "xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx " .ascii "\r\n" len2: .short . - msg2 att2: .byte 0x07 #------------------------------------------------------------------ exhibit_host_state: # # EXPECTS: ESI = hub-numbr # push %esi mov %esi, %eax inc %eax lea hcn$, %edi call cvtud mov mfg_id(, %esi, 2), %ax lea vid$$, %edi call ax2hex mov dev_id(, %esi, 2), %ax lea did$$, %edi call ax2hex lfs mmbase(, %esi, 8), %ebx # point FS:EBX to regs lea buf2a, %edi mov $8, %ecx b2a: mov %fs:(%ebx), %eax call eax2hex add $9, %edi add $4, %ebx loop b2a lea buf2b, %edi mov $8, %ecx b2b: mov %fs:(%ebx), %eax call eax2hex add $9, %edi add $4, %ebx loop b2b lea buf2c, %edi mov $8, %ecx b2c: mov %fs:(%ebx), %eax call eax2hex add $9, %edi add $4, %ebx loop b2c lea buf2d, %edi mov $8, %ecx b2d: mov %fs:(%ebx), %eax call eax2hex add $9, %edi add $4, %ebx loop b2d lea buf2e, %edi mov $8, %ecx b2e: mov %fs:(%ebx), %eax call eax2hex add $9, %edi add $4, %ebx loop b2e lea msg2, %bp # offset of text-message mov len2, %cx # length of text-message mov att2, %bl # text's color-attribute call draw_message_text # write message onscreen pop %esi ret #------------------------------------------------------------------ .align 16 # insures word alignment .space 512 # space for stack to use tos: # label for top-of-stack #------------------------------------------------------------------ .end