How are we supposed to send commands to USB flash drives?
-
- 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?
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.
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.
Re: How are we supposed to send commands to USB flash drives
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.
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.
-
- 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
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.
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.:
Note that are not actual addresses, but neither it is important.
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
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
Last edited by glauxosdev on Mon Mar 09, 2015 9:17 am, edited 1 time in total.
Re: How are we supposed to send commands to USB flash drives
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.
EDIT: I have just taken a look at you code. It's very short, porbably that's not enough. ehci2.asm is 2500+ lines.
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
Last edited by bigbob on Sat Jan 31, 2015 2:11 am, edited 4 times in total.
-
- 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
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
I hope it will help me. If I run into trouble, I'll post here.
Regards,
glauxosdev
Re: How are we supposed to send commands to USB flash drives
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!
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!
-
- 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
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...
Is the main function ehci_init_msd ?
Sorry if I am a bit annoying...
Re: How are we supposed to send commands to USB flash drives
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.glauxosdev wrote:but where do you init the ehci controller?
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
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.
-
- 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
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?
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?
Re: How are we supposed to send commands to USB flash drives
This information is given in the ehci QH connected to EP0:glauxosdev wrote:For example if I send GET_CONFIG_DESCRIPTOR request,
how the ehci controller knows which is the target port/device?
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;
Code: Select all
ehci_createQH(transfer->data, paging_getPhysAddr(transfer->data), firstTransaction->qTD, 0, transfer->device->num, transfer->endpoint->address, transfer->packetSize);
http://www.henkessoft.de/OS_Dev/OS_Dev3.htm (OSDEV)
http://www.c-plusplus.de/forum/viewforu ... is-62.html
irc.euirc.net #PrettyOS
http://www.c-plusplus.de/forum/viewforu ... is-62.html
irc.euirc.net #PrettyOS
-
- 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
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
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
Re: How are we supposed to send commands to USB flash drives
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
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
http://www.henkessoft.de/OS_Dev/OS_Dev3.htm (OSDEV)
http://www.c-plusplus.de/forum/viewforu ... is-62.html
irc.euirc.net #PrettyOS
http://www.c-plusplus.de/forum/viewforu ... is-62.html
irc.euirc.net #PrettyOS
-
- 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
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.
What about high-speed devices? The specification says port number is ignored then.
Do I misunderstand something?
Edit: I try bulk transactions.
-
- 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
To answer myself, we enable ports through the pci configuration space.
An enabled high-speed connected port must read 0x1005.
An enabled high-speed connected port must read 0x1005.
Re: How are we supposed to send commands to USB flash drives
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:
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).
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()
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).