Sunday 4 January 2015

USB Mass Storage Device Enumeration, and usbmon

An usbmon text output dissection

Some quick commands to before running usbmon:

# mount -t debugfs none_debugs /sys/kernel/debug
# modprobe usbmon
# cat /proc/bus/usb/devices
/*to find out the usb devices number on the usb bus*/


# cat /sys/kernel/debug/usb/usbmon/2u > /tmp/mon.out
As an example, a control packet:

f4ae6f40 1217192721 S Co:2:018:0 s 21 ff 0000 0000 0000 0

f4ae6f40 - URB Tag

1217192721 - Timestamp in microseconds


S - Event Type, S is submission


Co:2:018:0 - "Address". It consists of four fields, separated by colons: URB type and direction, Bus number, Device address, and Endpoint number. 
Co is Control output.

s - URB Status. This is a letter, so 's' is Setup Tag of control packet. It can also be several numbers separated by colons.



21 ff 0000 0000 0000 - Setup packet, if present, consists of 5 words: one of each for bmRequestType, bRequest, wValue, wIndex, wLength

0 - Data Length. For submissions, this is the requested length. For callbacks,this is the actual length.


Now we will look at mass storage device enumeration within the USB gadget framework. The USB gadget framework is available in Linux and U-boot.

USB halt and wedge feature:


Two types of events may cause a USB halt condition.

1) The device did not receive a handshake packet from USB host after communication.
2) The device receive set halt request from the host

Set halt and set wedge are basically the same, with the difference that, the device will ignore clear halt request from the host if device is in set wedge condition.

USB mass storage gadget enumeration steps:

The USB code can be divided into two parts: Usb Mass storage gadget code and Usb device controller code.

Firstly, the host PC sends the setup data to get descriptors from Usb device. It is performed using control transfer.


.
So the Usb chip receives it and interrupt is triggered. In Usb device controller code,
myudc_isr(void) [the interrupt handler]

reads the IVECT register and gets 0x00 value. It passes control to gadget code
fsg_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)

The setup data is processed, the response is prepared. If there is data to send out to EP0, the device controller function
my_ep_queue(struct usb_ep *ep, struct usb_request *req, gfp_t gfp_flags)

is called to send out the data.

After device, configuration, interface, endpoint, string descriptors are received by the host PC, the host PC detects the Usb device as a mass storage device (that is for a Usb mass storage device).

Device Descriptors:
As an example, the device descriptor is dissected here.

First Byte : bLength (the size of the descriptors in bytes) 0x12
Second Byte: bDescriptor (device descriptor)                 0x1
3rd-4th Byte: bcdUSB (USB spec number)                   0x0002
5th Byte: bDeviceClass (class code)                              0x00
6th Byte: bDeviceSubClass (subclass code)                   0x00
7th Byte: bDeviceProtocol (protocol code)                   0x00
8th Byte: bMaxPacketSize (max. packet size)                0x40
9-10 Byte: idVendor (vendor ID)                                  0x2505
11-12 Byte: idProduct (product ID)                               0xA5A4
13-14 Byte: bcdDevice (device release number)             0x0103
15th byte: iManufacturer (index of manufacturer string desc)      0x01
16th byte: iProduct (index of product string desc)                      0x02
17th byte: iSerialNumber (index of serial num. string desc)         0x03
18th byte: bNumConfigurations (number of possible config.)      0x01

Next step, the host PC will send SCSI commands to Usb device. The SCSI command is wrapped in CBW and sent using bulk transfer.


 So the Usb chip receives it and interrupt is triggered. In Usb device controller code,
myudc_isr(void) [the interrupt handler]

reads the IVECT register and gets 0x28 value for EP1 OUT IRQ. The data is read from FIFO, and sent to gadget function
bulk_out_complete(struct usb_ep *ep, struct usb_request *req)

in gadget code. This function will wake up thread in get_next_command((struct fsg_dev *fsg) and the thread will process the SCSI command. The get_next_command() is waiting for a buffer, which must be the same as the buffer returned by interrupt handler.

Again, if there is data to be replied to host PC, the device controller function
my_ep_queue(struct usb_ep *ep,  struct usb_request *req, gfp_t gfp_flags)
is called to send data to EP1 IN using bulk transfer.

After that, for every SCSI command, the gadget code will call gadget function
send_status(struct fsg_dev *fsg)

to prepare the CSW, and CSW is sent to the host PC using device controller function
my_ep_queue(struct usb_ep *ep,  struct usb_request *req, gfp_t gfp_flags)

 via EP1 IN.

The Transfer Protocol:

The Bulk Only Transport (BOT) is defined by Usb mass storage specification. It defines how Usb host can use bulk transfer to send commands and receives response from Usb device.

In the first transfer, the host sends a command in a structure called a Command Block Wrapper (CBW). The CBW is 31 byte in length. Many CBWs are followed by a transfer that contains data sent to the host or device.
For example, the CBW structure of a SCSI Inquiry command:

dCBWSignature  0x55 0x53 0x42 0x43
dCBWTag
dCBWDataTransferLength   0x24
bmCBWFlags
bCBWLUN
bCBWCBLength             0x06 (The valid length of CBWCB in bytes)
Operation Code               0x12
enableVitalProductData    false
commandSupportData       0x0
page or opcode                  0x0
allocation length                0x24
control                              0x0

In the final transfer, the device returns status in a structure called a Command Status Wrapper (CSW).

CSW structure:

dCSWSignature    0x55 0x53 0x42 0x43
dCSWTag
dCSWDataResidue    0x0
bCSWStatus               0x0

The SCSI Commands:

The SCSI commands used by the Usb mass storage device are from SCSI Primary Commands (SPC-2) and SCSI Block Commands (SBC-2).

Some of the commonly used commands, together with the command codes, are listed below.

SCSI Inquiry                0x12
SCSI Request Sense    0x3
SCSI Read Capacity    0x25
SCSI Read 10             0x28
SCSI Mode Sense 6    0x1A
SCSI Mode Select 6    0x15
SCSI Test Unit Ready  0x00

SD Card Partition Table:

The SD card will try to mimic a hard disk.

The MBR is located at offset 0x0, size is 512 bytes.

                      boot code (first 446 bytes)
                      partition #1  0x1BE (each partition is 16 bytes)
   MBR           partition #2
                       partition #3
                       partition #4
                       0xaa55   0x1FE  (signature)

Partition table info: (from offset 0x1BE)

0x00  0x80 if bootable, else 0x0
0x01  start of partition in CHS
0x04  type of partition
0x05  end of partition
0x08  relative offset to the partition in LBA (Partition LBA begin)
0x0C  size of the partition

For example, if the relative offset in LBA is 0x2000, it means the start of the boot sector is 0x2000.

At 0x2000, it is the FAT boot record (if formatted in FAT file system).

For example, FAT32 boot record:

0x00  3 bytes    0xeb0090       jump instruction
0x03  8 bytes    MSDOS5.0    OEM name
0x0B  2 bytes    0x0200           bytes per sector
0x0D  1 byte      0x08               sectors per cluster
0x0E   2 bytes     0x20               number of reserved sector (usually 0x20)
0x10   1 byte      0x2                 number of FATs
0x11   2 bytes                           N/A
0x13   2 bytes                            N/A
0x15   1 byte    0xF8                 media descriptors(F8 for hard disk)
0x16   2 bytes                            N/A
0x18   2 bytes                           sectors per track
0x1A  2 bytes                             number of heads
0x24   4 bytes                         sectors per FAT
0x2C   4 bytes                          root directory first cluster
0x5A  420 bytes                      bootstrap code
0x1fe  2 bytes    0x55aa           end of boot sector mark

Important info that are needed for accessing the FAT32 filesystem:


(unsigned long)fat_begin_lba = Partition_LBA_Begin + Number_of_Reserved_Sectors;
(unsigned long)cluster_begin_lba = Partition_LBA_Begin + Number_of_Reserved_Sectors + (Number_of_FATs * Sectors_Per_FAT);
(unsigned char)sectors_per_cluster = sectors_per_cluster;
(unsigned long)root_dir_first_cluster = root_directory_first_cluster;