/*---------------------------------------------------------------------------- UHCI USB demo code for DOS. Can read USB Flash memory devices. Chris Giese http://SisAndHappy.com/ChrisGiese/ I, the copyright holder of this work, hereby release it into the public domain. This applies worldwide. If this is not legally possible: I grant any entity the right to use this work for any purpose, without any conditions, unless such conditions are required by law. Builds with Turbo C, Watcom C, or DJGPP. 13 June 2019: - STALLing in usb_load_string() because I was reading beyond the end of the strings; fixed 30 May 2017: initial release To do: - Get USB keyboard and mouse working -- attach a suitable TD to one of the interrupt queue heads and LEAVE it there until the device is unplugged. Foreground task can poll the TD to implement kbhit() and getch() - Abstract the SCSI code shared with ATAPI - Execution of SCSI commands in this file is handled by: int usb_scsi_read(const usb_dev_t *d, const char pkt[16], void *data, unsigned data_len); In ATADISK.C it's handled by: int atapi_read_pio(const atadisk_t *ata, const char *pkt, void *buf0, unsigned want); Replace both "const usb_dev_t *d" and "const atadisk_t *ata" with "const void *state" - The SCSI functions must also then carry around a pointer to either usb_scsi_read() or atapi_read_pio(): int (*execute)(void *state, const char pkt[16], void *data, unsigned data_len); - SCSI RBC (Reduced Block Commands) set: READ(10) 0x28 WRITE(10) 0x2A READ CAPACITY 0x25 SYNCHRONIZE CACHE 0x35 START STOP UNIT 0x1B VERIFY(10) 0x2F MODE SENSE 0x5A MODE SELECT 0x55 INQUIRY 0x12 TEST UNIT READY 0x00 WRITE BUFFER 0x3B (used to download microcode) (no REQUEST SENSE?) - USB disk error recovery: (1) a Bulk-Only Mass Storage Reset: a class-specific (type=1) control transfer with bmReqType=00100001b, bRequest=255 (RESET), wValue=0, wIndex=interface_number, wLength=0 (2) a Clear Feature HALT to the Bulk-In Endpoint: a standard (type=0) control transfer with bmReqType=00000010b, bRequest=1 (CLEAR_FEATURE), wValue=0 (ENDPOINT_HALT), wIndex=endpoint_number, wLength=0 (3) a Clear Feature HALT to the Bulk-Out Endpoint Zero usb_endp_t.data_toggle after endpoint error recovery ----------------------------------------------------------------------------*/ #include /* printf(), putchar() */ #include /* FP_SEG(), FP_OFF() */ #if 0 #include #else typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned long uint32_t; #endif #if 0 #define DEBUG(X) X #else #define DEBUG(X) /* nothing */ #endif #define ERR_NO_MEM -3 /* heap memory exhausted; malloc() failed */ /* programmer/software errors */ #define ERR_BAD_ARG -8 /* invalid argument to function */ #define ERR_SOFTWARE -11 /* various hardware errors */ #define ERR_HARDWARE -13 #define COUNT(N) (sizeof(N) / sizeof((N)[0])) #if defined(__TURBOC__) #define inportw(P) inport(P) #define outportw(P,V) outport(P,V) #define PTR2LINEAR(P) (FP_SEG(P) * 16uL + FP_OFF(P)) #define LINEAR2NEAR(N) (void *)((N) - _DS * 16uL) #elif defined(__WATCOMC__) && !defined(__386__) #include #define inportb(P) inp(P) #define outportb(P,V) outp(P,V) #define inportw(P) inpw(P) #define outportw(P,V) outpw(P,V) #define PTR2LINEAR(P) (FP_SEG(P) * 16uL + FP_OFF(P)) /* #define LINEAR2NEAR(N) (void *)((N) - _DS * 16uL) */ void *LINEAR2NEAR(uint32_t N) { static char dummy; return (void *)((N) - FP_SEG(&dummy) * 16uL); } #elif defined(__WATCOMC__) && defined(__386__) #include #define inportb(P) inp(P) #define outportb(P,V) outp(P,V) #define inportw(P) inpw(P) #define outportw(P,V) outpw(P,V) #define inportl(P) inpd(P) #define outportl(P,V) outpd(P,V) #define PTR2LINEAR(P) (uint32_t)(P) #define LINEAR2NEAR(N) (void *)(N) #elif defined(__DJGPP__) #include /* __djgpp_conventional_base */ #define __386__ 1 #define PTR2LINEAR(P) ((uint32_t)(P) - __djgpp_conventional_base) #define LINEAR2NEAR(L) (void *)((L) + __djgpp_conventional_base) #else #error Sorry, unsupported compiler #endif /* these macros assume little-endian CPU such as x86: */ #define read_le16(P) *(uint16_t *)(P) #define read_le32(P) *(uint32_t *)(P) #define write_le16(P,V) *(uint16_t *)(P) = (V) #define write_le32(P,V) *(uint32_t *)(P) = (V) /***************************************************************************** these functions should work regardless of CPU byte order *****************************************************************************/ unsigned read_be16(const void *buf0) { const uint8_t *buf = buf0; unsigned rv; rv = buf[0]; /* MSB */ rv <<= 8; rv |= buf[1]; /* LSB */ return rv; } /****************************************************************************/ uint32_t read_be32(const void *buf0) { const uint8_t *buf = buf0; uint32_t rv; rv = buf[0]; /* MSB */ rv <<= 8; rv |= buf[1]; rv <<= 8; rv |= buf[2]; rv <<= 8; rv |= buf[3]; /* LSB */ return rv; } /****************************************************************************/ void write_be16(void *buf0, unsigned val) { uint8_t *buf = buf0; buf[1] = val; val >>= 8; buf[0] = val; } /****************************************************************************/ void write_be32(void *buf0, uint32_t val) { uint8_t *buf = buf0; buf[3] = val; val >>= 8; buf[2] = val; val >>= 8; buf[1] = val; val >>= 8; buf[0] = val; } /****************************************************************************/ #define BPERL 16 /* byte/line for dump */ void dump(const void *data0, unsigned count) { const uint8_t *data = data0; unsigned j, k; while(count != 0) { k = (count < BPERL) ? count : BPERL; for(j = 0; j < k; j++) printf("%02X ", data[j]); printf("\t"); for(j = 0; j < k; j++) putchar((data[j] < ' ') ? '_' : data[j]); printf("\n"); data = &data[k]; count -= k; } } /***************************************************************************** These functions are _code_, but are stored in the _data_ segment. Declare them 'far' and end them with 'retf' instead of 'ret' For 16-bit Watcom C, use 'cdecl' to force usage of normal, stack calling convention instead of Watcom register calling convention. *****************************************************************************/ #if defined(__TURBOC__) || (defined(__WATCOMC__) && !defined(__386__)) static const uint8_t g_inportl[] = { /* BITS 16 */ 0x55, /* push bp */ 0x8B, 0xEC, /* mov bp,sp */ 0x8B, 0x56, 0x06, /* mov dx,[bp + 6] */ 0x66, 0xED, /* in eax,dx */ 0x8B, 0xD0, /* mov dx,ax */ 0x66, 0xC1, 0xE8, 0x10, /* shr eax,16 */ 0x92, /* xchg dx,ax */ 0x5D, /* pop bp */ 0xCB /* retf */ }; uint32_t far cdecl (*inportl)(unsigned port) = (uint32_t far (*)(unsigned))g_inportl; /****************************************************************************/ static const uint8_t g_outportl[] = { /* BITS 16 */ 0x55, /* push bp */ 0x8B, 0xEC, /* mov bp,sp */ 0x8B, 0x56, 0x06, /* mov dx,[bp + 6] */ 0x66, 0x8B, 0x46, 0x08, /* mov eax,[bp + 8] */ 0x66, 0xEF, /* out dx,eax */ 0x5D, /* pop bp */ 0xCB /* retf */ }; void far cdecl (*outportl)(unsigned port, uint32_t val) = (void far (*)(unsigned, uint32_t))g_outportl; #endif /***************************************************************************** Memory compatible with PCI DMA (bus-mastering) must be: - physically present; not paged or swapped to disk, and - identity-mapped (virtual address == physical address), and - physically contiguous (else you need scatter-gather). (For 16-bit ISA DMA, the memory must also be below 16 MB.) DOS conventional memory (below 1 MB) satisfies all these requirements, for both real mode and under DPMI. *****************************************************************************/ #if defined(__TURBOC__) || (defined(__WATCOMC__) && !defined(__386__)) #include /* free(), malloc() */ /* for 16-bit DOS compilers, dma_mem_t handle is a near pointer: */ typedef void * dma_mem_t; /****************************************************************************/ void free_dma_mem(dma_mem_t handle) { if(handle != NULL) free(handle); } /****************************************************************************/ void *alloc_dma_mem(dma_mem_t *handle_ptr, unsigned size, unsigned align) { uint32_t linear; void *h; if(size == 0 || align == 0) return NULL; /* ERR_BAD_ARG */ if((align & (align - 1)) != 0) /* 'align' must be a power of 2 */ return NULL; /* ERR_BAD_ARG */ size += (align - 1); if((h = malloc(size)) == NULL) /* allocate conventional memory */ return NULL; /* ERR_NO_MEM */ linear = PTR2LINEAR(h); if(align > 1) /* align */ linear = (linear + align - 1) & -(uint32_t)align; *handle_ptr = h; return LINEAR2NEAR(linear); } #else #if defined(__DJGPP__) #include #else #include /* memset() */ #include /* union REGS, int386() */ /****************************************************************************/ static int __dpmi_allocate_dos_memory(unsigned paragraphs, int *sel) { union REGS regs; memset(®s, 0, sizeof(regs)); regs.w.ax = 0x0100; regs.w.bx = paragraphs; int386(0x31, ®s, ®s); *sel = regs.w.dx; return regs.w.cflag ? -1 : regs.w.ax; } /****************************************************************************/ static int __dpmi_free_dos_memory(int sel) { union REGS regs; memset(®s, 0, sizeof(regs)); regs.w.ax = 0x0101; regs.w.dx = sel; int386(0x31, ®s, ®s); return regs.w.cflag ? -1 : regs.w.ax; } #endif /* for 32-bit DOS compilers, dma_mem_t handle is a DPMI selector */ typedef unsigned dma_mem_t; /****************************************************************************/ void free_dma_mem(dma_mem_t handle) { __dpmi_free_dos_memory(handle); } /****************************************************************************/ void *alloc_dma_mem(dma_mem_t *handle_ptr, unsigned size, unsigned align) { uint32_t linear; int seg, sel; if(size == 0 || align == 0) return NULL; /* ERR_BAD_ARG */ if((align & (align - 1)) != 0) /* 'align' must be a power of 2 */ return NULL; /* ERR_BAD_ARG */ /* round up to at least 16; can't allocate less than one paragraph here */ size += ((align > 16) ? (align - 1) : 15); if((seg = __dpmi_allocate_dos_memory(size / 16, &sel)) == -1) return NULL; /* ERR_NO_MEM */ linear = seg * 16uL; if(align > 1) /* align */ linear = (linear + align - 1) & -(uint32_t)align; *handle_ptr = sel; return LINEAR2NEAR(linear); } #endif /*---------------------------------------------------------------------------- PCI CODE EXPORTS: int pci_detect(void); unsigned pci_read_byte(unsigned bdf, unsigned reg); unsigned pci_read_word(unsigned bdf, unsigned reg); uint32_t pci_read_dword (unsigned bdf, unsigned reg); void pci_write_byte(unsigned bdf, unsigned reg, unsigned val); void pci_write_word(unsigned bdf, unsigned reg, unsigned val); void pci_write_dword(unsigned bdf, unsigned reg, uint32_t val); int pci_iterate(unsigned *bdf_ptr); ----------------------------------------------------------------------------*/ #include /* [in|out]port[b]() */ #define PCI_ADDR_REG 0xCF8 #define PCI_DATA_REG 0xCFC /***************************************************************************** returns 0 if success, -1 if no PCI *****************************************************************************/ int pci_detect(void) { outportl(PCI_ADDR_REG, 0x80000000L); return (inportl(PCI_ADDR_REG) != 0x80000000L) ? -1 : 0; } /***************************************************************************** 'bdf' = bus (b15-b8), device (b7-b3), and function (b2-b0) *****************************************************************************/ unsigned pci_read_byte(unsigned bdf, unsigned reg) { uint32_t j = bdf; j <<= 8; j |= 0x80000000L; /* "enable configuration space mapping" */ j |= (reg & ~3); outportl(PCI_ADDR_REG, j); return inportb(PCI_DATA_REG + (reg & 3)); } /****************************************************************************/ unsigned pci_read_word(unsigned bdf, unsigned reg) { uint32_t j = bdf; j <<= 8; j |= 0x80000000L; j |= (reg & ~3); outportl(PCI_ADDR_REG, j); return inportw(PCI_DATA_REG + (reg & 2)); } /****************************************************************************/ uint32_t pci_read_dword(unsigned bdf, unsigned reg) { uint32_t j = bdf; j <<= 8; j |= 0x80000000L; j |= (reg & ~3); outportl(PCI_ADDR_REG, j); return inportl(PCI_DATA_REG); } /****************************************************************************/ void pci_write_byte(unsigned bdf, unsigned reg, unsigned val) { uint32_t j = bdf; j <<= 8; j |= 0x80000000L; j |= (reg & ~3); outportl(PCI_ADDR_REG, j); outportb(PCI_DATA_REG + (reg & 3), val); } /****************************************************************************/ void pci_write_word(unsigned bdf, unsigned reg, unsigned val) { uint32_t j = bdf; j <<= 8; j |= 0x80000000L; j |= (reg & ~3); outportl(PCI_ADDR_REG, j); outportw(PCI_DATA_REG + (reg & 2), val); } /****************************************************************************/ void pci_write_dword(unsigned bdf, unsigned reg, uint32_t val) { uint32_t j = bdf; j <<= 8; j |= 0x80000000L; j |= (reg & ~3); outportl(PCI_ADDR_REG, j); outportl(PCI_DATA_REG, val); } /***************************************************************************** "Brute-force" iteration. Can check if device is a PCI-PCI bridge (class 6, subclass 4 or 9), read secondary bus number from byte at location 0x19 in config space, and enumerate _that_ bus; instead of merely incrementing through all possible bus numbers. Usage: unsigned bdf = 0; do { // ...whatever... } while(!pci_iterate(&bdf)); This function increments the bdf value and returns +1 if it wraps around to zero. *****************************************************************************/ #define FN(BDF) (((BDF) >> 0) & 0x07) #define DEV(BDF) (((BDF) >> 3) & 0x1F) #define BUS(BDF) (((BDF) >> 8) & 0xFF) int pci_iterate(unsigned *bdf_ptr) { unsigned bdf = *bdf_ptr; if(FN(bdf) == 0 && (pci_read_byte(bdf, 0x0E) & 0x80) == 0) bdf += 8; /* not a multifunction device */ else bdf++; /* multifunction device */ if((bdf &= 0xFFFF) == 0) return 1; *bdf_ptr = bdf; return 0; } /*---------------------------------------------------------------------------- UHCI EXPORTS: int uhci_setup(unsigned addr, uint8_t *data0, unsigned data_len, unsigned max_packet_size, uint8_t setup0[8], int low_speed); int uhci_in_out(unsigned addr, uint8_t *data0, unsigned data_len, int low_speed, usb_endp_t *endp); int uhci_init(void); int uhci_poll(void); ----------------------------------------------------------------------------*/ #include /* printf() */ #include /* kbhit() */ #include /* FP_SEG(), FP_OFF(), [in|out]port[b]() */ #define USB_PID8_OUT 0xE1 #define USB_PID8_IN 0x69 #define USB_PID8_SETUP 0x2D /* I tried bitfields in td_t, but that's also awkward... */ #define TD_DWORD2(PID, ADDR, ENDP, D, MAX_LEN) \ (((uint32_t)(PID) & 0xFF) << 0) | \ (((uint32_t)(ADDR) & 0x7F) << 8) | \ (((uint32_t)(ENDP) & 0x0F) << 15) | \ (((uint32_t)(D) & 0x01) << 19) | \ ((((uint32_t)(MAX_LEN) - 1) & 0x7FF) << 21) /* index into queue head array for each of the 13 queue heads */ #define QH_INT1024_NUM 0 #define QH_INT512_NUM 1 #define QH_INT256_NUM 2 #define QH_INT128_NUM 3 #define QH_INT64_NUM 4 #define QH_INT32_NUM 5 #define QH_INT16_NUM 6 #define QH_INT8_NUM 7 #define QH_INT4_NUM 8 #define QH_INT2_NUM 9 #define QH_INT1_NUM 10 #define QH_CONTROL_NUM 11 #define QH_BULK_NUM 12 #define NUM_QH 13 /* the qh_t structures themselves: */ #define QH_INT1024 g_qh[QH_INT1024_NUM] #define QH_INT512 g_qh[QH_INT512_NUM] #define QH_INT256 g_qh[QH_INT256_NUM] #define QH_INT128 g_qh[QH_INT128_NUM] #define QH_INT64 g_qh[QH_INT64_NUM] #define QH_INT32 g_qh[QH_INT32_NUM] #define QH_INT16 g_qh[QH_INT16_NUM] #define QH_INT8 g_qh[QH_INT8_NUM] #define QH_INT4 g_qh[QH_INT4_NUM] #define QH_INT2 g_qh[QH_INT2_NUM] #define QH_INT1 g_qh[QH_INT1_NUM] #define QH_CONTROL g_qh[QH_CONTROL_NUM] #define QH_BULK g_qh[QH_BULK_NUM] /* USB endpoint info */ typedef struct { uint8_t endp_num; /* b7=IN/!OUT, b3-0=endpoint number */ uint8_t attrib; /* b5-4=usage(?), b3-2=sync(?), b1-0=transfer type: 3=interrupt, 2=bulk, 1=isochronous, 0=control */ unsigned max_packet_size; unsigned polling_interval; /* in 1 ms frames, can poll more often */ unsigned data_toggle : 1; /* <- used by UHCI; not USB */ } usb_endp_t; /* 16-byte UHCI transfer descriptor (TD) */ #pragma pack(1) typedef struct { /* dword #0 */ uint32_t link; /* dword #1 */ uint16_t actual_len; /* b10-b0=(actual data length - 1) */ /* b7=Active, other bits indicate error. I was using bitfields here, but that didn't work with Watcom C. The UHCI hardware does not write any field of a td_t or qh_t except for these; hence 'volatile': */ volatile uint8_t status; /* b7-b6=reserved, b5=Short Packet Detect (=0), b4-3=retry count, b2=TD is for low-speed device, b1=isochronous packet, b0=Interrupt On Complete */ uint8_t flags; /* dword #2. Lots of stuff here; see the TD_DWORD2 macro above: b21-b3=max_len, b20=reserved, b19=DATA1, b18-b15=endpoint, b14-b8=device address, b7-b0=packet ID */ uint32_t dword2; /* dword #3 */ uint32_t buf_ptr; /* linear address */ } td_t; /* 16-byte UHCI queue head (QH) */ typedef struct { /* dword #0: pointer to next horizontal object in schedule b3-b2=reserved, b1=next object is QH, b0=Terminate */ uint32_t h; /* dword #1: pointer to next vertical object in schedule b3-b2=reserved, b1=next object is QH, b0=Terminate */ uint32_t v; /* padding to make the total length 16 bytes */ uint32_t res[2]; } qh_t; static qh_t *g_qh; static unsigned g_io_addr; /****************************************************************************/ static int do_uhci_transfer(td_t *td, unsigned num_tds) { unsigned j, status; int i = 0; /* start the transfer by attaching our chain of TDs to the (xxx) Bulk QH */ QH_BULK.v = PTR2LINEAR(td); /* now poll the TDs as the hardware processes them */ DEBUG( printf("do_uhci_transfer(): processing %u TD(s), press a key " "to abort...\n\t", num_tds);) for(j = 0; j < num_tds; ) { status = td[j].status; /* this changes, so I know the UHCI is running: printf("FRNUM=%4u \r", inportw(g_io_addr + 6)); */ /* press a key to abort if it hangs... */ DEBUG( if(kbhit()) { i = 1; printf("\n"); break; }) if(status & 0x80) continue; DEBUG( printf("Processing TD %u/%u (%4u bytes) ", j + 1, num_tds, (td[j].actual_len + 1) & 0x7FF);) if(status != 0) { DEBUG( printf("\nUHCI transfer failed (TD #%u/%u, " "status=0x%02X)\n", j + 1, num_tds, status); if(status & 0x80) printf(" ACTIVE"); if(status & 0x40) printf(" STALLED"); if(status & 0x20) printf(" DATA BUFFER ERROR"); if(status & 0x10) printf(" BABBLE"); if(status & 0x08) printf(" NAK"); if(status & 0x04) printf(" CRC/TIMEOUT"); if(status & 0x02) printf(" BITSTUFF ERROR"); if(status & 0x01) printf(" BUS TURN-AROUND TIMEOUT");) i = ERR_HARDWARE; break; } /* current TD was processed successfully; advance to next TD */ j++; } DEBUG( printf("\n");) /* take this chain of TDs away from the UHCI */ QH_BULK.v = 0x01; /* b0=1: terminate */ return i; } /****************************************************************************/ #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) int uhci_setup(unsigned addr, uint8_t *data0, unsigned data_len, unsigned max_packet_size, uint8_t setup0[8], int low_speed) { static const unsigned setup_len = 8; static const unsigned endp = 0; /**/ #if defined(__386__) dma_mem_t setup_handle, data_handle; uint8_t *data1; #endif uint8_t data_toggle, pid, *setup, *data; unsigned num_tds, len, j, k; dma_mem_t td_handle; uint32_t td_linear; td_t *td; int i; DEBUG( printf("uhci_setup():\n");) pid = (setup0[0] & 0x80) ? USB_PID8_IN : USB_PID8_OUT; /* Allocate DMA memory for TDs. From UHCI11D.PDF, start of section 3: "Transfer Descriptors and Queue Heads must be aligned on 16-byte boundaries." */ num_tds = (data_len + max_packet_size - 1) / max_packet_size + 2; k = num_tds * sizeof(td_t); if((td = alloc_dma_mem(&td_handle, k, 16)) == NULL) return ERR_NO_MEM; td_linear = PTR2LINEAR(td); DEBUG( printf("%u TDs (%u bytes each) at linear address 0x%lX\n", num_tds, sizeof(td_t), td_linear);) #if !defined(__386__) setup = setup0; data = data0; #else /* Why is DMA so aggrivating under DPMI? INT 31h AX=0800h maps physical addresses to virtual -- if there were a DPMI function to do the opposite ("pin" a range of memory to prevent paging, make it physically contiguous, and return it's physical address), I wouldn't need these stupid bounce buffers in conventional memory. Obviously if you're writing your own kernel, you can make a dma_malloc() or something that allocates physically contiguous and unpaged memory. allocate DMA memory for setup */ if((setup = alloc_dma_mem(&setup_handle, 8, 1)) == NULL) { free_dma_mem(td_handle); return ERR_NO_MEM; } memcpy(setup, setup0, 8); /* allocate DMA memory for data */ data = NULL; if(data0 != NULL && data_len != 0) { if((data1 = alloc_dma_mem(&data_handle, data_len, 1)) == NULL) { free_dma_mem(td_handle); free_dma_mem(setup_handle); return ERR_NO_MEM; } if(pid == USB_PID8_OUT) memcpy(data1, data0, data_len); data = data1; } #endif /* build token TD dword #0 */ td[0].link = (td_linear + sizeof(td_t) * 1) | 0x04; /* b2=depth-first */ /* dword #1 (status) */ td[0].actual_len = (setup_len - 1) & 0x7FF; td[0].status = 0x80; /* b7=active */ /* b4-3=retry count=3, b2=low-speed */ td[0].flags = low_speed ? 0x1C : 0x18; /* dword #2 (token/destination) */ td[0].dword2 = TD_DWORD2(USB_PID8_SETUP, addr, endp, /*DATA0=*/0, setup_len); /* dword #3 (buffer) */ td[0].buf_ptr = PTR2LINEAR(setup); /* build data TDs */ data_toggle = 1; len = data_len; for(j = 1; j < num_tds - 1; j++) { td[j].link = (td_linear + sizeof(td_t) * (j + 1)) | 0x04; /* b2=1: depth-first */ k = MIN(len, max_packet_size); /* xxx - you're storing the same value in actual_len and the length value in dword #2 -- is this correct? (Yes, it works, but is it _correct_?) */ td[j].actual_len = (k - 1) & 0x7FF; td[j].status = 0x80; td[j].flags = low_speed ? 0x1C : 0x18; td[j].dword2 = TD_DWORD2(pid, addr, endp, data_toggle, k); td[j].buf_ptr = PTR2LINEAR(data); len -= k; data = &data[k]; data_toggle = data_toggle ? 0 : 1; } /* build handshake TD */ pid = (pid == USB_PID8_IN) ? USB_PID8_OUT : USB_PID8_IN; td[num_tds - 1].link = 0x01; /* b0=terminate */ td[num_tds - 1].actual_len = 0x7FF; /* =0 (no data) */ td[num_tds - 1].status = 0x80; td[num_tds - 1].flags = low_speed ? 0x1C : 0x18; td[num_tds - 1].dword2 = TD_DWORD2(pid, addr, endp, /*DATA1=*/1, /*max_len=*/0); td[num_tds - 1].buf_ptr = 0; i = do_uhci_transfer(td, num_tds); #if defined(__386__) if(data0 != NULL && data_len != 0) { if(pid == USB_PID8_OUT) /* 'pid' was complemented above */ memcpy(data0, data1, data_len); free_dma_mem(data_handle); } free_dma_mem(setup_handle); #endif free_dma_mem(td_handle); return i; } /***************************************************************************** uhci_in_out() does interrupt and bulk transfers *****************************************************************************/ int uhci_in_out(unsigned addr, uint8_t *data0, unsigned data_len, int low_speed, usb_endp_t *endp) { #if defined(__386__) dma_mem_t data_handle; uint8_t *data1; #endif unsigned num_tds, j, k, len; dma_mem_t td_handle; uint32_t td_linear; uint8_t pid, *data; td_t *td; int i; pid = (endp->endp_num & 0x80) ? USB_PID8_IN : USB_PID8_OUT; /* allocate DMA memory for TDs */ num_tds = (data_len + endp->max_packet_size - 1) / endp->max_packet_size; k = num_tds * sizeof(td_t); if((td = alloc_dma_mem(&td_handle, k, 16)) == NULL) return ERR_NO_MEM; td_linear = PTR2LINEAR(td); DEBUG( printf("\tuhci_in_out(): %u TDs (%u bytes each) at linear address " "0x%lX\n", num_tds, sizeof(td_t), td_linear);) #if !defined(__386__) data = data0; #else /* allocate DMA memory for setup */ data = NULL; if(data0 != NULL && data_len != 0) { if((data1 = alloc_dma_mem(&data_handle, data_len, 1)) == NULL) { free_dma_mem(td_handle); return ERR_NO_MEM; } if(pid == USB_PID8_OUT) memcpy(data1, data0, data_len); data = data1; } #endif /* build data TDs */ len = data_len; for(j = 0; j < num_tds; j++) { td[j].link = (td_linear + sizeof(td_t) * (j + 1)) | 0x04; /* b2=1: depth-first */ k = MIN(len, endp->max_packet_size); td[j].actual_len = (k - 1) & 0x7FF; td[j].status = 0x80; /* b7=1: Active */ /* b4-3=retry count=3, b2=low-speed */ td[j].flags = low_speed ? 0x1C : 0x18; td[j].dword2 = TD_DWORD2(pid, addr, endp->endp_num, endp->data_toggle, k); td[j].buf_ptr = PTR2LINEAR(data); len -= k; data = &data[k]; endp->data_toggle = endp->data_toggle ? 0 : 1; } td[num_tds - 1].link = 0x01; /* b0=1: terminate */ i = do_uhci_transfer(td, num_tds); #if defined(__386__) if(data0 != NULL && data_len != 0) { if(pid == USB_PID8_IN) memcpy(data0, data1, data_len); free_dma_mem(data_handle); } #endif free_dma_mem(td_handle); return i; } /****************************************************************************/ static dma_mem_t g_fl_qh_mem; static void uhci_shutdown(void) { free_dma_mem(g_fl_qh_mem); /* USBCMD register - shut down controller */ outportw(g_io_addr + 0, 0x0000); } /***************************************************************************** _____ ___ | |--------------------------->|int| ____ ____ | | ... | 1 | |ctrl| |bulk| __________ |frame| ___ |QH | |QH | |QH | | |list |---------------->|int|-..-->|___|->|____|->|____| UCHI chip | |(1024| ___ |256| | |ptrs)|--------->|int|->|QH | | | | ____ |512| |___| FRBASEADD--->|_____|->|int |->|QH | __________| |1024| |___| |QH | |____| 13 queue heads (QHs) total: 11 interrupt (serviced every 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, and 1 frames), 1 control, 1 bulk uhci_init() returns +1 if no UHCI USB controller detected, 0 if success, < 0 if error *****************************************************************************/ /* linear, physical addresses of each QH */ #define QH_INT1024_LINEAR (qh_linear + sizeof(qh_t) * QH_INT1024_NUM) #define QH_INT512_LINEAR (qh_linear + sizeof(qh_t) * QH_INT512_NUM) #define QH_INT256_LINEAR (qh_linear + sizeof(qh_t) * QH_INT256_NUM) #define QH_INT128_LINEAR (qh_linear + sizeof(qh_t) * QH_INT128_NUM) #define QH_INT64_LINEAR (qh_linear + sizeof(qh_t) * QH_INT64_NUM) #define QH_INT32_LINEAR (qh_linear + sizeof(qh_t) * QH_INT32_NUM) #define QH_INT16_LINEAR (qh_linear + sizeof(qh_t) * QH_INT16_NUM) #define QH_INT8_LINEAR (qh_linear + sizeof(qh_t) * QH_INT8_NUM) #define QH_INT4_LINEAR (qh_linear + sizeof(qh_t) * QH_INT4_NUM) #define QH_INT2_LINEAR (qh_linear + sizeof(qh_t) * QH_INT2_NUM) #define QH_INT1_LINEAR (qh_linear + sizeof(qh_t) * QH_INT1_NUM) #define QH_CONTROL_LINEAR (qh_linear + sizeof(qh_t) * QH_CONTROL_NUM) #define QH_BULK_LINEAR (qh_linear + sizeof(qh_t) * QH_BULK_NUM) int uhci_init(void) { uint32_t d, *fl, fl_linear, qh_linear; unsigned bdf, j; uint16_t w; uint8_t b; /* detect PCI, use it to find a UHCI USB controller */ if(pci_detect()) { printf("No PCI\n"); return +1; } bdf = 0; do { d = pci_read_dword(bdf, 0x08); if((d & 0xFFFFFF00L) == 0x0C030000L) goto OK; } while(!pci_iterate(&bdf)); ERROR: printf("No UHCI USB controller\n"); return +1; OK: // b = (d >> 8) & 0xFF; // if(b != 0) // goto ERROR; d = pci_read_dword(bdf, 0x20); d &= ~0x01L; /* b0 indicates I/O-mapped I/O */ DEBUG( printf("UHCI USB controller at I/O addres 0x%lX\n", d);) g_io_addr = (unsigned)d; /* enable bus-master */ if(((w = pci_read_word(bdf, 0x04)) & 0x04) == 0) { w |= 0x04; pci_write_word(bdf, 0x04, w); } /* Allocate DMA-compatible memory for frame list and the 13 queue heads. Align the memory on a 4K boundary; which will also align it on a 16-byte boundary for the QHs. From UHCI11D.PDF: "The 4-Kbyte Frame List Table is aligned on a 4-Kbyte boundary." -- section 1.2.1 "Transfer Descriptors and Queue Heads must be aligned on 16-byte boundaries." -- start of section 3 */ j = 4096 + NUM_QH * sizeof(qh_t); if((fl = alloc_dma_mem(&g_fl_qh_mem, j, 4096)) == NULL) return ERR_NO_MEM; fl_linear = PTR2LINEAR(fl); qh_linear = fl_linear + 4096; g_qh = LINEAR2NEAR(qh_linear); /* init queue heads -- each one points to the next in the QH array */ for(j = 0; j < NUM_QH; j++) { g_qh[j].v = 0x01; /* b0=1: terminate */ g_qh[j].h = (qh_linear + (j + 1) * sizeof(qh_t)) | 0x02; /* b1=1: points to another QH */ } QH_BULK.h = 0x01; /* b0=1: terminate */ DEBUG( printf("frame list : 4096 bytes at linear address 0x%lX\n", fl_linear); printf("queue heads: %4u bytes at linear address 0x%lX\n", sizeof(qh_t) * NUM_QH, qh_linear); ) /* init frame list How often is each QH processed */ for(j = 0; j < 1024; j++) /* for each pass through the */ { /* frame list (every 1024 frames)? */ if((j & 0x3FF) == 0) d = QH_INT1024_LINEAR; /* ...once */ else if((j & 0x1FF) == 0) d = QH_INT512_LINEAR; /* ...twice */ else if((j & 0x0FF) == 0) d = QH_INT256_LINEAR; /* ...four times */ else if((j & 0x07F) == 0) d = QH_INT128_LINEAR; /* ...eight times */ else if((j & 0x03F) == 0) d = QH_INT64_LINEAR; else if((j & 0x01F) == 0) d = QH_INT32_LINEAR; else if((j & 0x00F) == 0) d = QH_INT16_LINEAR; else if((j & 0x007) == 0) d = QH_INT8_LINEAR; else if((j & 0x003) == 0) d = QH_INT4_LINEAR; /* ...256 times */ else if((j & 0x001) == 0) d = QH_INT2_LINEAR; /* ...512 times */ else d = QH_INT1_LINEAR; /* ...1024 times */ fl[j] = d | 0x02; /* b1=1: points to a QH: */ } /* Reset UHCI. If you have a USB keyboard, it will stop responding at this point. Emulated PS/2 keyboard _may_ stop responding. */ outportw(g_io_addr + 0, 0x0002); for(w = 50; w != 0; w--) { if((inportw(g_io_addr + 0) & 0x0002) == 0) break; delay(1); } if(w == 0) { printf("UHCI controller reset failed\n"); return ERR_HARDWARE; } /* FRBASEADD register - write linear address of Frame List */ outportl(g_io_addr + 8, fl_linear); /* USBCMD register - set b0 to start controller; i.e. GO! */ outportw(g_io_addr + 0, 0x0001); atexit(uhci_shutdown); return 0; } /***************************************************************************** Returns: >0 if a device was connected: MSBs =0 (positive value) b7 =1 (ensures non-zero return value) b6 =1 if low-speed device b0 port number (only 0 or 1 for UHCI) 0 if no change in port status <0 if device was disconnected MSBs =1 (negative value) b7 =1 b0 port number *****************************************************************************/ int uhci_poll(void) { unsigned p, io, port; for(p = 0; p < 2; p++) /* UHCI has only 2 ports */ { io = g_io_addr + ((p == 1) ? 16 : 18); port = inportw(io); /* b1=1 if connect status changed */ if((port & 0x0002) == 0) continue; /* I guess we need to reset the port whenever the status changes??? */ outportw(io, 0x0200); delay(50); outportw(io, 0); delay(1); /* udelay(5); */ /* b3=PortEnable/DisableChanged (writing a '1' bit to clear it), b2=PortEnable, b1=ConnectStatusChanged (writing a '1' bit to clear it) */ outportw(io, 0x000E); delay(10); /* b0=1 if device connected */ if(port & 0x0001) return 0x80 | ((port & 0x100) ? 0x40 : 0) | p; /* device disconnected */ else return -0x80 | p; } /* no change: return 0 */ return 0; } /*---------------------------------------------------------------------------- USB EXPORTS: usb_iface_t usb_config_t usb_dev_t void usb_dump(const usb_dev_t *d); void usb_destroy_dev(usb_dev_t *d); int usb_setup(const usb_dev_t *d, unsigned req_type, unsigned req, unsigned value, unsigned index, void *data, unsigned data_len); int usb_enumerate(usb_dev_t *d); int usb_set_config(usb_dev_t *d, unsigned config_num); int usb_set_iface(const usb_dev_t *d, unsigned iface_num); int usb_in(const usb_dev_t *d, unsigned e, void *data, unsigned data_len); int usb_out(const usb_dev_t *d, unsigned e, const void *data, unsigned data_len); ----------------------------------------------------------------------------*/ #include /* calloc(), malloc(), free() */ #include /* memset() */ /* descriptor types */ #define USB_DTYPE_DEVICE 1 /* 18 bytes */ #define USB_DTYPE_CONFIG 2 /* 9 bytes */ #define USB_DTYPE_STRING 3 /* variable length */ #define USB_DTYPE_INTERFACE 4 /* 9 bytes; "Not directly accessible" */ #define USB_DTYPE_ENDPOINT 5 /* 7 bytes; "Not directly accessible" */ /* request types for control transfers */ #define USB_REQ_SET_ADDRESS 5 #define USB_REQ_GET_DESCRIPTOR 6 #define USB_REQ_SET_CONFIG 9 #define USB_REQ_SET_INTERFACE 11 /* USB interface info */ typedef struct { uint8_t iface_num; uint8_t alt; /* ? */ /* "class" is a C++ reserved word */ uint8_t _class, subclass, proto; char *iface_string; /* endpoints for this interface */ uint8_t num_endp; usb_endp_t *endp; } usb_iface_t; /* USB configuration info */ typedef struct { uint8_t config_num; char *config_string; uint8_t attrib; unsigned max_current; /* in mA */ /* interfaces for this configuration */ uint8_t num_ifaces; usb_iface_t *iface; unsigned curr_iface; /* 0-based */ } usb_config_t; /* USB device info */ typedef struct { uint8_t addr; /* 'addr' is assigned by usb_set_address() */ uint8_t usb_ver_major, usb_ver_minor; uint8_t _class, subclass, proto; uint8_t endpoint0_max_packet_size; unsigned vendor_id, device_id; char *mfgr_string; char *prod_string; char *serial_string; /* configurations for this device */ unsigned num_configs; usb_config_t *config; unsigned curr_config; /* 1-based; 0="deconfigured" */ /* this is actually read from the port; not from the device: */ unsigned low_speed : 1; } usb_dev_t; /****************************************************************************/ static void usb_dump_id(unsigned c, unsigned sc) { static const char *c_name[] = { "interface-defined", "audio", "communications", "HID", "?", "physical", "camera", "printer", "mass storage", "hub", "data", "smart card", "?", "content security", "video/webcam", "personal healthcare" /* DIAGNOSTIC_DEVICE = 0xdc, WIRELESS = 0xe0, including Bluetooth APPLICATION = 0xfe, VENDOR_SPEC = 0xff */ }; static const char *sc_name3[] = { "none", "boot-time support" }; static const char *sc_name8[] = { "?", "reduced block commands", "MMC (CD/DVD)", "QIC-157 (tape)", "UFI (floppy", "SFF-8070i (floppy)", "SCSI transparent", "lockable SCSI", "IEEE 1667", }; /**/ printf("class:subclass=%u:%u (%s:", c, sc, (c < COUNT(c_name)) ? c_name[c] : "?"); switch(c) { case 3: printf("%s", (sc < COUNT(sc_name3)) ? sc_name3[sc] : "?"); break; case 8: printf("%s", (sc < COUNT(sc_name8)) ? sc_name8[sc] : "?"); break; default: printf("?"); break; } printf(")"); } /***************************************************************************** Endpoint descriptor: Offset Length Name Description ------ ------ ------------------- --------------------------------- 0 1 bLength 7 = Length of this descriptor 1 1 bDescriptorType 5 = USB_DTYPE_ENDPOINT 2 1 bEndpointAddress b7=IN/!OUT, b3-b0=endpoint number 3 1 bmAttributes b5-b4=usage (?), b3-b2=synchronization (?), b1-b0=transfer type (3=interrupt, 2=bulk, 1=isochronous, 0=control) 4-5 2 wMaxPacketSize 6 1 bInterval Polling interval in 1 ms frames (for low/full speed) or 125 us microframes (for high speed) 7 *****************************************************************************/ static void usb_dump_endp(const usb_dev_t *d, unsigned c, unsigned i, unsigned e) { usb_config_t *cfg; usb_iface_t *iface; usb_endp_t *endp; cfg = &d->config[c]; iface = &cfg->iface[i]; endp = &iface->endp[e]; printf(" ENDPOINT INFO for address %u, config %u, " "interface %u, endpoint %u ('%u'):\n", d->addr, c, i, e, endp->endp_num & 0x0F); printf("\t%s, attrib=0x%02X {", (endp->endp_num & 0x80) ? "IN" : "OUT", endp->attrib); switch(endp->attrib & 0x30) { case 0: printf("data"); break; case 0x10: printf("feedback"); break; case 0x20: printf("implicit_feedback"); break; case 0x30: printf("RESERVED"); break; } if((endp->attrib & 0x03) == 0x01) /* isochronous endpoints only */ switch(endp->attrib & 0x0C) { case 0: printf(", no_sync"); break; case 4: printf(", asynchronous"); break; case 8: printf(", adaptive"); break; case 12: printf(", synchronous"); break; } switch(endp->attrib & 0x03) { case 0: printf(", control"); break; case 1: printf(", isochronous"); break; case 2: printf(", bulk"); break; case 3: printf(", interrupt"); break; } printf("}, max_packet_size=%u,\n\tpolling interval=%u frames\n", endp->max_packet_size, endp->polling_interval); } /***************************************************************************** Interface descriptor: Offset Length Name Description ------ ------ ------------------- --------------------------------- 0 1 bLength 9 = Length of this descriptor 1 1 bDescriptorType 4 = USB_DTYPE_INTERFACE 2 1 bInterfaceNumber 0-based 3 1 bAlternateSetting 4 1 bNumEndpoints 5 1 bInterfaceClass 6 1 bInterfaceSubClass 7 1 bInterfaceProtocol 8 1 iInterface String index 9 *****************************************************************************/ static void usb_dump_iface(const usb_dev_t *d, unsigned c, unsigned i) { usb_config_t *cfg; usb_iface_t *iface; cfg = &d->config[c]; iface = &cfg->iface[i]; printf(" INTERFACE INFO for address %u, config %u, " "interface %u:\n", d->addr, c, i); printf(" Interface name: %s\n ", iface->iface_string); usb_dump_id(iface->_class, iface->subclass); printf(", proto=0x%02X,\n alternate setting=%u, %u " "endpoint(s)\n", iface->proto, iface->alt, iface->num_endp); } /***************************************************************************** Configuration descriptor: Offset Length Name Description ------ ------ ------------------- --------------------------------- 0 1 bLength 9 = Length of this descriptor (exluding interface and endpoint descriptors; see wTotalLength) 1 1 bDescriptorType 2 = USB_DTYPE_CONFIG 2-3 2 wTotalLength Combined length of this descriptor and all subordinate descriptors (interface & endpoint) 4 1 bNumInterfaces 5 1 bConfigurationValue (1-based) currently active interface 6 1 iConfiguration String index 7 1 bmAttributes b7=1, b6=self-powered, b5=remote wakeup 8 1 bMaxPower MaxCurrent, actually, in units of 2 mA 9 - - interface & endpoint descriptors here *****************************************************************************/ static void usb_dump_config(const usb_dev_t *d, unsigned c) { usb_config_t *cfg; cfg = &d->config[c]; printf(" CONFIG INFO for address %u, config %u\n", d->addr, c); printf(" Config name: %s\n", cfg->config_string); printf(" Config attributes=0x%02X {", cfg->attrib); printf((cfg->attrib & 0x40) ? "self-powered" : "not self-powered"); printf((cfg->attrib & 0x20) ? ", remote wakeup" : ", not remote wakeup"); printf("},\n max current=%umA", cfg->max_current); printf(". Config has %u interface(s), current is %u\n", cfg->num_ifaces, cfg->curr_iface); } /***************************************************************************** Device descriptor: Offset Length Name Description ------ ------ ------------------- --------------------------------- 0 1 bLength 18 = Length of this descriptor 1 1 bDescriptorType 1 = USB_DTYPE_DEVICE 2-3 2 bcdUSB USB version in BCD format, minor first 4 1 bDeviceClass \ If these values are zero (they 5 1 bDeviceSubClass } (usually are), check class: 6 1 bDeviceProtocol / subclass:protocol of each interface 7 1 bMaxPacketSize0 Endpoint #0 max. pkt. size (8,16,32,64) 8-9 2 idVendor 10-11 2 idProduct 12-13 2 bcdDevice 14 1 iManufacturer String index 15 1 iProduct String index 16 1 iSerialNumber String index 17 1 bNumConfigurations 18 *****************************************************************************/ static void usb_dump_device(const usb_dev_t *d) { printf("DEVICE INFO for USB address %u:\n", d->addr); printf(" USB ver=%X.%X, ", d->usb_ver_major, d->usb_ver_minor); usb_dump_id(d->_class, d->subclass); printf(", proto=0x%02X,\n", d->proto); printf(" vendor ID=0x%04X, device ID=0x%04X, endpoint0_max_" "packet_size=%u\n", d->vendor_id, d->device_id, d->endpoint0_max_packet_size); /* bytes 12-13 bcdDevice 2 "Device release number in binary-coded decimal" */ printf(" Manufacturer: %s\n", d->mfgr_string); printf(" Product: %s\n", d->prod_string); printf(" Serial number: %s\n", d->serial_string); printf(" Device has %u configuration(s); current is %u\n", d->num_configs, d->curr_config); } /****************************************************************************/ void usb_dump(const usb_dev_t *d) { usb_iface_t *iface; usb_config_t *cfg; unsigned c, i, e; usb_dump_device(d); /* curr_config==0 means device is unconfigured but 'c' is an index into usb_dev_t.config[], so it's 0-based */ for(c = 0; c < d->num_configs; c++) { cfg = &d->config[c]; usb_dump_config(d, c); for(i = 0; i < cfg->num_ifaces; i++) { iface = &cfg->iface[i]; usb_dump_iface(d, c, i); for(e = 0; e < iface->num_endp; e++) usb_dump_endp(d, c, i, e); } } } /****************************************************************************/ void usb_destroy_dev(usb_dev_t *d) { usb_config_t *cfg; usb_iface_t *iface; unsigned c, i; /* for each config in device... */ for(c = 0; c < d->num_configs; c++) { cfg = &d->config[c]; /* for each interface in config... */ for(i = 0; i < cfg->num_ifaces; i++) { iface = &cfg->iface[i]; /* free endpoints */ if(iface->endp != NULL) free(iface->endp); /* free string */ if(iface->iface_string != NULL) free(iface->iface_string); } /* free interfaces */ if(cfg->iface != NULL) free(cfg->iface); /* free string */ if(cfg->config_string != NULL) free(cfg->config_string); } /* free configs */ if(d->config != NULL) free(d->config); /* free strings */ if(d->mfgr_string != NULL) free(d->mfgr_string); if(d->prod_string != NULL) free(d->prod_string); if(d->serial_string != NULL) free(d->serial_string); memset(d, 0, sizeof(usb_dev_t)); } /****************************************************************************/ int usb_setup(const usb_dev_t *d, unsigned req_type, unsigned req, unsigned value, unsigned index, void *data, unsigned data_len) { uint8_t setup[8]; DEBUG( printf("usb_setup():\n");) setup[0] = req_type; setup[1] = req; write_le16(&setup[2], value); write_le16(&setup[4], index); write_le16(&setup[6], data_len); return uhci_setup(d->addr, data, data_len, d->endpoint0_max_packet_size, setup, d->low_speed); } /***************************************************************************** This function will fail if desc_type==4==USB_DTYPE_INTERFACE or desc_type==5==USB_DTYPE_ENDPOINT. "USB Made Simple" calls these "Not directly accessible". To get these descriptors: 1. Load the config descriptor 2. Look at bytes 2-3 of the config descriptor. This is the combined length of the config descriptor and its subordinate interface and endpoint descriptors. 3. Load this entire mess of descriptors (config, interface, and endpoint) into memory, then pick out what you need *****************************************************************************/ static int usb_get_descriptor(const usb_dev_t *d, unsigned desc_type, unsigned desc_index, unsigned desc_len, void *data) { DEBUG( printf("usb_get_descriptor():\n");) return usb_setup(d, /* bmReqType */ 0x80 | 0 | 0, /* in, type=std, target=dev */ /* bRequest */ USB_REQ_GET_DESCRIPTOR, /* =6 */ /* wValue */ (desc_type << 8) | desc_index, /* wIndex */ 0, data, /* wLength */ desc_len); } /***************************************************************************** loads string from string descriptor given by index 'string_num'; returns pointer to malloc()ed memory at 's_ptr' *****************************************************************************/ static int usb_load_string(const usb_dev_t *d, char **s_ptr, unsigned string_num) { char data[256]; unsigned len, j; char *s = NULL; int i; DEBUG( printf("usb_load_string(%u):\n", string_num);) if(string_num != 0) { /* load first two bytes, which are data length and charset */ if((i = usb_setup(d, /* bmReqType */ 0x80 | 0 | 0, /* in, type=std, target=dev */ /* bRequest */ USB_REQ_GET_DESCRIPTOR, /*=6 */ /* wValue */ (USB_DTYPE_STRING << 8) | string_num, /* wIndex */ 0, data, /* wLength */ 2)) != 0) return i; /* load entire string */ len = data[0]; if((i = usb_setup(d, /* bmReqType */ 0x80 | 0 | 0, /* in, type=std, target=dev */ /* bRequest */ USB_REQ_GET_DESCRIPTOR, /*=6 */ /* wValue */ (USB_DTYPE_STRING << 8) | string_num, /* wIndex */ 0, data, /* wLength */ len)) != 0) return i; /* half-assed conversion from 16-bit UCS-2 Unicode to ASCII */ for(j = 0; j < len / 2 - 1; j++) data[j] = data[(j + 1) * 2]; data[len / 2 - 1] = '\0'; if((s = strdup(data)) == NULL) return ERR_NO_MEM; } *s_ptr = s; return 0; } /****************************************************************************/ static int usb_set_address(usb_dev_t *d, unsigned usb_addr) { int i; DEBUG( printf("usb_set_address(%u):\n", usb_addr);) if((i = usb_setup(d, /* bmReqType */ 0 | 0 | 0, /* out, type=std, target=dev */ /* bRequest */ USB_REQ_SET_ADDRESS, /* =5 */ /* wValue */ usb_addr, /* wIndex */ 0, /* wLength */ NULL, 0)) == 0) d->addr = usb_addr; return i; } /***************************************************************************** If this function fails, 'd' may be partially initialized -- call usb_destroy_dev(d) to clean it up *****************************************************************************/ int usb_enumerate(usb_dev_t *d) { static unsigned usb_dev_addr = 1; /**/ unsigned c/*onfig*/, i/*nterface*/, e/*ndpoint*/ = 0; uint8_t d_desc[256], *cie_desc; unsigned d_len, cie_len, j; usb_iface_t *iface; usb_config_t *cfg; usb_endp_t *endp; int err; DEBUG( printf("usb_enumerate():\n");) memset(d, 0, sizeof(d)); /* temporary values */ d->addr = 0; d->endpoint0_max_packet_size = 8; DEBUG( printf("Loading first 8 bytes of device descriptor...\n");) if((err = usb_get_descriptor(d, USB_DTYPE_DEVICE, 0, 8, d_desc)) != 0) return err; /* get what data we can from the first 8 bytes */ d_len = d_desc[0]; d->usb_ver_minor = d_desc[2]; d->usb_ver_major = d_desc[3]; d->_class = d_desc[4]; d->subclass = d_desc[5]; d->proto = d_desc[6]; d->endpoint0_max_packet_size = d_desc[7]; /* 8, 16, 32, or 64 */ DEBUG( printf("Assigning USB address %u to device...\n", usb_dev_addr);) if((err = usb_set_address(d, usb_dev_addr)) != 0) return err; usb_dev_addr++; /* got endpoint0_max_packet_size; can now load the rest of the device descriptor (up to 18 bytes) */ DEBUG( printf("Loading entire device descriptor (%u bytes)...\n", d_len);) if((err = usb_get_descriptor(d, USB_DTYPE_DEVICE, 0, d_len, d_desc)) != 0) return err; d->vendor_id = read_le16(&d_desc[8]); d->device_id = read_le16(&d_desc[10]); DEBUG( printf("Loading strings...\n");) if((err = usb_load_string(d, &d->mfgr_string, d_desc[14])) != 0) return err; if((err = usb_load_string(d, &d->prod_string, d_desc[15])) != 0) return err; if((err = usb_load_string(d, &d->serial_string, d_desc[16])) != 0) return err; d->num_configs = d_desc[17]; DEBUG( printf("Allocating memory for configs...\n");) if((d->config = calloc(d->num_configs, sizeof(usb_config_t))) == NULL) return ERR_NO_MEM; /* load configuration descriptor (9 bytes; reuse memory used by device descriptor) */ for(c = 0; c < d->num_configs; c++) { DEBUG( printf("Loading first 9 bytes of configuration " "descriptor %u/%u...\n", c + 1, d->num_configs);) if((err = usb_get_descriptor(d, USB_DTYPE_CONFIG, c, 9, d_desc)) != 0) return err; cfg = &d->config[c]; cfg->num_ifaces = d_desc[4]; cfg->config_num = d_desc[5]; DEBUG( printf("Loading config name string...\n");) if((err = usb_load_string(d, &cfg->config_string, d_desc[6])) != 0) return err; cfg->attrib = d_desc[7]; cfg->max_current = d_desc[8] * 2; /* d_desc[2-3] contains combined length of this (configuration) descriptor and all subordinate (interface and endpoint) descriptors. Allocate memory and load it all. */ cie_len = read_le16(&d_desc[2]); DEBUG( printf("Allocating memory for descriptors...\n");) if((cie_desc = malloc(cie_len)) == NULL) return ERR_NO_MEM; /* allocate memory for interfaces */ if((cfg->iface = calloc(cfg->num_ifaces, sizeof(usb_iface_t))) == NULL) { free(cie_desc); return ERR_NO_MEM; } DEBUG( printf("Loading entire configuration descriptor %u/%u " "(%u bytes)...\n", c + 1, d->num_configs, cie_len);) /* reload config descriptor, along with associated interface and endpoint descriptors */ if((err = usb_get_descriptor(d, USB_DTYPE_CONFIG, c, cie_len, cie_desc)) != 0) { free(cie_desc); return err; } /* find interface and endpoint descriptors and read them */ i = 0; DEBUG( printf("Reading endpoint descriptors...\n");) /* for(j = 0; j < cie_len; ) first 9 bytes is the configuration descriptor, so skip it */ for(j = 9; j < cie_len; ) { /* byte 1 of the descriptor is the descriptor type */ if(cie_desc[j + 1] == USB_DTYPE_INTERFACE) { if(i >= cfg->num_ifaces) break; iface = &cfg->iface[i]; iface->iface_num = cie_desc[j + 2]; iface->alt = cie_desc[j + 3]; iface->num_endp = cie_desc[j + 4]; iface->_class = cie_desc[j + 5]; iface->subclass = cie_desc[j + 6]; iface->proto = cie_desc[j + 7]; if(usb_load_string(d, &iface->iface_string, cie_desc[j + 8]) != 0) { free(cie_desc); return err; } i++; /* allocate memory for endpoints */ if((iface->endp = calloc(iface->num_endp, sizeof(usb_endp_t))) == NULL) { free(cie_desc); return ERR_NO_MEM; } e = 0; } else if(cie_desc[j + 1] == USB_DTYPE_ENDPOINT) { if(e >= iface->num_endp) break; endp = &iface->endp[e]; endp->endp_num = cie_desc[j + 2]; endp->attrib = cie_desc[j + 3]; endp->max_packet_size = read_le16(&cie_desc[j + 4]); endp->polling_interval = cie_desc[j + 6]; e++; } /* I get a HID descriptor (type 33) from the USB keyboard: */ else { printf("*** Warning: weird descriptor " "at offset %u; type=%u, len=%u\n", j, cie_desc[j + 1], cie_desc[j + 0]); dump(&cie_desc[j], cie_desc[j + 0]); } /* byte 0 of the descriptor is the total length; for all descriptor types */ j += cie_desc[j + 0]; } free(cie_desc); } DEBUG( printf("");) return 0; } /***************************************************************************** config_num = 0 to deconfigure device, else it's 1-based *****************************************************************************/ int usb_set_config(usb_dev_t *d, unsigned config_num) { int i; DEBUG( printf("usb_set_config(%u)\n", config_num);) if(config_num > d->num_configs) { printf("Error: config_num (%u) > num_configs (%u)\n", config_num, d->num_configs); return ERR_BAD_ARG; } if((i = usb_setup(d, /* bmReqType */ 0 | 0 | 0, /* out, type=std, target=dev */ /* bRequest */ USB_REQ_SET_CONFIG, /* =9 */ /* wValue */ config_num, /* wIndex */ 0, /* wLength */ NULL, 0)) == 0) d->curr_config = config_num; return i; } /***************************************************************************** iface_num is 0-based *****************************************************************************/ int usb_set_iface(const usb_dev_t *d, unsigned iface_num) { usb_config_t *cfg; int i; DEBUG( printf("usb_set_iface(%u)\n", iface_num);) if(d->curr_config < 1 || d->curr_config > d->num_configs) { printf("Error in usb_set_iface(): config_num (%u) " "== 0 or > num_configs (%u)\n", d->curr_config, d->num_configs); return ERR_SOFTWARE; } cfg = &d->config[d->curr_config - 1]; if(iface_num >= cfg->num_ifaces) { printf("Error in usb_set_iface(): interface_num (%u) " ">= num_interfaces (%u)\n", iface_num, cfg->num_ifaces); return ERR_BAD_ARG; } if((i = usb_setup(d, /* bmReqType */ 0 | 0 | 1, /* out, type=std, target=iface */ /* bRequest */ USB_REQ_SET_INTERFACE, /* =11 */ /* wValue */ 0, /* alternate setting (?) */ /* wIndex */ iface_num, /* wLength */ NULL, 0)) == 0) cfg->curr_iface = iface_num; return i; } /****************************************************************************/ int usb_in(const usb_dev_t *d, unsigned e, void *data, unsigned data_len) { usb_config_t *cfg; usb_iface_t *iface; usb_endp_t *endp; DEBUG( printf("usb_in():\n");) if(d->curr_config < 1 || d->curr_config > d->num_configs) { printf("Error in usb_in(): config_num (%u) ==0 or > " "num_configs (%u)\n", d->curr_config, d->num_configs); return ERR_SOFTWARE; /* programmer error */ } cfg = &d->config[d->curr_config - 1]; /* 1-based! */ if(cfg->curr_iface >= cfg->num_ifaces) { printf("Error in usb_in(): interface_num (%u) >= " "num_interfaces (%u)\n", cfg->curr_iface, cfg->num_ifaces); return ERR_SOFTWARE; } iface = &cfg->iface[cfg->curr_iface]; if(e >= iface->num_endp) { printf("Error in usb_in(): endpoint_num (%u) >= " "num_endpoints (%u)\n", e, iface->num_endp); return ERR_SOFTWARE; } endp = &iface->endp[e]; if((endp->endp_num & 0x80) == 0) { printf("Error in usb_in(): endpoint %u is for output\n", e); return ERR_SOFTWARE; } DEBUG( printf("usb_in(): config=%u, iface=%u, endpoint=%u ('%u')...\n", d->curr_config, cfg->curr_iface, e, endp->endp_num & 0x0F);) return uhci_in_out(d->addr, data, data_len, d->low_speed, endp); } /****************************************************************************/ int usb_out(const usb_dev_t *d, unsigned e, const void *data, unsigned data_len) { usb_config_t *cfg; usb_iface_t *iface; usb_endp_t *endp; DEBUG( printf("usb_out():\n");) if(d->curr_config < 1 || d->curr_config > d->num_configs) { printf("Error in usb_out(): config_num (%u) ==0 or > " "num_configs (%u)\n", d->curr_config, d->num_configs); return ERR_SOFTWARE; } cfg = &d->config[d->curr_config - 1]; /* 1-based! */ if(cfg->curr_iface >= cfg->num_ifaces) { printf("Error in usb_out(): interface_num (%u) >= " "num_interfaes (%u)\n", cfg->curr_iface, cfg->num_ifaces); return ERR_SOFTWARE; } iface = &cfg->iface[cfg->curr_iface]; if(e >= iface->num_endp) { printf("Error in usb_out(): endpoint_num (%u) >= " "num_endpoints (%u)\n", e, iface->num_endp); return ERR_SOFTWARE; } endp = &iface->endp[e]; if(endp->endp_num & 0x80) { printf("Error in usb_out(): endpoint %u is for input\n", e); return ERR_SOFTWARE; } DEBUG( printf("usb_out(): config=%u, iface=%u, endpoint=%u ('%u')...\n", d->curr_config, cfg->curr_iface, e, endp->endp_num & 0x0F);) return uhci_in_out(d->addr, data, data_len, d->low_speed, endp); } /*---------------------------------------------------------------------------- ----------------------------------------------------------------------------*/ /***************************************************************************** Like atapi_read_pio(), this functions returns <0 if error, else it returns the number of data bytes that were read; which might be less than 'data_len' *****************************************************************************/ static int usb_scsi_read(const usb_dev_t *d, const char pkt[16], void *data, unsigned data_len) { static const uint32_t tag = 0xDEADBEEFL; /**/ char cmd[31], csw[13]; unsigned e, ei, eo; usb_iface_t *iface; usb_config_t *cfg; usb_endp_t *endp; uint32_t j; int i; DEBUG( printf("usb_scsi_read(cmd=0x%02X):\n", pkt[0]);) cfg = &d->config[d->curr_config - 1]; iface = &cfg->iface[cfg->curr_iface]; /* From "Universal Serial Bus - Mass Storage Class - Bulk-Only Transport" section 4: "The host shall use the first reported Bulk-In and Bulk-Out endpoints for the selected interface." */ ei = eo = -1u; for(e = 0; e < iface->num_endp; e++) { endp = &iface->endp[e]; if((endp->attrib & 0x03) != 2) /* not Bulk */ continue; if(endp->endp_num & 0x80) { if(ei == -1u) ei = e; } else { if(eo == -1u) eo = e; } } if(eo == -1u || ei == -1u) { printf("Error in usb_scsi_read(): missing " "IN and/or OUT bulk endpoint\n"); return -1; } /* 15-byte Command Block Wrapper (CBW). Unlike the SCSI command packet, multi-byte values here are LITTLE-ENDIAN! */ strcpy(cmd, "USBC"); write_le32(&cmd[4], tag); /* tag (transaction ID) */ write_le32(&cmd[8], data_len); /* # bytes of data */ cmd[12] = 0x80; /* b7=direction=device-to-host */ cmd[13] = 0; /* LUN; always zero I think */ cmd[14] = 16; /* # bytes in SCSI command packet */ memcpy(&cmd[15], pkt, 16); /* send command */ if((i = usb_out(d, eo, cmd, sizeof(cmd))) != 0) return i; /* read data */ if((i = usb_in(d, ei, data, data_len)) != 0) return i; /* read 13-byte Command Status Wrapper (CSW) */ if((i = usb_in(d, ei, csw, 13)) != 0) return i; /* validate CSW */ if(memcmp(csw, "USBS", 4)) { printf("Error in usb_scsi_read(): CSW starts with " "'%-4.4s'; should be 'USBS'\n", &csw[0]); return -1; } if((j = read_le32(&csw[4])) != tag) { printf("Error in usb_scsi_read(): CSW tag is 0x%lX, " "should be 0x%lX\n", j, tag); return -1; } if(csw[12] != 0) { printf("Error in usb_scsi_read(): CSW status is %u, " "should be 0\n", csw[12]); return -1; } if((j = read_le32(&csw[8])) > data_len) { printf("Error in usb_scsi_read(): CSW residue %lu > " "request size %lu\n", j, data_len); return -1; } if(j != 0) printf("Warning in usb_scsi_read(): 'residue'=%lu\n", j); return (unsigned)(data_len - j); } /***************************************************************************** ((sense >> 24) & 0xFF) = sense key ((sense >> 16) & 0xFF) = additional sense code (ASC) ((sense >> 8) & 0xFF) = additional sense code qualifier (ASCQ) ((sense >> 0) & 0xFF) = 1 if ASCQ valid ****************************************************************************** This function returns a value that indicates how to recover from the error: */ #define REC_IGNORE 1 #define REC_RESET 2 #define REC_ABORT 3 static unsigned scsi_dump_sense(uint32_t sense) { static const struct { uint8_t asc, ascq, recovery; char *msg; } scsi_error[] = { /* ASC, ASCQ, and error message text copy-and-pasted from SFF-8020i */ { 0x00, 0x00, REC_IGNORE, "NO ADDITIONAL SENSE INFORMATION" }, { 0x00, 0x11, REC_IGNORE, "PLAY OPERATION IN PROGRESS" }, { 0x00, 0x12, REC_IGNORE, "PLAY OPERATION PAUSED" }, { 0x00, 0x13, REC_IGNORE, "PLAY OPERATION SUCCESSFULLY COMPLETED" }, { 0x00, 0x14, REC_IGNORE, "PLAY OPERATION STOPPED DUE TO ERROR" }, { 0x00, 0x15, REC_IGNORE, "NO CURRENT AUDIO STATUS TO RETURN" }, { 0x01, 0x00, REC_RESET, "MECHANICAL POSITIONING OR CHANGER ERROR" }, { 0x02, 0x00, REC_RESET, "NO SEEK COMPLETE" }, { 0x04, 0x00, REC_RESET, "LOGICAL DRIVE NOT READY - CAUSE NOT REPORTABLE" }, { 0x04, 0x01, REC_IGNORE, "LOGICAL DRIVE NOT READY - IN PROGRESS OF BECOMING READY" }, { 0x04, 0x02, REC_ABORT, "LOGICAL DRIVE NOT READY - INITIALIZING COMMAND REQUIRED" }, { 0x04, 0x03, REC_ABORT, "LOGICAL DRIVE NOT READY - MANUAL INTERVENTION REQUIRED" }, { 0x05, 0x01, REC_RESET, "MEDIA LOAD - EJECT FAILED" }, { 0x06, 0x00, REC_ABORT, "NO REFERENCE POSITION FOUND (disk may be upside-down)" }, { 0x09, 0x00, REC_RESET, "TRACK FOLLOWING ERROR" }, { 0x09, 0x01, REC_RESET, "TRACKING SERVO FAILURE" }, { 0x09, 0x02, REC_RESET, "FOCUS SERVO FAILURE" }, { 0x09, 0x03, REC_RESET, "SPINDLE SERVO FAILURE" }, { 0x11, 0x00, REC_RESET, "UNRECOVERED READ ERROR" }, { 0x11, 0x06, REC_RESET, "CIRC UNRECOVERED ERROR" }, { 0x15, 0x00, REC_RESET, "RANDOM POSITIONING ERROR" }, { 0x15, 0x01, REC_RESET, "MECHANICAL POSITIONING OR CHANGER ERROR" }, { 0x15, 0x02, REC_RESET, "POSITIONING ERROR DETECTED BY READ OF MEDIUM" }, { 0x17, 0x00, REC_IGNORE, "RECOVERED DATA WITH NO ERROR CORRECTION APPLIED" }, { 0x17, 0x01, REC_IGNORE, "RECOVERED DATA WITH RETRIES" }, { 0x17, 0x02, REC_IGNORE, "RECOVERED DATA WITH POSITIVE HEAD OFFSET" }, { 0x17, 0x03, REC_IGNORE, "RECOVERED DATA WITH NEGATIVE HEAD OFFSET" }, { 0x17, 0x04, REC_IGNORE, "RECOVERED DATA WITH RETRIES AND/OR CIRC APPLIED" }, { 0x17, 0x05, REC_IGNORE, "RECOVERED DATA USING PREVIOUS SECTOR ID" }, { 0x18, 0x00, REC_IGNORE, "RECOVERED DATA WITH ERROR CORRECTION APPLIED" }, { 0x18, 0x01, REC_IGNORE, "RECOVERED DATA WITH ERROR CORRECTION & RETRIES APPLIED" }, { 0x18, 0x02, REC_IGNORE, "RECOVERED DATA - THE DATA WAS AUTO-REALLOCATED" }, { 0x18, 0x03, REC_IGNORE, "RECOVERED DATA WITH CIRC" }, { 0x18, 0x04, REC_IGNORE, "RECOVERED DATA WITH L-EC" }, { 0x1A, 0x00, REC_ABORT, "PARAMETER LIST LENGTH ERROR" }, { 0x20, 0x00, REC_ABORT, "INVALID COMMAND OPERATION CODE" }, { 0x21, 0x00, REC_ABORT, "LOGICAL BLOCK ADDRESS OUT OF RANGE" }, { 0x24, 0x00, REC_ABORT, "INVALID FIELD IN COMMAND PACKET" }, { 0x26, 0x00, REC_ABORT, "INVALID FIELD IN PARAMETER LIST" }, { 0x26, 0x01, REC_ABORT, "PARAMETER NOT SUPPORTED" }, { 0x26, 0x02, REC_ABORT, "PARAMETER VALUE INVALID" }, { 0x28, 0x00, REC_IGNORE, "NOT READY TO READY TRANSITION, MEDIUM MAY HAVE CHANGED" }, { 0x29, 0x00, REC_IGNORE, "POWER ON, RESET OR BUS DEVICE RESET OCCURRED" }, { 0x2A, 0x00, REC_IGNORE, "PARAMETERS CHANGED" }, { 0x2A, 0x01, REC_IGNORE, "MODE PARAMETERS CHANGED" }, { 0x30, 0x00, REC_ABORT, "INCOMPATIBLE MEDIUM INSTALLED" }, { 0x30, 0x01, REC_ABORT, "CANNOT READ MEDIUM - UNKNOWN FORMAT" }, { 0x30, 0x02, REC_ABORT, "CANNOT READ MEDIUM - INCOMPATIBLE FORMAT" }, { 0x39, 0x00, REC_ABORT, "SAVING PARAMETERS NOT SUPPORTED" }, { 0x3A, 0x00, REC_ABORT, "MEDIUM NOT PRESENT" }, { 0x3F, 0x00, REC_IGNORE, "ATAPI CD-ROM DRIVE OPERATING CONDITIONS HAVE CHANGED" }, { 0x3F, 0x01, REC_IGNORE, "MICROCODE HAS BEEN CHANGED" }, { 0x40, 0x00, REC_IGNORE, "DIAGNOSTIC FAILURE ON COMPONENT #ascq" }, { 0x44, 0x00, REC_RESET, "INTERNAL ATAPI CD-ROM DRIVE FAILURE" }, { 0x4E, 0x00, REC_ABORT, "OVERLAPPED COMMANDS ATTEMPTED" }, { 0x53, 0x00, REC_RESET, "MEDIA LOAD OR EJECT FAILED" }, { 0x53, 0x02, REC_ABORT, "MEDIUM REMOVAL PREVENTED" }, { 0x57, 0x00, REC_RESET, "UNABLE TO RECOVER TABLE OF CONTENTS" }, { 0x5A, 0x00, REC_IGNORE, "OPERATOR REQUEST OR STATE CHANGE INPUT (UNSPECIFIED)" }, { 0x5A, 0x01, REC_IGNORE, "OPERATOR MEDIUM REMOVAL REQUEST" }, { 0x63, 0x00, REC_ABORT, "END OF USER AREA ENCOUNTERED ON THIS TRACK" }, { 0x64, 0x00, REC_ABORT, "ILLEGAL MODE FOR THIS TRACK" }, { 0xB9, 0x00, REC_IGNORE, "PLAY OPERATION ABORTED" }, { 0xBF, 0x00, REC_IGNORE, "LOSS OF STREAMING" } /* prostate trouble? */ }; static const char *sense_key[] = { "NO SENSE", "RECOVERED ERROR", "NOT READY", "MEDIUM ERROR", "HARDWARE ERROR", "ILLEGAL REQUEST", "UNIT ATTENTION", "DATA PROTECT", "BLANK CHECK", "vendor specific", "COPY ABORTED", "ABORTED COMMAND", "obsolete", "VOLUME OVERFLOW", "MISCOMPARE", "reserved" }; /**/ uint8_t ascq_valid, ascq, asc, key; unsigned j; ascq_valid = (sense & 0x01) ? 1 : 0; sense >>= 8; ascq = (uint8_t)sense; sense >>= 8; asc = (uint8_t)sense; sense >>= 8; key = (uint8_t)sense; /* display sense key value and message */ printf("scsi_dump_sense(): sense key=%u", key); if(key < COUNT(sense_key)) printf(" (%s)", sense_key[key]); /* display Additional Sense Code value */ printf(",\n\tASC=%u", asc); /* find message corresponding to ASC (if any) and display it */ for(j = 0; j < COUNT(scsi_error); j++) { if(scsi_error[j].asc == asc && (asc == 0x40 || scsi_error[j].ascq == ascq)) break; } if(j < COUNT(scsi_error)) { printf(" (%s)", scsi_error[j].msg); j = scsi_error[j].recovery; } else { printf(" (?)"); j = REC_IGNORE; } /* display Additional Sense Code Qualifier value, if valid */ if(ascq_valid) printf(", ASCQ=%u", ascq); printf("\n"); /* return recovery value */ return j; } /****************************************************************************/ static int demo_usb_disk(const usb_dev_t *d) { char pkt[16], buf[1024]; unsigned want, j, ns; /* ns = number of sectors */ uint32_t lba, sense; int i; // if((i = msc_get_max_lun(d, 0)) < 0) // { //printf("msc_get_max_lun() failed\n"); // return i; // } //printf("USB disk has %u logical units\n", i + 1); //if((i = msc_reset(d, 0)) != 0) //{ // printf("msc_reset() failed\n"); // return -1; //} printf("\nREQUEST SENSE\n"); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x03; pkt[4] = 14; want = 14; if((i = usb_scsi_read(d, pkt, buf, want)) < 0) return i; sense = buf[2] & 0x0F; /* sense key */ j = 8 + buf[7]; if(j < 13) sense <<= 24; else { sense <<= 8; sense |= buf[12]; /* ASC */ if(j < 14) sense <<= 16; /* ASCQ=0, ASCQ valid=0 */ else { sense <<= 8; sense |= buf[13]; /* ASCQ */ sense <<= 8; sense |= 0x01; /* ASCQ valid */ } } scsi_dump_sense(sense); /* MBR/partition table */ lba = 0; ns = 1; printf("\nREAD(10): %u sector(s), lba=%lu\n", ns, lba); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x28; write_be32(&pkt[2], lba); /* SCSI is big-endian */ write_be16(&pkt[7], ns); want = 512 * ns; if((i = usb_scsi_read(d, pkt, buf, want)) < 0) return i; // dump(&buf[256], 256); dump(buf, 512); /* start of first partition */ lba = read_le32(&buf[446 + 16 * 0 + 8]); /* bootsector, if any */ ns = 2; printf("\nREAD(10): %u sector(s), lba=%lu\n", ns, lba); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x28; write_be32(&pkt[2], lba); write_be16(&pkt[7], ns); want = 512 * ns; if((i = usb_scsi_read(d, pkt, buf, want)) < 0) return i; // dump(&buf[16 * 20], 16 * 13); dump(buf, 512); #if 1 printf("\nINQUIRY\n"); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x12; want = 96; if((i = usb_scsi_read(d, pkt, buf, want)) < 0) return i; dump(buf, i); #endif printf("\nREAD CAPACITY\n"); memset(pkt, 0, sizeof(pkt)); pkt[0] = 0x25; want = 8; if((i = usb_scsi_read(d, pkt, buf, want)) < 0) return i; printf("%lu sectors, %lu bytes each\n", read_be32(buf), read_be32(&buf[4])); return 0; } /****************************************************************************/ static int demo(usb_dev_t *d) { usb_iface_t *iface; usb_config_t *cfg; unsigned c, i; int err; /* enumerate interfaces; look for devices that we support */ for(c = 0; c < d->num_configs; c++) { cfg = &d->config[c]; for(i = 0; i < cfg->num_ifaces; i++) { iface = &cfg->iface[i]; /* class 8 = mass storage, subclass 6 = SCSI transparent, protocol 0x50 = bulk-only */ if(iface->_class == 8 && iface->subclass == 6 && iface->proto == 0x50) { printf("Found USB mass storage device\n"); if((err = usb_set_config(d, c + 1)) != 0) return err; if((err = usb_set_iface(d, i)) != 0) return err; return demo_usb_disk(d); } /* class 7 = printer, subclass 1 = printer (?), protocol 2 = bidirectional LPT emulation */ else if(iface->_class == 7 && iface->subclass == 1) { char buf[512]; unsigned j; if((err = usb_setup(d, /* bmReqType */ 0x80 | 0x20 | 0x01, /* in, type=class, target=interface */ /* bRequest */ 0, /* wValue */ 1, /* config index */ /* wIndex */ 0, /* interface & alternate setting (?) */ buf, /* wLength */ 2)) != 0) return err; j = read_be16(buf); if(j > sizeof(buf)) j = sizeof(buf); if((err = usb_setup(d, /* bmReqType */ 0x80 | 0x20 | 0x01, /* in, type=class, target=interface */ /* bRequest */ 0, /* wValue */ 1, /* config index */ /* wIndex */ 0, /* interface & alternate setting (?) */ buf, /* wLength */ j)) != 0) return err; dump(buf, j); } } } return 0; } /****************************************************************************/ #if defined(__DJGPP__) #include /* __djgpp_nearptr_enable() */ #include /* _CRT0_FLAG_..., _crt0_startup_flags */ int _crt0_startup_flags = _CRT0_FLAG_LOCK_MEMORY; #endif int main(void) { static usb_dev_t dev0, dev1; /* static only to zero them */ usb_dev_t *d; int i; #if defined(__DJGPP__) /* turn off data segment limit, for nearptr access */ if(!(_crt0_startup_flags & _CRT0_FLAG_NEARPTR)) { if(!__djgpp_nearptr_enable()) { printf("Error: can not enable near pointer " "access (WinNT/2k/XP?)\n"); return 1; } } #endif if(uhci_init()) return 1; printf("Polling for USB hardware; press Esc to quit\n"); while(!kbhit()) { if((i = uhci_poll()) == 0) continue; d = (i & 1) ? &dev1 : &dev0; if(i < 0) { printf("\nDevice disconnected from port %d\n", i & 1); usb_destroy_dev(d); continue; } printf("\nDevice connected to port %d\n", i & 1); d->low_speed = (i & 0x40) ? 1 : 0; if(usb_enumerate(d) == 0) { printf("\n"); usb_dump(d); printf("\n"); (void)demo(d); printf("OK\n"); } } if(getch() == 0) (void)getch(); return 0; }