diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..695f6c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +mass_storage/debug.h diff --git a/demos/mass_storage_OLIMEX_STM32E407/main.c b/demos/mass_storage_OLIMEX_STM32E407/main.c index 028b7ef..c2173df 100644 --- a/demos/mass_storage_OLIMEX_STM32E407/main.c +++ b/demos/mass_storage_OLIMEX_STM32E407/main.c @@ -1,40 +1,263 @@ +#include "usb_msd.h" +#include "ch.h" +#include "hal.h" #include #include -#include "ch.h" -#include "hal.h" +/* endpoint index */ +#define USB_MS_DATA_EP 1 -#include "usb_msd.h" +/* USB device descriptor */ +static const uint8_t deviceDescriptorData[] = +{ + USB_DESC_DEVICE + ( + 0x0200, /* supported USB version (2.0) */ + 0x00, /* device class (none, specified in interface) */ + 0x00, /* device sub-class (none, specified in interface) */ + 0x00, /* device protocol (none, specified in interface) */ + 64, /* max packet size of control end-point */ + 0x0483, /* vendor ID (STMicroelectronics!) */ + 0x5740, /* product ID (STM32F407) */ + 0x0100, /* device release number */ + 1, /* index of manufacturer string descriptor */ + 2, /* index of product string descriptor */ + 3, /* index of serial number string descriptor */ + 1 /* number of possible configurations */ + ) +}; +static const USBDescriptor deviceDescriptor = +{ + sizeof(deviceDescriptorData), + deviceDescriptorData +}; + +/* configuration descriptor */ +static const uint8_t configurationDescriptorData[] = +{ + /* configuration descriptor */ + USB_DESC_CONFIGURATION + ( + 32, /* total length */ + 1, /* number of interfaces */ + 1, /* value that selects this configuration */ + 0, /* index of string descriptor describing this configuration */ + 0xC0, /* attributes (self-powered) */ + 50 /* max power (100 mA) */ + ), + + /* interface descriptor */ + USB_DESC_INTERFACE + ( + 0, /* interface number */ + 0, /* value used to select alternative setting */ + 2, /* number of end-points used by this interface */ + 0x08, /* interface class (Mass Storage) */ + 0x06, /* interface sub-class (SCSI Transparent Storage) */ + 0x50, /* interface protocol (Bulk Only) */ + 0 /* index of string descriptor describing this interface */ + ), + + /* end-point descriptor */ + USB_DESC_ENDPOINT + ( + USB_MS_DATA_EP | 0x80, /* address (end point index | OUT direction) */ + USB_EP_MODE_TYPE_BULK, /* attributes (bulk) */ + 64, /* max packet size */ + 0x05 /* polling interval (ignored for bulk end-points) */ + ), + + /* end-point descriptor */ + USB_DESC_ENDPOINT + ( + USB_MS_DATA_EP | 0x00, /* address (end point index | IN direction) */ + USB_EP_MODE_TYPE_BULK, /* attributes (bulk) */ + 64, /* max packet size */ + 0x05 /* polling interval (ignored for bulk end-points) */ + ) +}; +static const USBDescriptor configurationDescriptor = +{ + sizeof(configurationDescriptorData), + configurationDescriptorData +}; + +/* Language descriptor */ +static const uint8_t languageDescriptorData[] = +{ + USB_DESC_BYTE(4), + USB_DESC_BYTE(USB_DESCRIPTOR_STRING), + USB_DESC_WORD(0x0409) /* U.S. english */ +}; +static const USBDescriptor languageDescriptor = +{ + sizeof(languageDescriptorData), + languageDescriptorData +}; + +/* Vendor descriptor */ +static const uint8_t vendorDescriptorData[] = +{ + USB_DESC_BYTE(22), + USB_DESC_BYTE(USB_DESCRIPTOR_STRING), + 'D', 0, 'e', 0, 'm', 0, 'o', 0, 'V', 0, 'e', 0, 'n', 0, 'd', 0, 'o', 0, 'r', 0 +}; +static const USBDescriptor vendorDescriptor = +{ + sizeof(vendorDescriptorData), + vendorDescriptorData +}; + +/* Product descriptor */ +static const uint8_t productDescriptorData[] = +{ + USB_DESC_BYTE(24), + USB_DESC_BYTE(USB_DESCRIPTOR_STRING), + 'D', 0, 'e', 0, 'm', 0, 'o', 0, 'P', 0, 'r', 0, 'o', 0, 'd', 0, 'u', 0, 'c', 0, 't', 0 +}; +static const USBDescriptor productDescriptor = +{ + sizeof(productDescriptorData), + productDescriptorData +}; + +/* Serial number descriptor */ +static const uint8_t serialNumberDescriptorData[] = +{ + USB_DESC_BYTE(26), + USB_DESC_BYTE(USB_DESCRIPTOR_STRING), + '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '0', 0, '1', 0 +}; +static const USBDescriptor serialNumberDescriptor = +{ + sizeof(serialNumberDescriptorData), + serialNumberDescriptorData +}; + +/* Handles GET_DESCRIPTOR requests from the USB host */ +static const USBDescriptor* getDescriptor(USBDriver* usbp, uint8_t type, uint8_t index, uint16_t lang) +{ + (void)usbp; + (void)lang; -/* - * Green LED blinker thread, times are in milliseconds. - */ -static WORKING_AREA(waThread1, 128); -static msg_t Thread1(void *arg) { - - (void)arg; - chRegSetThreadName("blinker"); - while (TRUE) { - palTogglePad(GPIOC, GPIOC_LED); - chThdSleepMilliseconds(500); - } + switch (type) + { + case USB_DESCRIPTOR_DEVICE: + return &deviceDescriptor; + + case USB_DESCRIPTOR_CONFIGURATION: + return &configurationDescriptor; + + case USB_DESCRIPTOR_STRING: + switch (index) + { + case 0: return &languageDescriptor; + case 1: return &vendorDescriptor; + case 2: return &productDescriptor; + case 3: return &serialNumberDescriptor; + } + } + + return 0; +} + +/* Handles global events of the USB driver */ +static void usbEvent(USBDriver* usbp, usbevent_t event) +{ + switch (event) + { + case USB_EVENT_CONFIGURED: + chSysLockFromIsr(); + msdConfigureHookI(usbp); + chSysUnlockFromIsr(); + break; + + case USB_EVENT_RESET: + case USB_EVENT_ADDRESS: + case USB_EVENT_SUSPEND: + case USB_EVENT_WAKEUP: + case USB_EVENT_STALLED: + default: + break; + } +} + +/* Configuration of the USB driver */ +const USBConfig usbConfig = +{ + usbEvent, + getDescriptor, + msdRequestsHook, + 0 +}; + +/* Turns on a LED when there is I/O activity on the USB port */ +static void usbActivity(bool_t active) +{ + if (active) + palSetPad(GPIOC, GPIOC_LED); + else + palClearPad(GPIOC, GPIOC_LED); } -int main(void) { - halInit(); - chSysInit(); +/* USB mass storage configuration */ +static USBMassStorageConfig msdConfig = +{ + &USBD2, + (BaseBlockDevice*)&SDCD1, + USB_MS_DATA_EP, + &usbActivity, + "DVendor", + "DProduct", + "0.1" +}; + +/* USB mass storage driver */ +USBMassStorageDriver UMSD1; + +int main(void) +{ + /* system & hardware initialization */ + halInit(); + chSysInit(); - sdcStart(&SDCD1, NULL); - sdcConnect(&SDCD1); + /* initialize the SD card */ + sdcStart(&SDCD1, NULL); + sdcConnect(&SDCD1); - //palSetPad(GPIOC, GPIOC_LED); + /* turn off the test LED */ + palClearPad(GPIOC, GPIOC_LED); - USBMassStorageDriver UMSD1; - msdInit(&USBD1, &SDCD1, &UMSD1); + /* initialize the USB mass storage driver */ + msdInit(&UMSD1); - chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO, Thread1, NULL); + /* start the USB mass storage service */ + msdStart(&UMSD1, &msdConfig); - while (TRUE) { + /* start the USB driver */ + usbDisconnectBus(&USBD2); chThdSleepMilliseconds(1000); - } + usbStart(&USBD2, &usbConfig); + usbConnectBus(&USBD2); + + /* watch the mass storage events */ + EventListener connected; + EventListener ejected; + chEvtRegisterMask(&UMSD1.evt_connected, &connected, EVENT_MASK(1)); + chEvtRegisterMask(&UMSD1.evt_ejected, &ejected, EVENT_MASK(2)); + + while (TRUE) + { + eventmask_t event = chEvtWaitOne(EVENT_MASK(1) | EVENT_MASK(2)); + if (event == EVENT_MASK(1)) + { + /* media connected */ + } + else if (event == EVENT_MASK(2)) + { + /* media ejected */ + } + } + + return 0; } diff --git a/demos/mass_storage_OLIMEX_STM32E407/readme.md b/demos/mass_storage_OLIMEX_STM32E407/readme.md index 2cdb823..4e5e8da 100644 --- a/demos/mass_storage_OLIMEX_STM32E407/readme.md +++ b/demos/mass_storage_OLIMEX_STM32E407/readme.md @@ -1,5 +1,3 @@ Mass Storage Demo for Olimex STM32E407 Uses SDC card + HS USB - -Note: not yet functional \ No newline at end of file diff --git a/mass_storage/usb_msd.c b/mass_storage/usb_msd.c index aeeb354..41f0188 100644 --- a/mass_storage/usb_msd.c +++ b/mass_storage/usb_msd.c @@ -1,170 +1,149 @@ -#include "ch.h" -#include "hal.h" #include "usb_msd.h" -static WORKING_AREA(waMassStorage, 1024); -static msg_t MassStorageThd(void *arg); -static void WaitForISR(USBMassStorageDriver *msdp); -static Thread *msdThd = NULL; +/* Request types */ +#define MSD_REQ_RESET 0xFF +#define MSD_GET_MAX_LUN 0xFE + +/* CBW/CSW block signatures */ +#define MSD_CBW_SIGNATURE 0x43425355 +#define MSD_CSW_SIGNATURE 0x53425355 + +/* Setup packet access macros */ +#define MSD_SETUP_WORD(setup, index) (uint16_t)(((uint16_t)setup[index + 1] << 8) | (setup[index] & 0x00FF)) +#define MSD_SETUP_VALUE(setup) MSD_SETUP_WORD(setup, 2) +#define MSD_SETUP_INDEX(setup) MSD_SETUP_WORD(setup, 4) +#define MSD_SETUP_LENGTH(setup) MSD_SETUP_WORD(setup, 6) + +/* Command statuses */ +#define MSD_COMMAND_PASSED 0x00 +#define MSD_COMMAND_FAILED 0x01 +#define MSD_COMMAND_PHASE_ERROR 0x02 + +/* SCSI commands */ +#define SCSI_CMD_TEST_UNIT_READY 0x00 +#define SCSI_CMD_REQUEST_SENSE 0x03 +#define SCSI_CMD_FORMAT_UNIT 0x04 +#define SCSI_CMD_INQUIRY 0x12 +#define SCSI_CMD_MODE_SENSE_6 0x1A +#define SCSI_CMD_START_STOP_UNIT 0x1B +#define SCSI_CMD_SEND_DIAGNOSTIC 0x1D +#define SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E +#define SCSI_CMD_READ_FORMAT_CAPACITIES 0x23 +#define SCSI_CMD_READ_CAPACITY_10 0x25 +#define SCSI_CMD_READ_10 0x28 +#define SCSI_CMD_WRITE_10 0x2A +#define SCSI_CMD_VERIFY_10 0x2F + +/* SCSI sense keys */ +#define SCSI_SENSE_KEY_GOOD 0x00 +#define SCSI_SENSE_KEY_RECOVERED_ERROR 0x01 +#define SCSI_SENSE_KEY_NOT_READY 0x02 +#define SCSI_SENSE_KEY_MEDIUM_ERROR 0x03 +#define SCSI_SENSE_KEY_HARDWARE_ERROR 0x04 +#define SCSI_SENSE_KEY_ILLEGAL_REQUEST 0x05 +#define SCSI_SENSE_KEY_UNIT_ATTENTION 0x06 +#define SCSI_SENSE_KEY_DATA_PROTECT 0x07 +#define SCSI_SENSE_KEY_BLANK_CHECK 0x08 +#define SCSI_SENSE_KEY_VENDOR_SPECIFIC 0x09 +#define SCSI_SENSE_KEY_COPY_ABORTED 0x0A +#define SCSI_SENSE_KEY_ABORTED_COMMAND 0x0B +#define SCSI_SENSE_KEY_VOLUME_OVERFLOW 0x0D +#define SCSI_SENSE_KEY_MISCOMPARE 0x0E + +#define SCSI_ASENSE_NO_ADDITIONAL_INFORMATION 0x00 +#define SCSI_ASENSE_WRITE_FAULT 0x03 +#define SCSI_ASENSE_LOGICAL_UNIT_NOT_READY 0x04 +#define SCSI_ASENSE_READ_ERROR 0x11 +#define SCSI_ASENSE_INVALID_COMMAND 0x20 +#define SCSI_ASENSE_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x21 +#define SCSI_ASENSE_INVALID_FIELD_IN_CDB 0x24 +#define SCSI_ASENSE_WRITE_PROTECTED 0x27 +#define SCSI_ASENSE_NOT_READY_TO_READY_CHANGE 0x28 +#define SCSI_ASENSE_FORMAT_ERROR 0x31 +#define SCSI_ASENSE_MEDIUM_NOT_PRESENT 0x3A + +#define SCSI_ASENSEQ_NO_QUALIFIER 0x00 +#define SCSI_ASENSEQ_FORMAT_COMMAND_FAILED 0x01 +#define SCSI_ASENSEQ_INITIALIZING_COMMAND_REQUIRED 0x02 +#define SCSI_ASENSEQ_OPERATION_IN_PROGRESS 0x07 -/* TODO: need a way of specifying the size of this */ -static uint8_t rw_buf[2][512]; - -inline uint32_t swap_uint32( uint32_t val ) { - val = ((val << 8) & 0xFF00FF00 ) | ((val >> 8) & 0xFF00FF ); - return ((val << 16) & 0xFFFF0000) | ((val >> 16) & 0x0000FFFF); -} - -#define swap_uint16(x) ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8)) - -#if !defined(MSD_RW_LED_ON) -#define MSD_RW_LED_ON() -#endif - -#if !defined(MSD_RW_LED_OFF) -#define MSD_RW_LED_OFF() -#endif - -/*===========================================================================*/ -/* USB related stuff. */ -/*===========================================================================*/ - -/* - * USB Device Descriptor. +/** + * @brief Response to a READ_CAPACITY_10 SCSI command */ -static const uint8_t msd_device_descriptor_data[18] = { - USB_DESC_DEVICE (0x0200, /* bcdUSB (2.0). */ - 0x00, /* bDeviceClass (None). */ - 0x00, /* bDeviceSubClass. */ - 0x00, /* bDeviceProtocol. */ - 0x40, /* Control Endpoint Size. */ - 0x0483, /* idVendor (ST). */ - 0x5742, /* idProduct. */ - 0x0100, /* bcdDevice. */ - 1, /* iManufacturer. */ - 2, /* iProduct. */ - 3, /* iSerialNumber. */ - 1) /* bNumConfigurations. */ -}; +PACK_STRUCT_BEGIN typedef struct { + uint32_t last_block_addr; + uint32_t block_size; +} PACK_STRUCT_STRUCT msd_scsi_read_capacity_10_response_t PACK_STRUCT_END; -/* - * Device Descriptor wrapper. +/** + * @brief Response to a READ_FORMAT_CAPACITIES SCSI command */ -static const USBDescriptor msd_device_descriptor = { - sizeof msd_device_descriptor_data, - msd_device_descriptor_data -}; - -/* Configuration Descriptor tree for a CDC.*/ -static const uint8_t msd_configuration_descriptor_data[] = { - /* Configuration Descriptor.*/ - USB_DESC_CONFIGURATION(0x0020, /* wTotalLength. */ - 0x01, /* bNumInterfaces. */ - 0x01, /* bConfigurationValue. */ - 0, /* iConfiguration. */ - 0xC0, /* bmAttributes (self powered). */ - 0x32), /* bMaxPower (100mA). */ - /* Interface Descriptor.*/ - USB_DESC_INTERFACE (0x00, /* bInterfaceNumber. */ - 0x00, /* bAlternateSetting. */ - 0x02, /* bNumEndpoints. */ - 0x08, /* bInterfaceClass (Mass Storage) */ - 0x06, /* bInterfaceSubClass (SCSI - Transparent storage class) */ - 0x50, /* bInterfaceProtocol (Bulk Only) */ - 0), /* iInterface. (none) */ - /* Mass Storage Data In Endpoint Descriptor.*/ - USB_DESC_ENDPOINT (USB_MS_DATA_EP|0x80, - 0x02, /* bmAttributes (Bulk). */ - USB_MS_EP_SIZE,/* wMaxPacketSize. */ - 0x05), /* bInterval. 1ms */ - /* Mass Storage Data In Endpoint Descriptor.*/ - USB_DESC_ENDPOINT (USB_MS_DATA_EP, - 0x02, /* bmAttributes (Bulk). */ - USB_MS_EP_SIZE,/* wMaxPacketSize. */ - 0x05) /* bInterval. 1ms */ -}; +PACK_STRUCT_BEGIN typedef struct { + uint8_t reserved[3]; + uint8_t capacity_list_length; + uint32_t block_count; + uint32_t desc_and_block_length; +} PACK_STRUCT_STRUCT msd_scsi_read_format_capacities_response_t PACK_STRUCT_END; -/* - * Configuration Descriptor wrapper. +/** + * @brief Read-write buffers (TODO: need a way of specifying the size of this) */ -static const USBDescriptor msd_configuration_descriptor = { - sizeof msd_configuration_descriptor_data, - msd_configuration_descriptor_data -}; +static uint8_t rw_buf[2][512]; -/* - * U.S. English language identifier. +/** + * @brief Byte-swap a 32 bits unsigned integer */ -static const uint8_t msd_string0[] = { - USB_DESC_BYTE(4), /* bLength. */ - USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */ - USB_DESC_WORD(0x0409) /* wLANGID (U.S. English). */ -}; +#define swap_uint32(x) ((((x) & 0x000000FF) << 24) \ + | (((x) & 0x0000FF00) << 8) \ + | (((x) & 0x00FF0000) >> 8) \ + | (((x) & 0xFF000000) >> 24)) -/* - * Vendor string. +/** + * @brief Byte-swap a 16 bits unsigned integer */ -static const uint8_t msd_string1[] = { - USB_DESC_BYTE(38), /* bLength. */ - USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */ - 'S', 0, 'T', 0, 'M', 0, 'i', 0, 'c', 0, 'r', 0, 'o', 0, 'e', 0, - 'l', 0, 'e', 0, 'c', 0, 't', 0, 'r', 0, 'o', 0, 'n', 0, 'i', 0, - 'c', 0, 's', 0 -}; +#define swap_uint16(x) ((((x) & 0x00FF) << 8) \ + | (((x) & 0xFF00) >> 8)) + +static void msd_handle_end_point_notification(USBDriver *usbp, usbep_t ep); -/* - * Device Description string. +/** + * @brief IN end-point 1 state */ -static const uint8_t msd_string2[] = { - USB_DESC_BYTE(62), /* bLength. */ - USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */ - 'C', 0, 'h', 0, 'i', 0, 'b', 0, 'i', 0, 'O', 0, 'S', 0, '/', 0, - 'R', 0, 'T', 0, ' ', 0, 'M', 0, 'a', 0, 's', 0, 's', 0, ' ', 0, - 'S', 0, 't', 0, 'o', 0, 'r', 0, 'a', 0, 'g', 0, 'e', 0, ' ', 0, - 'D', 0, 'e', 0, 'v', 0, 'i', 0, 'c', 0, 'e' -}; +static USBInEndpointState ep1_in_state; -static const uint8_t msd_string3[] = { - USB_DESC_BYTE(26), /* bLength. */ - USB_DESC_BYTE(USB_DESCRIPTOR_STRING), /* bDescriptorType. */ - 'A', 0, 'E', 0, 'C', 0, 'C', 0, 'E', 0, 'C', 0, 'C', 0, 'C', 0, 'C', 0, - '0' + CH_KERNEL_MAJOR, 0, - '0' + CH_KERNEL_MINOR, 0, - '0' + CH_KERNEL_PATCH, 0 -}; +/** + * @brief OUT end-point 1 state + */ +static USBOutEndpointState ep1_out_state; -/* - * Strings wrappers array. +/** + * @brief End-point 1 initialization structure */ -static const USBDescriptor msd_strings[] = { - {sizeof msd_string0, msd_string0}, - {sizeof msd_string1, msd_string1}, - {sizeof msd_string2, msd_string2}, - {sizeof msd_string3, msd_string3} +static const USBEndpointConfig ep_data_config = { + USB_EP_MODE_TYPE_BULK, + NULL, + msd_handle_end_point_notification, + msd_handle_end_point_notification, + 64, + 64, + &ep1_in_state, + &ep1_out_state, + 1, + NULL }; -/* - * Handles the GET_DESCRIPTOR callback. All required descriptors must be - * handled here. +/** + * @brief USB device configured handler. + * + * @param[in] msdp pointer to the @p USBMassStorageDriver object + * + * @iclass */ -static const USBDescriptor *get_descriptor(USBDriver *usbp, - uint8_t dtype, - uint8_t dindex, - uint16_t lang) { - - (void)usbp; - (void)lang; - switch (dtype) { - case USB_DESCRIPTOR_DEVICE: - return &msd_device_descriptor; - case USB_DESCRIPTOR_CONFIGURATION: - return &msd_configuration_descriptor; - case USB_DESCRIPTOR_STRING: - if (dindex < 4) - return &msd_strings[dindex]; - } - return NULL; +void msdConfigureHookI(USBMassStorageDriver *msdp) +{ + usbInitEndpointI(msdp->config->usbp, msdp->config->bulk_ep, &ep_data_config); + chBSemSignalI(&msdp->bsem); + chEvtBroadcastI(&msdp->evt_connected); } /** @@ -176,572 +155,676 @@ static const USBDescriptor *get_descriptor(USBDriver *usbp, * @retval FALSE Message not handled. */ bool_t msdRequestsHook(USBDriver *usbp) { - if (((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) && - ((usbp->setup[0] & USB_RTYPE_RECIPIENT_MASK) == USB_RTYPE_RECIPIENT_INTERFACE)) { - /* check that the request is for interface 0.*/ - if(MSD_SETUP_INDEX(usbp->setup) != 0) - return FALSE; - - /* act depending on bRequest = setup[1] */ - switch(usbp->setup[1]) { - case MSD_REQ_RESET: - /* check that it is a HOST2DEV request */ - if(((usbp->setup[0] & USB_RTYPE_DIR_MASK) != USB_RTYPE_DIR_HOST2DEV) || - (MSD_SETUP_LENGTH(usbp->setup) != 0) || - (MSD_SETUP_VALUE(usbp->setup) != 0)) - return FALSE; - - /* reset all endpoints */ - /* TODO!*/ - /* The device shall NAK the status stage of the device request until - * the Bulk-Only Mass Storage Reset is complete. - */ - return TRUE; - case MSD_GET_MAX_LUN: - /* check that it is a DEV2HOST request */ - if(((usbp->setup[0] & USB_RTYPE_DIR_MASK) != USB_RTYPE_DIR_DEV2HOST) || - (MSD_SETUP_LENGTH(usbp->setup) != 1) || - (MSD_SETUP_VALUE(usbp->setup) != 0)) - return FALSE; - - static uint8_t len_buf[1] = {0}; - /* stall to indicate that we don't support LUN */ - usbSetupTransfer(usbp, len_buf, 1, NULL); - return TRUE; - default: - return FALSE; - break; - } - } - return FALSE; -} -static void WaitForISR(USBMassStorageDriver *msdp) { - /* sleep until it completes */ - chSysLock(); - chBSemWaitS(&msdp->bsem); - chSysUnlock(); + /* check that the request is of type Class / Interface */ + if (((usbp->setup[0] & USB_RTYPE_TYPE_MASK) == USB_RTYPE_TYPE_CLASS) && + ((usbp->setup[0] & USB_RTYPE_RECIPIENT_MASK) == USB_RTYPE_RECIPIENT_INTERFACE)) { + + /* check that the request is for interface 0 */ + if (MSD_SETUP_INDEX(usbp->setup) != 0) + return FALSE; + + /* act depending on bRequest = setup[1] */ + switch (usbp->setup[1]) { + case MSD_REQ_RESET: + /* check that it is a HOST2DEV request */ + if (((usbp->setup[0] & USB_RTYPE_DIR_MASK) != USB_RTYPE_DIR_HOST2DEV) || + (MSD_SETUP_LENGTH(usbp->setup) != 0) || + (MSD_SETUP_VALUE(usbp->setup) != 0)) + { + return FALSE; + } + + /* reset all endpoints */ + /* TODO!*/ + /* The device shall NAK the status stage of the device request until + * the Bulk-Only Mass Storage Reset is complete. + */ + return TRUE; + case MSD_GET_MAX_LUN: + /* check that it is a DEV2HOST request */ + if (((usbp->setup[0] & USB_RTYPE_DIR_MASK) != USB_RTYPE_DIR_DEV2HOST) || + (MSD_SETUP_LENGTH(usbp->setup) != 1) || + (MSD_SETUP_VALUE(usbp->setup) != 0)) + { + return FALSE; + } + + static uint8_t len_buf[1] = {0}; + /* stall to indicate that we don't support LUN */ + usbSetupTransfer(usbp, len_buf, 1, NULL); + return TRUE; + default: + return FALSE; + break; + } + } + + return FALSE; } -void msdUsbEvent(USBDriver *usbp, usbep_t ep) { - (void)usbp; - (void)ep; +/** + * @brief Wait until the end-point interrupt handler has been called + */ +static void msd_wait_for_isr(USBMassStorageDriver *msdp) { - chSysLockFromIsr(); - chBSemSignalI(&((USBMassStorageDriver *)usbp->param)->bsem); - chSysUnlockFromIsr(); + /* sleep until it completes */ + chSysLock(); + chBSemWaitS(&msdp->bsem); + chSysUnlock(); } /** - * @brief IN EP1 state. + * @brief Called when data can be read or written on the endpoint -- wakes the thread up */ -static USBInEndpointState ep1InState, ep1OutState; +static void msd_handle_end_point_notification(USBDriver *usbp, usbep_t ep) { + + (void)usbp; + (void)ep; + + chSysLockFromIsr(); + chBSemSignalI(&((USBMassStorageDriver *)usbp->in_params[ep])->bsem); + chSysUnlockFromIsr(); +} /** - * @brief EP1 initialization structure (IN only). + * @brief Starts sending data */ -static const USBEndpointConfig epDataConfig = { - USB_EP_MODE_TYPE_BULK, - NULL, - msdUsbEvent, - msdUsbEvent, - USB_MS_EP_SIZE, - USB_MS_EP_SIZE, - &ep1InState, - &ep1OutState, - 1, - NULL -}; +static void msd_start_transmit(USBMassStorageDriver *msdp, const uint8_t* buffer, size_t size) { -/* - * Handles the USB driver global events. - */ -static void usb_event(USBDriver *usbp, usbevent_t event) { - USBMassStorageDriver *msdp = (USBMassStorageDriver *)usbp->param; - switch (event) { - case USB_EVENT_RESET: - return; - case USB_EVENT_ADDRESS: - return; - case USB_EVENT_CONFIGURED: - chSysLockFromIsr(); - usbInitEndpointI(usbp, USB_MS_DATA_EP, &epDataConfig); - /* initialise the thread */ - chBSemSignalI(&msdp->bsem); - - /* signal that the device is connected */ - chEvtBroadcastI(&msdp->evt_connected); - chSysUnlockFromIsr(); - - return; - case USB_EVENT_SUSPEND: - return; - case USB_EVENT_WAKEUP: - return; - case USB_EVENT_STALLED: - return; - } - return; + usbPrepareTransmit(msdp->config->usbp, msdp->config->bulk_ep, buffer, size); + chSysLock(); + usbStartTransmitI(msdp->config->usbp, msdp->config->bulk_ep); + chSysUnlock(); } -static const USBConfig msd_usb_config = { - usb_event, - get_descriptor, - msdRequestsHook, - NULL -}; +/** + * @brief Starts receiving data + */ +static void msd_start_receive(USBMassStorageDriver *msdp, uint8_t* buffer, size_t size) { + + usbPrepareReceive(msdp->config->usbp, msdp->config->bulk_ep, buffer, size); + chSysLock(); + usbStartReceiveI(msdp->config->usbp, msdp->config->bulk_ep); + chSysUnlock(); +} -static inline void SCSISetSense(USBMassStorageDriver *msdp, uint8_t key, uint8_t acode, uint8_t aqual) { - msdp->sense.byte[2] = key; - msdp->sense.byte[12] = acode; - msdp->sense.byte[13] = aqual; +/** + * @brief Changes the SCSI sense information + */ +static inline void msd_scsi_set_sense(USBMassStorageDriver *msdp, uint8_t key, uint8_t acode, uint8_t aqual) { + msdp->sense.byte[2] = key; + msdp->sense.byte[12] = acode; + msdp->sense.byte[13] = aqual; } -bool_t SCSICommandInquiry(USBMassStorageDriver *msdp) { - msd_cbw_t *cbw = &(msdp->cbw); - - static const scsi_inquiry_response_t inquiry = { - 0x00, // direct access block device - 0x80, // removable - 0x04, // SPC-2 - 0x02, // response data format - 0x20, // response has 0x20 + 4 bytes - 0x00, - 0x00, - 0x00, - "Chibios", - "Mass Storage", - {'v',CH_KERNEL_MAJOR+'0','.',CH_KERNEL_MINOR+'0'}, - }; - - if((cbw->scsi_cmd_data[1] & ((1 << 0) | (1 << 1))) || - cbw->scsi_cmd_data[2]) { - /* Optional but unsupported bits set - update the SENSE key and fail the request */ - SCSISetSense( msdp, - SCSI_SENSE_KEY_ILLEGAL_REQUEST, - SCSI_ASENSE_INVALID_FIELD_IN_CDB, - SCSI_ASENSEQ_NO_QUALIFIER); - - return FALSE; - } - - usbPrepareTransmit(msdp->usbp, USB_MS_DATA_EP, (uint8_t *)&inquiry, - sizeof(scsi_inquiry_response_t)); - - chSysLock(); - usbStartTransmitI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlock(); - - msdp->result = TRUE; - - /* wait for ISR */ - return TRUE; +/** + * @brief Processes an INQUIRY SCSI command + */ +bool_t msd_scsi_process_inquiry(USBMassStorageDriver *msdp) { + + msd_cbw_t *cbw = &(msdp->cbw); + + /* check the EVPD bit (Vital Product Data) */ + if (cbw->scsi_cmd_data[1] & 0x01) { + + /* check the Page Code byte to know the type of product data to reply */ + switch (cbw->scsi_cmd_data[2]) { + + /* unit serial number */ + case 0x80: { + uint8_t response[] = {'0'}; /* TODO */ + msd_start_transmit(msdp, response, sizeof(response)); + msdp->result = TRUE; + + /* wait for ISR */ + return TRUE; + } + + /* unhandled */ + default: + msd_scsi_set_sense(msdp, + SCSI_SENSE_KEY_ILLEGAL_REQUEST, + SCSI_ASENSE_INVALID_FIELD_IN_CDB, + SCSI_ASENSEQ_NO_QUALIFIER); + return FALSE; + } + } + else + { + msd_start_transmit(msdp, (const uint8_t *)&msdp->inquiry, sizeof(msdp->inquiry)); + msdp->result = TRUE; + + /* wait for ISR */ + return TRUE; + } } -bool_t SCSICommandRequestSense(USBMassStorageDriver *msdp) { - usbPrepareTransmit(msdp->usbp, USB_MS_DATA_EP, (uint8_t *)&msdp->sense, - sizeof(scsi_sense_response_t)); +/** + * @brief Processes a REQUEST_SENSE SCSI command + */ +bool_t msd_scsi_process_request_sense(USBMassStorageDriver *msdp) { - chSysLock(); - usbStartTransmitI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlock(); + msd_start_transmit(msdp, (const uint8_t *)&msdp->sense, sizeof(msdp->sense)); + msdp->result = TRUE; - msdp->result = TRUE; + /* wait for ISR immediately, otherwise the caller may reset the sense bytes before they are sent to the host! */ + msd_wait_for_isr(msdp); - /* wait for ISR */ - return TRUE; + /* ... don't wait for ISR, we just did it */ + return FALSE; } -bool_t SCSICommandReadCapacity10(USBMassStorageDriver *msdp) { - static SCSIReadCapacity10Response_t response; - - response.block_size = swap_uint32(msdp->block_dev_info.blk_size); - response.last_block_addr = swap_uint32(msdp->block_dev_info.blk_num-1); +/** + * @brief Processes a READ_CAPACITY_10 SCSI command + */ +bool_t msd_scsi_process_read_capacity_10(USBMassStorageDriver *msdp) { - usbPrepareTransmit(msdp->usbp, USB_MS_DATA_EP, (uint8_t *)&response, - sizeof(SCSIReadCapacity10Response_t)); + static msd_scsi_read_capacity_10_response_t response; - chSysLock(); - usbStartTransmitI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlock(); + response.block_size = swap_uint32(msdp->block_dev_info.blk_size); + response.last_block_addr = swap_uint32(msdp->block_dev_info.blk_num-1); - msdp->result = TRUE; + msd_start_transmit(msdp, (const uint8_t *)&response, sizeof(response)); + msdp->result = TRUE; - /* wait for ISR */ - return TRUE; + /* wait for ISR */ + return TRUE; } -bool_t SCSICommandSendDiagnostic(USBMassStorageDriver *msdp) { - msd_cbw_t *cbw = &(msdp->cbw); +/** + * @brief Processes a SEND_DIAGNOSTIC SCSI command + */ +bool_t msd_scsi_process_send_diagnostic(USBMassStorageDriver *msdp) { - if(!cbw->scsi_cmd_data[1] & (1 << 2)) { - /* Only self-test supported - update SENSE key and fail the command */ - SCSISetSense( msdp, - SCSI_SENSE_KEY_ILLEGAL_REQUEST, - SCSI_ASENSE_INVALID_FIELD_IN_CDB, - SCSI_ASENSEQ_NO_QUALIFIER); + msd_cbw_t *cbw = &(msdp->cbw); - return FALSE; - } + if (!(cbw->scsi_cmd_data[1] & (1 << 2))) { + /* only self-test supported - update SENSE key and fail the command */ + msd_scsi_set_sense(msdp, + SCSI_SENSE_KEY_ILLEGAL_REQUEST, + SCSI_ASENSE_INVALID_FIELD_IN_CDB, + SCSI_ASENSEQ_NO_QUALIFIER); + msdp->result = FALSE; + return FALSE; + } - /* TODO: actually perform the test */ - msdp->result = TRUE; + /* TODO: actually perform the test */ + msdp->result = TRUE; - /* don't wait for ISR */ - return FALSE; + /* don't wait for ISR */ + return FALSE; } -bool_t SCSICommandStartReadWrite10(USBMassStorageDriver *msdp) { - msd_cbw_t *cbw = &(msdp->cbw); - bool_t data_cached = FALSE; - - if((cbw->scsi_cmd_data[0] == SCSI_CMD_WRITE_10) && - blkIsWriteProtected(msdp->bbdp)) { - /* device is write protected and a write has been issued */ - /* Block address is invalid, update SENSE key and return command fail */ - SCSISetSense( msdp, - SCSI_SENSE_KEY_DATA_PROTECT, - SCSI_ASENSE_WRITE_PROTECTED, - SCSI_ASENSEQ_NO_QUALIFIER); - msdp->result = FALSE; - return FALSE; - } - - uint32_t rw_block_address = swap_uint32(*(uint32_t *)&cbw->scsi_cmd_data[2]); - uint16_t total = swap_uint16(*(uint16_t *)&cbw->scsi_cmd_data[7]); - uint16_t i = 0; - - if(rw_block_address >= msdp->block_dev_info.blk_num) { - /* Block address is invalid, update SENSE key and return command fail */ - SCSISetSense( msdp, - SCSI_SENSE_KEY_DATA_PROTECT, - SCSI_ASENSE_WRITE_PROTECTED, - SCSI_ASENSEQ_NO_QUALIFIER); - msdp->result = FALSE; - - /* don't wait for ISR */ - return FALSE; - } - - if(cbw->scsi_cmd_data[0] == SCSI_CMD_WRITE_10) { - /* get the first packet */ - usbPrepareReceive(msdp->usbp, USB_MS_DATA_EP, rw_buf[i % 2], - msdp->block_dev_info.blk_size); - - chSysLock(); - usbStartReceiveI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlock(); - - WaitForISR(msdp); - /* loop over each block */ - for(i = 0; i < total; i++) { - - if(i < (total - 1)) { - /* There is at least one block of data left to be read over USB */ - /* queue this read before issuing the blocking write */ - usbPrepareReceive(msdp->usbp, USB_MS_DATA_EP, rw_buf[(i+1) % 2], - msdp->block_dev_info.blk_size); - - chSysLock(); - usbStartReceiveI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlock(); - } - - /* now write the block to the block device */ - if(blkWrite(msdp->bbdp, rw_block_address++, rw_buf[i % 2], 1) == CH_FAILED) { - /* TODO: handle this */ - chSysHalt(); - } - - if(i < (total -1 )) { - /* now wait for the USB event to complete */ - WaitForISR(msdp); - } - } - } else { - i = 0; - /* read the first block from block device */ - if(blkRead(msdp->bbdp, rw_block_address++, rw_buf[i % 2], 1) == CH_FAILED) { - /* TODO: handle this */ - chSysHalt(); - } - - /* loop over each block */ - for(i = 0; i < total; i++) { - /* transmit the block */ - usbPrepareTransmit(msdp->usbp, USB_MS_DATA_EP, rw_buf[i % 2], - msdp->block_dev_info.blk_size); - - chSysLock(); - usbStartTransmitI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlock(); - - if(i < (total - 1)) { - /* there is at least one more block to be read from device */ - /* so read that whilst the USB transfer takes place */ - - if(blkRead(msdp->bbdp, rw_block_address++, rw_buf[(i+1) % 2], 1) == CH_FAILED) { - /* TODO: handle this */ - chSysHalt(); - } - } - - WaitForISR(msdp); - } - } - - msdp->result = TRUE; - - /* don't wait for ISR */ - return FALSE; +/** + * @brief Processes a READ_WRITE_10 SCSI command + */ +bool_t msd_scsi_process_start_read_write_10(USBMassStorageDriver *msdp) { + + msd_cbw_t *cbw = &(msdp->cbw); + + if ((cbw->scsi_cmd_data[0] == SCSI_CMD_WRITE_10) && blkIsWriteProtected(msdp->config->bbdp)) { + /* device is write protected and a write has been issued */ + /* block address is invalid, update SENSE key and return command fail */ + msd_scsi_set_sense(msdp, + SCSI_SENSE_KEY_DATA_PROTECT, + SCSI_ASENSE_WRITE_PROTECTED, + SCSI_ASENSEQ_NO_QUALIFIER); + msdp->result = FALSE; + + /* don't wait for ISR */ + return FALSE; + } + + uint32_t rw_block_address = swap_uint32(*(uint32_t *)&cbw->scsi_cmd_data[2]); + uint16_t total = swap_uint16(*(uint16_t *)&cbw->scsi_cmd_data[7]); + uint16_t i = 0; + + if (rw_block_address >= msdp->block_dev_info.blk_num) { + /* block address is invalid, update SENSE key and return command fail */ + msd_scsi_set_sense(msdp, + SCSI_SENSE_KEY_ILLEGAL_REQUEST, + SCSI_ASENSE_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE, + SCSI_ASENSEQ_NO_QUALIFIER); + msdp->result = FALSE; + + /* don't wait for ISR */ + return FALSE; + } + + if (cbw->scsi_cmd_data[0] == SCSI_CMD_WRITE_10) { + /* process a write command */ + + /* get the first packet */ + msd_start_receive(msdp, rw_buf[i % 2], msdp->block_dev_info.blk_size); + msd_wait_for_isr(msdp); + + /* loop over each block */ + for (i = 0; i < total; i++) { + + if (i < (total - 1)) { + /* there is at least one block of data left to be read over USB */ + /* queue this read before issuing the blocking write */ + msd_start_receive(msdp, rw_buf[(i + 1) % 2], msdp->block_dev_info.blk_size); + } + + /* now write the block to the block device */ + if (blkWrite(msdp->config->bbdp, rw_block_address++, rw_buf[i % 2], 1) == CH_FAILED) { + /* write failed */ + msd_scsi_set_sense(msdp, + SCSI_SENSE_KEY_MEDIUM_ERROR, + SCSI_ASENSE_WRITE_FAULT, + SCSI_ASENSEQ_NO_QUALIFIER); + msdp->result = FALSE; + + /* don't wait for ISR */ + return FALSE; + } + + if (i < (total - 1)) { + /* now wait for the USB event to complete */ + msd_wait_for_isr(msdp); + } + } + } else { + /* process a read command */ + + i = 0; + + /* read the first block from block device */ + if (blkRead(msdp->config->bbdp, rw_block_address++, rw_buf[i % 2], 1) == CH_FAILED) { + /* read failed */ + msd_scsi_set_sense(msdp, + SCSI_SENSE_KEY_MEDIUM_ERROR, + SCSI_ASENSE_READ_ERROR, + SCSI_ASENSEQ_NO_QUALIFIER); + msdp->result = FALSE; + + /* don't wait for ISR */ + return FALSE; + } + + /* loop over each block */ + for (i = 0; i < total; i++) { + /* transmit the block */ + msd_start_transmit(msdp, rw_buf[i % 2], msdp->block_dev_info.blk_size); + + if (i < (total - 1)) { + /* there is at least one more block to be read from device */ + /* so read that whilst the USB transfer takes place */ + if (blkRead(msdp->config->bbdp, rw_block_address++, rw_buf[(i + 1) % 2], 1) == CH_FAILED) { + /* read failed */ + msd_scsi_set_sense(msdp, + SCSI_SENSE_KEY_MEDIUM_ERROR, + SCSI_ASENSE_READ_ERROR, + SCSI_ASENSEQ_NO_QUALIFIER); + msdp->result = FALSE; + + /* wait for ISR (the previous transmission is still running) */ + return TRUE; + } + } + + /* wait for the USB event to complete */ + msd_wait_for_isr(msdp); + } + } + + msdp->result = TRUE; + + /* don't wait for ISR */ + return FALSE; } -bool_t SCSICommandStartStopUnit(USBMassStorageDriver *msdp) { - SCSIStartStopUnitRequest_t *ssu = (SCSIStartStopUnitRequest_t *)&(msdp->cbw.scsi_cmd_data); - - if((ssu->loej_start & 0b00000011) == 0b00000010) { - /* device has been ejected */ - chEvtBroadcast(&msdp->evt_ejected); +/** + * @brief Processes a START_STOP_UNIT SCSI command + */ +bool_t msd_scsi_process_start_stop_unit(USBMassStorageDriver *msdp) { - msdp->state = ejected; - } + if ((msdp->cbw.scsi_cmd_data[4] & 0x03) == 0x02) { + /* device has been ejected */ + chEvtBroadcast(&msdp->evt_ejected); + msdp->state = MSD_EJECTED; + } - msdp->result = TRUE; + msdp->result = TRUE; - /* don't wait for ISR */ - return FALSE; + /* don't wait for ISR */ + return FALSE; } -bool_t SCSICommandModeSense6(USBMassStorageDriver *msdp) { - /* Send an empty header response with the Write Protect flag status */ - /* TODO set byte3 to 0x80 if disk is read only */ - static uint8_t response[4] = {0x00, 0x00, 0x00, 0x00}; - - usbPrepareTransmit(msdp->usbp, USB_MS_DATA_EP, response, 4); +/** + * @brief Processes a MODE_SENSE_6 SCSI command + */ +bool_t msd_scsi_process_mode_sense_6(USBMassStorageDriver *msdp) { - chSysLock(); - usbStartTransmitI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlock(); + static uint8_t response[4] = { + 0x03, /* number of bytes that follow */ + 0x00, /* medium type is SBC */ + 0x00, /* not write protected (TODO handle it correctly) */ + 0x00 /* no block descriptor */ + }; - msdp->result = TRUE; + msd_start_transmit(msdp, response, sizeof(response)); + msdp->result = TRUE; - /* wait for ISR */ - return TRUE; + /* wait for ISR */ + return TRUE; } -bool_t msdWaitForCommandBlock(USBMassStorageDriver *msdp) { - usbPrepareReceive(msdp->usbp, USB_MS_DATA_EP, - (uint8_t *)&msdp->cbw, sizeof(msd_cbw_t)); +/** + * @brief Processes a READ_FORMAT_CAPACITIES SCSI command + */ +bool_t msd_scsi_process_read_format_capacities(USBMassStorageDriver *msdp) { - chSysLock(); - usbStartReceiveI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlock(); + msd_scsi_read_format_capacities_response_t response; + response.capacity_list_length = 1; + response.block_count = swap_uint32(msdp->block_dev_info.blk_num); + response.desc_and_block_length = swap_uint32((0x02 << 24) | (msdp->block_dev_info.blk_size & 0x00FFFFFF)); - msdp->state = read_cmd_block; + msd_start_transmit(msdp, (const uint8_t*)&response, sizeof(response)); + msdp->result = TRUE; /* wait for ISR */ return TRUE; } +/** + * @brief Processes a TEST_UNIT_READY SCSI command + */ +bool_t msd_scsi_process_test_unit_ready(USBMassStorageDriver *msdp) { + + if (blkIsInserted(msdp->config->bbdp)) { + /* device inserted and ready */ + msdp->result = TRUE; + } else { + /* device not present or not ready */ + msd_scsi_set_sense(msdp, + SCSI_SENSE_KEY_NOT_READY, + SCSI_ASENSE_MEDIUM_NOT_PRESENT, + SCSI_ASENSEQ_NO_QUALIFIER); + msdp->result = FALSE; + } + + /* don't wait for ISR */ + return FALSE; +} +/** + * @brief Waits for a new command block + */ +bool_t msd_wait_for_command_block(USBMassStorageDriver *msdp) { -/* A command block has been received */ -bool_t msdReadCommandBlock(USBMassStorageDriver *msdp) { - msd_cbw_t *cbw = &(msdp->cbw); - - /* by default transition back to the idle state */ - msdp->state = idle; - - /* check the command */ - if((cbw->signature != MSD_CBW_SIGNATURE) || - (cbw->lun > 0) || - ((cbw->data_len > 0) && (cbw->flags & 0x1F)) || - (cbw->scsi_cmd_len == 0) || - (cbw->scsi_cmd_len > 16)) { - - /* stall both IN and OUT endpoints */ - chSysLockFromIsr(); - usbStallReceiveI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlockFromIsr(); - - /* don't wait for ISR */ - return FALSE; - } - - bool_t sleep = FALSE; - switch(cbw->scsi_cmd_data[0]) { - case SCSI_CMD_INQUIRY: - sleep = SCSICommandInquiry(msdp); - break; - case SCSI_CMD_REQUEST_SENSE: - sleep = SCSICommandRequestSense(msdp); - break; - case SCSI_CMD_READ_CAPACITY_10: - sleep = SCSICommandReadCapacity10(msdp); - break; - case SCSI_CMD_READ_10: - case SCSI_CMD_WRITE_10: - MSD_RW_LED_ON(); - sleep = SCSICommandStartReadWrite10(msdp); - MSD_RW_LED_OFF(); - break; - case SCSI_CMD_SEND_DIAGNOSTIC: - sleep = SCSICommandSendDiagnostic(msdp); - break; - case SCSI_CMD_TEST_UNIT_READY: - case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: - case SCSI_CMD_VERIFY_10: - /* don't handle */ - msdp->result = TRUE; - break; - case SCSI_CMD_MODE_SENSE_6: - sleep = SCSICommandModeSense6(msdp); - break; - case SCSI_CMD_START_STOP_UNIT: - sleep = SCSICommandStartStopUnit(msdp); - break; - default: - SCSISetSense( msdp, - SCSI_SENSE_KEY_ILLEGAL_REQUEST, - SCSI_ASENSE_INVALID_COMMAND, - SCSI_ASENSEQ_NO_QUALIFIER); - - /* stall IN endpoint */ - chSysLockFromIsr(); - usbStallTransmitI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlockFromIsr(); - - cbw->data_len = 0; - return FALSE; - } - - cbw->data_len = 0; - - if(msdp->result) { - /* update sense with success status */ - SCSISetSense( msdp, - SCSI_SENSE_KEY_GOOD, - SCSI_ASENSE_NO_ADDITIONAL_INFORMATION, - SCSI_ASENSEQ_NO_QUALIFIER); - } else { - /* stall IN endpoint */ - chSysLockFromIsr(); - usbStallTransmitI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlockFromIsr(); - - cbw->data_len = 0; - return FALSE; - } - - if(sleep) { - WaitForISR(msdp); - } - - msd_csw_t *csw = &(msdp->csw); - - if(!msdp->result && cbw->data_len) { - /* still bytes left to send, this is too early to send CSW? */ - chSysLockFromIsr(); - usbStallReceiveI(msdp->usbp, USB_MS_DATA_EP); - usbStallTransmitI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlockFromIsr(); - - return FALSE; - } - - csw->status = (msdp->result) ? MSD_COMMAND_PASSED : MSD_COMMAND_FAILED; - csw->signature = MSD_CSW_SIGNATURE; - csw->data_residue = cbw->data_len; - csw->tag = cbw->tag; - - usbPrepareTransmit(msdp->usbp, USB_MS_DATA_EP, (uint8_t *)csw, - sizeof(msd_csw_t)); - - chSysLock(); - usbStartTransmitI(msdp->usbp, USB_MS_DATA_EP); - chSysUnlock(); - - /* wait on ISR */ - return TRUE; -} + msd_start_receive(msdp, (uint8_t *)&msdp->cbw, sizeof(msdp->cbw)); + msdp->state = MSD_READ_COMMAND_BLOCK; -static msg_t MassStorageThd(void *arg) { - USBMassStorageDriver *msdp = (USBMassStorageDriver *)arg; - - chRegSetThreadName("USB-MSD"); - - bool_t wait_for_isr = FALSE; - - /* wait for the usb to be initialised */ - WaitForISR(msdp); - - while (TRUE) { - wait_for_isr = FALSE; - - /* wait on data depending on the current state */ - switch(msdp->state) { - case idle: - wait_for_isr = msdWaitForCommandBlock(msdp); - break; - case read_cmd_block: - wait_for_isr = msdReadCommandBlock(msdp); - break; - case ejected: - /* disconnect usb device */ - usbDisconnectBus(msdp->usbp); - usbStop(msdp->usbp); - chThdExit(0); - return 0; - } - - /* wait until the ISR wakes thread */ - if(wait_for_isr) - WaitForISR(msdp); - } - - return 0; + /* wait for ISR */ + return TRUE; } -void msdInit(USBDriver *usbp, BaseBlockDevice *bbdp, USBMassStorageDriver *msdp) { - uint8_t i; - msdp->usbp = usbp; - msdp->state = idle; - msdp->bbdp = bbdp; - - chEvtInit(&msdp->evt_connected); - chEvtInit(&msdp->evt_ejected); +/** + * @brief Reads a newly received command block + */ +bool_t msd_read_command_block(USBMassStorageDriver *msdp) { + + msd_cbw_t *cbw = &(msdp->cbw); + + /* by default transition back to the idle state */ + msdp->state = MSD_IDLE; + + /* check the command */ + if ((cbw->signature != MSD_CBW_SIGNATURE) || + (cbw->lun > 0) || + ((cbw->data_len > 0) && (cbw->flags & 0x1F)) || + (cbw->scsi_cmd_len == 0) || + (cbw->scsi_cmd_len > 16)) { + + /* stall both IN and OUT endpoints */ + chSysLock(); + usbStallReceiveI(msdp->config->usbp, msdp->config->bulk_ep); + usbStallTransmitI(msdp->config->usbp, msdp->config->bulk_ep); + chSysUnlock(); + + /* don't wait for ISR */ + return FALSE; + } + + bool_t sleep = FALSE; + + /* check the command */ + switch (cbw->scsi_cmd_data[0]) { + case SCSI_CMD_INQUIRY: + sleep = msd_scsi_process_inquiry(msdp); + break; + case SCSI_CMD_REQUEST_SENSE: + sleep = msd_scsi_process_request_sense(msdp); + break; + case SCSI_CMD_READ_CAPACITY_10: + sleep = msd_scsi_process_read_capacity_10(msdp); + break; + case SCSI_CMD_READ_10: + case SCSI_CMD_WRITE_10: + if (msdp->config->rw_activity_callback) + msdp->config->rw_activity_callback(TRUE); + sleep = msd_scsi_process_start_read_write_10(msdp); + if (msdp->config->rw_activity_callback) + msdp->config->rw_activity_callback(FALSE); + break; + case SCSI_CMD_SEND_DIAGNOSTIC: + sleep = msd_scsi_process_send_diagnostic(msdp); + break; + case SCSI_CMD_MODE_SENSE_6: + sleep = msd_scsi_process_mode_sense_6(msdp); + break; + case SCSI_CMD_START_STOP_UNIT: + sleep = msd_scsi_process_start_stop_unit(msdp); + break; + case SCSI_CMD_READ_FORMAT_CAPACITIES: + sleep = msd_scsi_process_read_format_capacities(msdp); + break; + case SCSI_CMD_TEST_UNIT_READY: + sleep = msd_scsi_process_test_unit_ready(msdp); + break; + case SCSI_CMD_FORMAT_UNIT: + /* don't handle */ + msdp->result = TRUE; + break; + case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + /* don't handle */ + msdp->result = TRUE; + break; + case SCSI_CMD_VERIFY_10: + /* don't handle */ + msdp->result = TRUE; + break; + default: + msd_scsi_set_sense(msdp, + SCSI_SENSE_KEY_ILLEGAL_REQUEST, + SCSI_ASENSE_INVALID_COMMAND, + SCSI_ASENSEQ_NO_QUALIFIER); + + /* stall IN endpoint */ + chSysLock(); + usbStallTransmitI(msdp->config->usbp, msdp->config->bulk_ep); + chSysUnlock(); + + return FALSE; + } + + if (msdp->result) { + /* update sense with success status */ + msd_scsi_set_sense(msdp, + SCSI_SENSE_KEY_GOOD, + SCSI_ASENSE_NO_ADDITIONAL_INFORMATION, + SCSI_ASENSEQ_NO_QUALIFIER); + + /* reset data length left */ + cbw->data_len = 0; + } + + /* wait for ISR if needed */ + if (sleep) + msd_wait_for_isr(msdp); + + msd_csw_t *csw = &(msdp->csw); + + if (!msdp->result && cbw->data_len) { + /* still bytes left to send, this is too early to send CSW? */ + chSysLock(); + usbStallReceiveI(msdp->config->usbp, msdp->config->bulk_ep); + usbStallTransmitI(msdp->config->usbp, msdp->config->bulk_ep); + chSysUnlock(); + + /*return FALSE;*/ + } + + /* update the command status wrapper and send it to the host */ + csw->status = (msdp->result) ? MSD_COMMAND_PASSED : MSD_COMMAND_FAILED; + csw->signature = MSD_CSW_SIGNATURE; + csw->data_residue = cbw->data_len; + csw->tag = cbw->tag; + + msd_start_transmit(msdp, (const uint8_t *)csw, sizeof(*csw)); - /* initialise binary semaphore as taken */ - chBSemInit(&msdp->bsem, TRUE); + /* wait for ISR */ + return TRUE; +} - /* initialise sense values to zero */ - for(i = 0; i < sizeof(scsi_sense_response_t); i++) - msdp->sense.byte[i] = 0x00; +/** + * @brief Mass storage thread that processes commands + */ +static WORKING_AREA(mass_storage_thread_wa, 1024); +static msg_t mass_storage_thread(void *arg) { + + USBMassStorageDriver *msdp = (USBMassStorageDriver *)arg; + + chRegSetThreadName("USB-MSD"); + + bool_t wait_for_isr = FALSE; + + /* wait for the usb to be initialised */ + msd_wait_for_isr(msdp); + + while (!chThdShouldTerminate()) { + wait_for_isr = FALSE; + + /* wait on data depending on the current state */ + switch (msdp->state) { + case MSD_IDLE: + wait_for_isr = msd_wait_for_command_block(msdp); + break; + case MSD_READ_COMMAND_BLOCK: + wait_for_isr = msd_read_command_block(msdp); + break; + case MSD_EJECTED: + /* disconnect usb device */ + usbDisconnectBus(msdp->config->usbp); + usbStop(msdp->config->usbp); + chThdExit(0); + return 0; + } + + /* wait until the ISR wakes thread */ + if (wait_for_isr) + msd_wait_for_isr(msdp); + } + + return 0; +} - /* Response code = 0x70, additional sense length = 0x0A */ - msdp->sense.byte[0] = 0x70; - msdp->sense.byte[7] = 0x0A; +/** + * @brief Initializse a USB mass storage driver + */ +void msdInit(USBMassStorageDriver *msdp) { + + chDbgCheck(msdp != NULL, "msdInit"); + + msdp->config = NULL; + msdp->thread = NULL; + msdp->state = MSD_IDLE; + + /* initialize the driver events */ + chEvtInit(&msdp->evt_connected); + chEvtInit(&msdp->evt_ejected); + + /* initialise the binary semaphore as taken */ + chBSemInit(&msdp->bsem, TRUE); + + /* initialise the sense data structure */ + size_t i; + for (i = 0; i < sizeof(msdp->sense.byte); i++) + msdp->sense.byte[i] = 0x00; + msdp->sense.byte[0] = 0x70; /* response code */ + msdp->sense.byte[7] = 0x0A; /* additional sense length */ + + /* initialize the inquiry data structure */ + msdp->inquiry.peripheral = 0x00; /* direct access block device */ + msdp->inquiry.removable = 0x80; /* removable */ + msdp->inquiry.version = 0x04; /* SPC-2 */ + msdp->inquiry.response_data_format = 0x02; /* response data format */ + msdp->inquiry.additional_length = 0x20; /* response has 0x20 + 4 bytes */ + msdp->inquiry.sccstp = 0x00; + msdp->inquiry.bqueetc = 0x00; + msdp->inquiry.cmdque = 0x00; +} - /* make sure block device is working and get info */ - while(TRUE) { - blkstate_t state = blkGetDriverState(bbdp); - if(state == BLK_READY) - break; +/** + * @brief Starts a USB mass storage driver + */ +void msdStart(USBMassStorageDriver *msdp, const USBMassStorageConfig *config) { + + chDbgCheck(msdp != NULL, "msdStart"); + chDbgCheck(config != NULL, "msdStart"); + chDbgCheck(msdp->thread == NULL, "msdStart"); + + /* save the configuration */ + msdp->config = config; + + /* copy the config strings to the inquiry response structure */ + size_t i; + for (i = 0; i < sizeof(msdp->config->short_vendor_id); ++i) + msdp->inquiry.vendor_id[i] = config->short_vendor_id[i]; + for (i = 0; i < sizeof(msdp->config->short_product_id); ++i) + msdp->inquiry.product_id[i] = config->short_product_id[i]; + for (i = 0; i < sizeof(msdp->config->short_product_version); ++i) + msdp->inquiry.product_rev[i] = config->short_product_version[i]; + + /* set the initial state */ + msdp->state = MSD_IDLE; + + /* make sure block device is working */ + while (blkGetDriverState(config->bbdp) != BLK_READY) { + chThdSleepMilliseconds(50); + } + + /* get block device information */ + blkGetInfo(config->bbdp, &msdp->block_dev_info); + + /* store the pointer to the mass storage driver into the user param + of the USB driver, so that we can find it back in callbacks */ + config->usbp->in_params[config->bulk_ep] = (void *)msdp; + config->usbp->out_params[config->bulk_ep] = (void *)msdp; + + /* run the thread */ + msdp->thread = chThdCreateStatic(mass_storage_thread_wa, sizeof(mass_storage_thread_wa), NORMALPRIO, mass_storage_thread, msdp); +} - chThdSleepMilliseconds(50); - } +/** + * @brief Stops a USB mass storage driver + */ +void msdStop(USBMassStorageDriver *msdp) { - blkGetInfo(bbdp, &msdp->block_dev_info); + chDbgCheck(msdp->thread != NULL, "msdStop"); - usbDisconnectBus(usbp); - chThdSleepMilliseconds(1000); - usbp->param = (void *)msdp; + /* notify the thread that it's over */ + chThdTerminate(msdp->thread); - usbStart(usbp, &msd_usb_config); - usbConnectBus(usbp); + /* wake the thread up and wait until it ends */ + chBSemSignal(&msdp->bsem); + chThdWait(msdp->thread); + msdp->thread = NULL; - if(msdThd == NULL) { - msdThd = chThdCreateStatic(waMassStorage, sizeof(waMassStorage), - NORMALPRIO, MassStorageThd, msdp); - } + /* release the user params in the USB driver */ + msdp->config->usbp->in_params[msdp->config->bulk_ep] = NULL; + msdp->config->usbp->out_params[msdp->config->bulk_ep] = NULL; } diff --git a/mass_storage/usb_msd.h b/mass_storage/usb_msd.h index b6edf77..81aa34c 100644 --- a/mass_storage/usb_msd.h +++ b/mass_storage/usb_msd.h @@ -1,65 +1,17 @@ +/** + * @file usb_msd.h + * @brief USB mass storage driver and functions + */ -#define USB_MS_DATA_EP 1 - -#define USB_MS_EP_SIZE 64 - -#define MSD_REQ_RESET 0xFF -#define MSD_GET_MAX_LUN 0xFE -#define MSD_CBW_SIGNATURE 0x43425355 -#define MSD_CSW_SIGNATURE 0x53425355 -#define MSD_COMMAND_DIR_DATA_OUT (0 << 7) -#define MSD_COMMAND_DIR_DATA_IN (1 << 7) - -#define MSD_SETUP_WORD(setup, index) (uint16_t)(((uint16_t)setup[index+1] << 8) | (setup[index] & 0x00FF)) - -#define MSD_SETUP_VALUE(setup) MSD_SETUP_WORD(setup, 2) -#define MSD_SETUP_INDEX(setup) MSD_SETUP_WORD(setup, 4) -#define MSD_SETUP_LENGTH(setup) MSD_SETUP_WORD(setup, 6) - -#define SCSI_CMD_INQUIRY 0x12 -#define SCSI_CMD_REQUEST_SENSE 0x03 -#define SCSI_CMD_READ_CAPACITY_10 0x25 -#define SCSI_CMD_READ_10 0x28 -#define SCSI_CMD_WRITE_10 0x2A -#define SCSI_CMD_TEST_UNIT_READY 0x00 -#define SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL 0x1E -#define SCSI_CMD_VERIFY_10 0x2F -#define SCSI_CMD_SEND_DIAGNOSTIC 0x1D -#define SCSI_CMD_MODE_SENSE_6 0x1A -#define SCSI_CMD_START_STOP_UNIT 0x1B - -#define MSD_COMMAND_PASSED 0x00 -#define MSD_COMMAND_FAILED 0x01 -#define MSD_COMMAND_PHASE_ERROR 0x02 - -#define SCSI_SENSE_KEY_GOOD 0x00 -#define SCSI_SENSE_KEY_RECOVERED_ERROR 0x01 -#define SCSI_SENSE_KEY_NOT_READY 0x02 -#define SCSI_SENSE_KEY_MEDIUM_ERROR 0x03 -#define SCSI_SENSE_KEY_HARDWARE_ERROR 0x04 -#define SCSI_SENSE_KEY_ILLEGAL_REQUEST 0x05 -#define SCSI_SENSE_KEY_UNIT_ATTENTION 0x06 -#define SCSI_SENSE_KEY_DATA_PROTECT 0x07 -#define SCSI_SENSE_KEY_BLANK_CHECK 0x08 -#define SCSI_SENSE_KEY_VENDOR_SPECIFIC 0x09 -#define SCSI_SENSE_KEY_COPY_ABORTED 0x0A -#define SCSI_SENSE_KEY_ABORTED_COMMAND 0x0B -#define SCSI_SENSE_KEY_VOLUME_OVERFLOW 0x0D -#define SCSI_SENSE_KEY_MISCOMPARE 0x0E -#define SCSI_ASENSE_NO_ADDITIONAL_INFORMATION 0x00 -#define SCSI_ASENSE_LOGICAL_UNIT_NOT_READY 0x04 -#define SCSI_ASENSE_INVALID_FIELD_IN_CDB 0x24 -#define SCSI_ASENSE_NOT_READY_TO_READY_CHANGE 0x28 -#define SCSI_ASENSE_WRITE_PROTECTED 0x27 -#define SCSI_ASENSE_FORMAT_ERROR 0x31 -#define SCSI_ASENSE_INVALID_COMMAND 0x20 -#define SCSI_ASENSE_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x21 -#define SCSI_ASENSE_MEDIUM_NOT_PRESENT 0x3A -#define SCSI_ASENSEQ_NO_QUALIFIER 0x00 -#define SCSI_ASENSEQ_FORMAT_COMMAND_FAILED 0x01 -#define SCSI_ASENSEQ_INITIALIZING_COMMAND_REQUIRED 0x02 -#define SCSI_ASENSEQ_OPERATION_IN_PROGRESS 0x07 +#ifndef _USB_MSD_H_ +#define _USB_MSD_H_ +#include "ch.h" +#include "hal.h" + +/** + * @brief Command Block Wrapper structure + */ PACK_STRUCT_BEGIN typedef struct { uint32_t signature; uint32_t tag; @@ -70,6 +22,9 @@ PACK_STRUCT_BEGIN typedef struct { uint8_t scsi_cmd_data[16]; } PACK_STRUCT_STRUCT msd_cbw_t PACK_STRUCT_END; +/** + * @brief Command Status Wrapper structure + */ PACK_STRUCT_BEGIN typedef struct { uint32_t signature; uint32_t tag; @@ -77,63 +32,158 @@ PACK_STRUCT_BEGIN typedef struct { uint8_t status; } PACK_STRUCT_STRUCT msd_csw_t PACK_STRUCT_END; -typedef struct { +/** + * @brief Structure holding sense data (status/error information) + */ +PACK_STRUCT_BEGIN typedef struct { uint8_t byte[18]; -} __attribute__ ((packed)) scsi_sense_response_t; +} PACK_STRUCT_STRUCT msd_scsi_sense_response_t PACK_STRUCT_END; +/** + * @brief structure holding the data to reply to an INQUIRY SCSI command + */ PACK_STRUCT_BEGIN typedef struct { - uint8_t peripheral; - uint8_t removable; - uint8_t version; - uint8_t response_data_format; - uint8_t additional_length; - uint8_t sccstp; - uint8_t bqueetc; - uint8_t cmdque; - uint8_t vendorID[8]; - uint8_t productID[16]; - uint8_t productRev[4]; -} PACK_STRUCT_STRUCT scsi_inquiry_response_t PACK_STRUCT_END; - -PACK_STRUCT_BEGIN typedef struct { - uint32_t last_block_addr; - uint32_t block_size; -} PACK_STRUCT_STRUCT SCSIReadCapacity10Response_t PACK_STRUCT_END; - -PACK_STRUCT_BEGIN typedef struct { - uint8_t op_code; - uint8_t lun_immed; - uint8_t res1; - uint8_t res2; - uint8_t loej_start; - uint8_t control; -} PACK_STRUCT_STRUCT SCSIStartStopUnitRequest_t; - -typedef struct USBMassStorageDriver USBMassStorageDriver; - -typedef enum { idle, read_cmd_block, ejected} msd_state_t; - -struct USBMassStorageDriver { - USBDriver *usbp; + uint8_t peripheral; + uint8_t removable; + uint8_t version; + uint8_t response_data_format; + uint8_t additional_length; + uint8_t sccstp; + uint8_t bqueetc; + uint8_t cmdque; + uint8_t vendor_id[8]; + uint8_t product_id[16]; + uint8_t product_rev[4]; +} PACK_STRUCT_STRUCT msd_scsi_inquiry_response_t PACK_STRUCT_END; + +/** + * @brief Possible states for the USB mass storage driver + */ +typedef enum { + MSD_IDLE, + MSD_READ_COMMAND_BLOCK, + MSD_EJECTED +} msd_state_t; + +/** + * @brief Driver configuration structure + */ +typedef struct { + /** + * @brief USB driver to use for communication + */ + USBDriver *usbp; + + /** + * @brief Block device to use for storage + */ + BaseBlockDevice *bbdp; + + /** + * @brief Index of the USB endpoint to use for transfers + */ + usbep_t bulk_ep; + + /** + * @brief Optional callback that will be called whenever there is + * read/write activity + * @note The callback is called with argument TRUE when activity starts, + * and FALSE when activity stops. + */ + void (*rw_activity_callback)(bool_t); + + /** + * @brief Short vendor identification + * @note ASCII characters only, maximum 8 characters (pad with zeroes). + */ + uint8_t short_vendor_id[8]; + + /** + * @brief Short product identification + * @note ASCII characters only, maximum 16 characters (pad with zeroes). + */ + uint8_t short_product_id[16]; + + /** + * @brief Short product revision + * @note ASCII characters only, maximum 4 characters (pad with zeroes). + */ + uint8_t short_product_version[4]; + +} USBMassStorageConfig; + +/** + * @brief USB mass storage driver structure. + * @details This structure holds all the states and members of a USB mass + * storage driver. + */ +typedef struct { + const USBMassStorageConfig* config; BinarySemaphore bsem; - BaseBlockDevice *bbdp; + Thread* thread; EventSource evt_connected, evt_ejected; BlockDeviceInfo block_dev_info; msd_state_t state; msd_cbw_t cbw; msd_csw_t csw; - scsi_sense_response_t sense; + msd_scsi_sense_response_t sense; + msd_scsi_inquiry_response_t inquiry; bool_t result; -}; - -#define MSD_CONNECTED 0 -#define MSD_EJECTED 1 +} USBMassStorageDriver; #ifdef __cplusplus extern "C" { #endif -void msdInit(USBDriver *usbp, BaseBlockDevice *bdp, USBMassStorageDriver *msdp); + +/** + * @brief Initializes a USB mass storage driver. + */ +void msdInit(USBMassStorageDriver *msdp); + +/** + * @brief Starts a USB mass storage driver. + * @details This function is sufficient to have USB mass storage running, it internally + * runs a thread that handles USB requests and transfers. + * The block device must be connected but no file system must be mounted, + * everything is handled by the host system. + */ +void msdStart(USBMassStorageDriver *msdp, const USBMassStorageConfig *config); + +/** + * @brief Stops a USB mass storage driver. + * @details This function waits for current tasks to be finished, if any, and then + * stops the mass storage thread. + */ +void msdStop(USBMassStorageDriver *msdp); + +/** + * @brief USB device configured handler. + * + * @param[in] msdp pointer to the @p USBMassStorageDriver object + * + * @iclass + */ +void msdConfigureHookI(USBMassStorageDriver *msdp); + +/** + * @brief Default requests hook. + * @details Applications wanting to use the Mass Storage over USB driver can use + * this function as requests hook in the USB configuration. + * The following requests are emulated: + * - MSD_REQ_RESET. + * - MSD_GET_MAX_LUN. + * . + * + * @param[in] usbp pointer to the @p USBDriver object + * @return The hook status. + * @retval TRUE Message handled internally. + * @retval FALSE Message not handled. + */ +bool_t msdRequestsHook(USBDriver *usbp); + #ifdef __cplusplus } #endif + +#endif /* _USB_MSD_H_ */