How are we supposed to send commands to USB flash drives?

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
glauxosdev
Member
Member
Posts: 119
Joined: Tue Jan 20, 2015 9:01 am
Libera.chat IRC: glauxosdever

How are we supposed to send commands to USB flash drives?

Post by glauxosdev »

Hello. As the subject says, I am having trouble sending SCSI commands to USB flash drives.

I have read about the CBW (Command Block Wrapper), and the TD (Transfer Descriptor).
I don't understand how they interact.

Also, I have looked at TatOS and PrettyOS source codes, but they seem too compicated.
Maybe it would be better if usb was in a single file?

Thank you for your attention!

Edit: I'm in 32-bits Protected Mode.
bigbob
Member
Member
Posts: 122
Joined: Tue Oct 01, 2013 2:50 am
Location: Budapest, Hungary
Contact:

Re: How are we supposed to send commands to USB flash drives

Post by bigbob »

EDIT:
Can you correctly enumerate devices, get device descriptor and string descriptors?
If not I can't help with xHCI but I implemented EHCI driver for reading/writing from/to usb-pendrives (sectors and files from FAT32).
Sending SCSI-commands are the same for both.
glauxosdev
Member
Member
Posts: 119
Joined: Tue Jan 20, 2015 9:01 am
Libera.chat IRC: glauxosdever

Re: How are we supposed to send commands to USB flash drives

Post by glauxosdev »

Thank you for your reply!

Basically, I use the BIOS while in Real Mode to find the PCI address of boot device.
Then, I try to init the EHCI controller (not sure if properly), I have prepared a CBW,
a command TD and a data TD, but I'm not sure how to use them.

Edit: Don't base your code at this code below. It's awfully wrong.

Code: Select all


glaux_usb_init:

; -------------------------------------------------------------------------
; description: inits ehci controller
; input: {none}
; output: dword [usb_base_0], usb base registers address
;         dword [usb_opbase_0], usb base operational register address
;         dword [usb_eecp_0], usb base extended capability register address
; altered: eax, ecx, edx
;          dword [pci_address]
;          dword [pci_offset]
;          dword [pci_data]
; -------------------------------------------------------------------------

    .set_device_bdf:
        mov eax, dword [bus_dev_fun_0]
        mov dword [pci_address], eax

    .enable_bus_master:
        mov dword [pci_offset], 4
        call glaux_pci_read_dword
        or dword [pci_data], 6
        call glaux_pci_write_dword
    
    .save_base_address:
        mov dword [pci_offset], 10h
        call glaux_pci_read_dword
        mov eax, dword [pci_data]
        and eax, 0FFFFFF00h
        mov dword [usb_base_0], eax
    
    .save_operational_base_address:
        movsx edx, byte [eax]
        add edx, eax
        mov dword [usb_opbase_0], edx
    
    .get_capability_parameters:
        mov edx, dword [usb_base_0]
        add edx, 8
        mov eax, dword [edx]
    
    .save_extended_capability_pointer:
        shr eax, 8
        and eax, 0FFh
        mov dword [usb_eecp_0], eax
    
    .check_bios_control:
        mov dword [pci_offset], eax
        call glaux_pci_read_dword
        test dword [pci_data], 10000h
        jz .clear_conf_flag
    
    .set_os_control:
        or dword [pci_data], 1000000h
        mov eax, dword [usb_eecp_0]
        mov dword [pci_offset], eax
        call glaux_pci_write_dword
    
    .clear_conf_flag:
        mov eax, dword [usb_opbase_0]
        add eax, 40h
        mov dword [eax], 0

    .return:
        ret

; ==============================================================================

glaux_usb_read_capacity:

    .return:
        ret

; ==============================================================================

glaux_usb_read_sectors:

    .compute_length:
        mov eax, dword [usb_count]
        shl eax, 9                           ; same as imul eax, 512
        mov dword [usb_cbw.length], eax
    
    .copy_lba_block:
        mov eax, dword [usb_lba]
        bswap eax
        mov dword [usb_cbw.lba], eax
    
    .copy_block_count:
        mov ax, word [usb_count]
        mov byte [usb_cbw.count], ah
        mov byte [usb_cbw.count+1], al
    
    .copy_buffer_pointer:
        mov eax, dword [ram_address]
        mov dword [usb_TD_cmd.buffer], eax
    
    .clear_scsi_csw:
        mov ecx, 13
        mov ebx, usb_csw
    
    .loop_clear:
        mov byte [ebx], 0FFh
        add ebx, 1
        sub ecx, 1
        jnz .loop_clear

    .return:
        ret

; ==================================================

usb_cbw:

    .signature      dd 43425355h
    .tag            dd 012345678h
    .length         dd 0
    .direction      db 80h
    .lun            db 0
    .size           db 0Ah
    .oper_code      db 28h
    .flags          db 0
    .lba            dd 0                ; big endian
    .group_num      db 0
    .count          dw 0                ; big endian
    .control        db 0
    .pad:
        times 6 db 0
        
usb_csw:

    .pad:
        times 13 db 0

; ==============================================================================

usb_TD_cmd:

    .cbw          dd usb_cbw
    .size         dd 31
    .fullspeed    dd 0
    .pid_out      dd 0
    .toggle_out   dd 0
    .endpoint_out dd 0
    .address      dd 0

; ==============================================================================

usb_TD_data:

    .buffer       dd 0
    .length       dd 0
    .fullspeed    dd 0
    .pid_in       dd 0
    .toggle_in    dd 0
    .endpoint_in  dd 0
    .address      dd 0

I know it's complicated, and probably it will get more!

There are some variables, that are not included in this source file,
instead they are referenced memory addresses e.g.:

Code: Select all

usb_lba equ 00018000h
ram_address equ 0001A000h
Note that are not actual addresses, but neither it is important. :)
Last edited by glauxosdev on Mon Mar 09, 2015 9:17 am, edited 1 time in total.
bigbob
Member
Member
Posts: 122
Joined: Tue Oct 01, 2013 2:50 am
Location: Budapest, Hungary
Contact:

Re: How are we supposed to send commands to USB flash drives

Post by bigbob »

I strongly recommend "Benjamin David Lunt - USB: The Universal Serial Bus".
With that book, it's possible to have Flash-disk support in two months.
Without it, it would take maybe a year.
It's almost impossible to explain it here, so if you don't want to buy that book for some reason, I can only offer you my code. My OS is also in assembly, just like TatOS, and probably equally difficult.
https://sites.google.com/site/forthoperatingsystem/
QH: Queue Head
TD: Transfer Descriptor
In ehci2.asm the datatoggle-bit (i.e. dt) is used in the TDs (in ehci.asm dt is used in the QH), so I recommend ehci2.asm to start with.
I can't understand your code now, I am in the middle of implementing the HD Audio driver.

EDIT:
If you manage to get the device-, string-, configuration-descriptors, then this is how to send SCSI commands:
The EndPt in the QH is the endpoint-number. It's zero for control transfers(getting the above mentioned descriptors are control transfers). You can get the Bulk In/Out EndPt of the pendrive from the Configuration-descriptor.
In case of bulk-transfers, the Bulk In/Out EndPts need to be used in the QH. For example the In-Endpt number is 1 and Out-Endpt is 2 (usually).
So, sending an SCSI-command e.g. TESTUNIT (see below), you need to set the Out-Endpoint number of the pendrive in the QH, and then send the data of TESTUNIT (just like sending the request for the Device-descriptor, but there are no SetupTD and StatusTD). You need to toggle dt (datatoggle-bit in the TD). The dt-bit is for the endpoint, so you need to have two variables, one for the dt_out, and one for dt_in. When you send the data, you need to toggle the dt_out (and the dt_in if you read).
We need the data-toggle-bit in variables, because we need to remember its value, and toggle it always in case of a new TD. I implemented USB several months ago, so I hope, I remember correctly.

Code: Select all

ehci_testunit_cbw	dd 0x43425355	;dCBWSignature
					dd 0xAABBCCDD	;dCBWTag
					dd 0			;dCBWDataTransferLength
					db 0			;bmCBWFlags 0x80=Device2Host, 00=Host2Device
					db 0			;bCBWLun
					db 6			;bCBWCBLength 
					db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
EDIT: I have just taken a look at you code. It's very short, porbably that's not enough. ehci2.asm is 2500+ lines.
Last edited by bigbob on Sat Jan 31, 2015 2:11 am, edited 4 times in total.
glauxosdev
Member
Member
Posts: 119
Joined: Tue Jan 20, 2015 9:01 am
Libera.chat IRC: glauxosdever

Re: How are we supposed to send commands to USB flash drives

Post by glauxosdev »

Thank you very much! I have just downloaded your code (I must admit it is a bit difficult for a newcomer to find it).
I hope it will help me. If I run into trouble, I'll post here.

Regards,
glauxosdev
bigbob
Member
Member
Posts: 122
Joined: Tue Oct 01, 2013 2:50 am
Location: Budapest, Hungary
Contact:

Re: How are we supposed to send commands to USB flash drives

Post by bigbob »

Good luck with it. Of course, I will help if you have questions regarding my code (and USB in general) :) .
I forgot to mention that the one who buys the Lunt-book can request the C-source-code from the author.
It was huge help. It shows how to initialize the EHCI-controller, get the descriptors, but there is nothing in it that's SCSI-related.
A minor problem is that apart from the C-code that shows how to init the EHCI, the other parts of the code(e.g. get the device-descriptor) is optimized, so not that easy to understand.

By the way, tatOS helped a lot too!
glauxosdev
Member
Member
Posts: 119
Joined: Tue Jan 20, 2015 9:01 am
Libera.chat IRC: glauxosdever

Re: How are we supposed to send commands to USB flash drives

Post by glauxosdev »

Hello again, I have looked through your code, but where do you init the ehci controller? :?
Is the main function ehci_init_msd ?

Sorry if I am a bit annoying... :)
bigbob
Member
Member
Posts: 122
Joined: Tue Oct 01, 2013 2:50 am
Location: Budapest, Hungary
Contact:

Re: How are we supposed to send commands to USB flash drives

Post by bigbob »

glauxosdev wrote:but where do you init the ehci controller?
The very first function is ehci_enum which finds the EHCI controller on the PCI-bus and then calls ehci_process which initializes the EHCI-controller and calls ehci_get_descriptor (it gets device- and string-descriptors and also sets device-address). In the C-source the function that inits the controllers was called ..._process, that's why I also call it this way.

You can have a working EHCI-flash-driver in a few days by copying code, but the code hasn't been thoroughly tested.
Initializing the controller and getting the descriptors should be ok, but the msd-code (Mass Storage Device; i.e. pendrive or winchester) has only been tested with usb-disks having mps(i.e. MaxPacketSize)=512. In case of a bug, you will have to fix it.
I forgot to mention that the mps of the EndPts also need to be saved from the descriptors, and used in bulk-transfers.

By the way, in FORTH a command that can be executed (e.g.from the command line) is called a WORD.
Check usb_enum in forth.asm:

Code: Select all

usb_enum_     dd   _usb_enum
              db   "usbenum", 0, 0
The name of the WORD is "usbenum" (it's case-insensitive), if the user enters it, then the function _usb_enum will be called.
Its implementation is in forthusb.asm (calls usb.asm::usb_enum that calls ehci_enum)

EDIT: I couldn't get USB working with Virtualbox, so I had to do it on real HW.
glauxosdev
Member
Member
Posts: 119
Joined: Tue Jan 20, 2015 9:01 am
Libera.chat IRC: glauxosdever

Re: How are we supposed to send commands to USB flash drives

Post by glauxosdev »

Hello again!

I still have problems with USB, and I wonder how to choose
a specific port to get device-config descriptors.
I'm really in doubt what to do...

Thanks again for your attention! :)

Edit: For example if I send GET_CONFIG_DESCRIPTOR request,
how the ehci controller knows which is the target port/device?
User avatar
ehenkes
Member
Member
Posts: 124
Joined: Mon Mar 23, 2009 3:15 am
Location: Germany
Contact:

Re: How are we supposed to send commands to USB flash drives

Post by ehenkes »

glauxosdev wrote:For example if I send GET_CONFIG_DESCRIPTOR request,
how the ehci controller knows which is the target port/device?
This information is given in the ehci QH connected to EP0:

Code: Select all

typedef struct ehci_qhd
{
    uint32_t   horizontalPointer;
    uint32_t   deviceAddress       :  7; // <== !!!
    uint32_t   inactive            :  1;
    uint32_t   endpoint            :  4;
    uint32_t   endpointSpeed       :  2;
    uint32_t   dataToggleControl   :  1;
    uint32_t   H                   :  1;
    uint32_t   maxPacketLength     : 11;
    uint32_t   controlEndpointFlag :  1;
    uint32_t   nakCountReload      :  4;
    uint8_t    interruptScheduleMask;
    uint8_t    splitCompletionMask;
    uint16_t   hubAddr             :  7;
    uint16_t   portNumber          :  7;
    uint16_t   mult                :  2;
    uint32_t   current;
    ehci_qtd_t qtd; // transfer overlay
} __attribute__((packed)) ehci_qhd_t;
Function:

Code: Select all

ehci_createQH(transfer->data, paging_getPhysAddr(transfer->data), firstTransaction->qTD, 0, transfer->device->num, transfer->endpoint->address, transfer->packetSize);
glauxosdev
Member
Member
Posts: 119
Joined: Tue Jan 20, 2015 9:01 am
Libera.chat IRC: glauxosdever

Re: How are we supposed to send commands to USB flash drives

Post by glauxosdev »

That struct QH passes information to your function named "ehci_createQH".
But I would like to know about the actual QH layout, not struct QH.
This document says nothing about port definition in QH.
Beside that, it seems either incomplete, either completely wrong.
I'm almost positive it is the second case, because it conficts with TatOS usb driver,
though I have not found anything better...

Maybe I misread something?

Regards,
glauxosdev
User avatar
ehenkes
Member
Member
Posts: 124
Joined: Mon Mar 23, 2009 3:15 am
Location: Germany
Contact:

Re: How are we supposed to send commands to USB flash drives

Post by ehenkes »

The actual QH layout you will find in detail in the ehci specification (Rev. 1.0, March 12, 2002):
http://www.intel.com/content/www/us/en/ ... r-usb.html
Cf. at chapter "3.6 Queue Head".
Figure 3-7 shows the 3 HC Read-Only Dwords and the overlay (part of QH and the qTD) structure that is HC read/write and called the "transaction working space".

PrettyOS separates this into the core QH (first four Dwords) and the qTD struct: http://sourceforge.net/p/prettyos/code/ ... hciQHqTD.h
glauxosdev
Member
Member
Posts: 119
Joined: Tue Jan 20, 2015 9:01 am
Libera.chat IRC: glauxosdever

Re: How are we supposed to send commands to USB flash drives

Post by glauxosdev »

Ok, I see I can set port number in the QH, but only for low-speed and full-speed devices.
What about high-speed devices? The specification says port number is ignored then.
Do I misunderstand something?

Edit: I try bulk transactions.
glauxosdev
Member
Member
Posts: 119
Joined: Tue Jan 20, 2015 9:01 am
Libera.chat IRC: glauxosdever

Re: How are we supposed to send commands to USB flash drives

Post by glauxosdev »

To answer myself, we enable ports through the pci configuration space.
An enabled high-speed connected port must read 0x1005.
bigbob
Member
Member
Posts: 122
Joined: Tue Oct 01, 2013 2:50 am
Location: Budapest, Hungary
Contact:

Re: How are we supposed to send commands to USB flash drives

Post by bigbob »

You are persistent, that's good news :)
Getting the Device-Descriptor(DD) is a huge step ahead. It is still not clear to me whether you have managed to do that or not.
It was several months ago, when I implemented USB, so I forgot a lot. I had to check the code (I did it for a few minutes).

The C-code, that comes with the book, first initializes the controller. There are many things it does here, e.g. delays are very important, it waits 20ms after powering a port.
It gets the number of ports of the EHCI-controller and then:

Code: Select all

for (i=0; i<portsnum; i++)
	if (reset_port(i))
		get_desc()
In reset_port(port), the enable bit and status change bits are cleared (PortStatus+port*4) and then that Status-register is read, and see if there is a device attached from its value (the enable-bit will be set if device is a high-speed one).
EDIT: It seems to me, that the port-number is not used later. The port is enabled and later will be used without its number(or index).
I wouldn't be fair to publish more source-code from the book :)
Getting the other descriptors (e.g. Configuration) is the same but you need to send a different data-structure.

I think it's not a bad idea to discuss how to use the data-toggle in the QHs in this topic, because it is not in the book(using it in the TDs was already explained above). Later you(and others) may find it useful. Of course, this will only be helpful in case of bulk-transfers, where huge amount of data is transfered.
You need two QHs: one for in and one for out (the bit will be toggled for you). You insert QHin into the list of QHs in case of a read, and QHout in case of a write.
If you want to do a new transfer, you simply don't touch the value of the dt in the QH, you only need to update parts of the QH that are necessary.

We can transfer max. 0x5000 bytes per TD, if dt is used in the QH.
If used in TD, and MPS(Max Packet Size) of usb-drive is e.g. 512, we would need to use much more memory, and the software would be slower.
Let's assume that we would like to read 65535 sectors from USB-drive.
Using the dt-bit in the TD with mps=512 would need ~4Mb memory (size of TD-structure is 64 bytes).
Using the dt in QH, and transfering 0x5000 bytes per TD will be: 40* less.
It's also much faster not to create that many TDs.

We'll be able to help you only after you have managed to get the DD of an EHCI (i.e. high-speed) device. USB-2.0 external HUBs will also be found and their DDs can be retrieved, but a USB-keyboard or mouse can't because they are low-speed devices (UHCI or OHCI).
Post Reply