[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[cdi-devel] [PATCH v3 10/10] usb-storage: Add USB mass storage driver



+ This adds a USB mass storage device driver. It is missing proper error
  handling (e.g. for STALLed endpoints) and support for any USB MSD
  class other than bulk-only (e.g. control-bulk-interrupt for USB floppy
  drives).

Signed-off-by: Max Reitz <max@xxxxxxxxxx>
---
 usb-storage/main.c        |  76 ++++++++++++++++
 usb-storage/usb-storage.c | 216 ++++++++++++++++++++++++++++++++++++++++++++++
 usb-storage/usb-storage.h |  60 +++++++++++++
 3 files changed, 352 insertions(+)
 create mode 100644 usb-storage/main.c
 create mode 100644 usb-storage/usb-storage.c
 create mode 100644 usb-storage/usb-storage.h

diff --git a/usb-storage/main.c b/usb-storage/main.c
new file mode 100644
index 0000000..aa896ca
--- /dev/null
+++ b/usb-storage/main.c
@@ -0,0 +1,76 @@
+/******************************************************************************
+ * Copyright (c) 2015 Max Reitz                                               *
+ *                                                                            *
+ * Permission is hereby granted,  free of charge,  to any person  obtaining a *
+ * copy of this software and associated documentation files (the "Software"), *
+ * to deal in the Software without restriction,  including without limitation *
+ * the rights to use, copy,  modify, merge, publish,  distribute, sublicense, *
+ * and/or  sell copies of the  Software,  and to permit  persons to whom  the *
+ * Software is furnished to do so, subject to the following conditions:       *
+ *                                                                            *
+ * The above copyright notice and this permission notice shall be included in *
+ * all copies or substantial portions of the Software.                        *
+ *                                                                            *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED,  INCLUDING BUT NOT LIMITED TO THE WARRANTIES  OF MERCHANTABILITY, *
+ * FITNESS  FOR A PARTICULAR  PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL *
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY,  WHETHER IN AN ACTION OF CONTRACT,  TORT OR OTHERWISE,  ARISING *
+ * FROM,  OUT OF  OR IN CONNECTION  WITH  THE SOFTWARE  OR THE  USE OR  OTHER *
+ * DEALINGS IN THE SOFTWARE.                                                  *
+ ******************************************************************************/
+
+#include <cdi.h>
+#include <cdi/scsi.h>
+#include <cdi/usb.h>
+
+#include "usb-storage.h"
+
+
+#define DRIVER_NAME "usb-storage"
+
+
+static struct cdi_scsi_driver drv;
+static struct cdi_usb_bus_device_pattern bus_pattern;
+
+static int drv_init(void)
+{
+    cdi_scsi_driver_init(&drv);
+    cdi_handle_bus_device(&drv.drv, &bus_pattern.pattern);
+    return 0;
+}
+
+static int drv_destroy(void)
+{
+    cdi_driver_destroy(&drv.drv);
+    return 0;
+}
+
+
+static struct cdi_scsi_driver drv = {
+    .drv = {
+        .name               = DRIVER_NAME,
+        .type               = CDI_SCSI,
+        .bus                = CDI_USB,
+        .init               = drv_init,
+        .destroy            = drv_destroy,
+        .init_device        = init_usb_device,
+    },
+
+    .request    = usb_storage_request,
+};
+
+static struct cdi_usb_bus_device_pattern bus_pattern = {
+    .pattern = {
+        .bus_type = CDI_USB,
+    },
+
+    .vendor_id  = -1,
+    .product_id = -1,
+
+    .class_id       = CDI_USB_IFCCLS_STORAGE,
+    .subclass_id    = -1,
+    .protocol_id    = -1,
+};
+
+CDI_DRIVER(DRIVER_NAME, drv)
diff --git a/usb-storage/usb-storage.c b/usb-storage/usb-storage.c
new file mode 100644
index 0000000..07d6eb7
--- /dev/null
+++ b/usb-storage/usb-storage.c
@@ -0,0 +1,216 @@
+/******************************************************************************
+ * Copyright (c) 2015 Max Reitz                                               *
+ *                                                                            *
+ * Permission is hereby granted,  free of charge,  to any person  obtaining a *
+ * copy of this software and associated documentation files (the "Software"), *
+ * to deal in the Software without restriction,  including without limitation *
+ * the rights to use, copy,  modify, merge, publish,  distribute, sublicense, *
+ * and/or  sell copies of the  Software,  and to permit  persons to whom  the *
+ * Software is furnished to do so, subject to the following conditions:       *
+ *                                                                            *
+ * The above copyright notice and this permission notice shall be included in *
+ * all copies or substantial portions of the Software.                        *
+ *                                                                            *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED,  INCLUDING BUT NOT LIMITED TO THE WARRANTIES  OF MERCHANTABILITY, *
+ * FITNESS  FOR A PARTICULAR  PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL *
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY,  WHETHER IN AN ACTION OF CONTRACT,  TORT OR OTHERWISE,  ARISING *
+ * FROM,  OUT OF  OR IN CONNECTION  WITH  THE SOFTWARE  OR THE  USE OR  OTHER *
+ * DEALINGS IN THE SOFTWARE.                                                  *
+ ******************************************************************************/
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <cdi.h>
+#include <cdi/misc.h>
+#include <cdi/scsi.h>
+#include <cdi/usb.h>
+
+#include "usb-storage.h"
+
+
+#define CBW_IN  0x80
+#define CBW_OUT 0x00
+
+#define CBW_SIGNATURE 0x43425355 // 'USBC' => USB command
+#define CSW_SIGNATURE 0x53425355 // 'USBS' => USB status
+
+struct msd_bo_cbw {
+    uint32_t signature;
+    uint32_t tag;
+    uint32_t data_length;
+    uint8_t flags;
+    uint8_t lun;
+    uint8_t cb_length;
+    uint8_t cb[16];
+} __attribute__((packed));
+
+struct msd_bo_csw {
+    uint32_t signature;
+    uint32_t tag;
+    uint32_t data_residue;
+    uint8_t status;
+} __attribute__((packed));
+
+
+typedef enum usb_mass_storage_type {
+    USBMS_BULK_ONLY,
+} usb_mass_storage_type_t;
+
+
+typedef struct usb_mass_storage_device {
+    struct cdi_scsi_device dev;
+    struct cdi_usb_device *usb;
+
+    usb_mass_storage_type_t type;
+    int luns;
+
+    // FIXME: put into own class
+    int bulk_in, bulk_out;
+} usb_mass_storage_device_t;
+
+
+static int bulk_only_reset(usb_mass_storage_device_t *dev)
+{
+    cdi_usb_transmission_result_t res;
+
+    res = cdi_usb_control_transfer(dev->usb, 0, &(struct cdi_usb_setup_packet){
+            .bm_request_type    = CDI_USB_CREQ_CLASS | CDI_USB_CREQ_INTERFACE
+                                | CDI_USB_CREQ_OUT,
+            .b_request          = USBMS_REQ_BOMSR,
+            .w_index            = dev->usb->interface
+        }, NULL);
+    if (res != CDI_USB_OK) {
+        return -EIO;
+    }
+
+    if (dev->luns > 0) {
+        return 0;
+    }
+
+    uint8_t lun_count;
+    res = cdi_usb_control_transfer(dev->usb, 0, &(struct cdi_usb_setup_packet) {
+            .bm_request_type    = CDI_USB_CREQ_CLASS | CDI_USB_CREQ_INTERFACE
+                                | CDI_USB_CREQ_IN,
+            .b_request          = USBMS_REQ_GML,
+            .w_index            = dev->usb->interface,
+            .w_length           = 1
+        }, &lun_count);
+    if (res == CDI_USB_OK) {
+        dev->luns = lun_count + 1;
+    } else {
+        dev->luns = 1;
+    }
+
+    return 0;
+}
+
+
+struct cdi_device *init_usb_device(struct cdi_bus_data *bus_data)
+{
+    static int dev_counter = 0;
+    usb_mass_storage_device_t *dev = calloc(1, sizeof(*dev));
+    dev->usb = CDI_UPCAST(bus_data, struct cdi_usb_device, bus_data);
+
+    if ((dev->usb->subclass_id == USBMS_SUBCLS_SCSILEG ||
+         dev->usb->subclass_id == USBMS_SUBCLS_SCSI) &&
+        dev->usb->protocol_id == USBMS_PROTO_BBB)
+    {
+        dev->type = USBMS_BULK_ONLY;
+    } else {
+        free(dev);
+        return NULL;
+    }
+
+    if (dev->type == USBMS_BULK_ONLY) {
+        if (bulk_only_reset(dev) < 0) {
+            free(dev);
+            return NULL;
+        }
+
+        struct cdi_usb_endpoint_descriptor ep_desc;
+        for (int ep = 1; ep < dev->usb->endpoint_count; ep++) {
+            cdi_usb_get_endpoint_descriptor(dev->usb, ep, &ep_desc);
+
+            if ((ep_desc.bm_attributes & CDI_USB_EPDSC_ATTR_XFER_TYPE_MASK)
+                == CDI_USB_EPDSC_ATTR_BULK)
+            {
+                bool is_in;
+                is_in = ep_desc.b_endpoint_address & CDI_USB_EPDSC_EPADR_DIR;
+                if (!dev->bulk_in && is_in) {
+                    dev->bulk_in = ep;
+                } else if (dev->bulk_in && !is_in) {
+                    dev->bulk_out = ep;
+                }
+            }
+        }
+
+        if (!dev->bulk_in || !dev->bulk_out) {
+            free(dev);
+            return NULL;
+        }
+    }
+
+    dev->dev.dev.name = malloc(5);
+    sprintf((char *)dev->dev.dev.name, "msd%c", 'a' + dev_counter++);
+
+    dev->dev.type = CDI_STORAGE;
+    dev->dev.lun_count = dev->luns;
+
+    return &dev->dev.dev;
+}
+
+
+int usb_storage_request(struct cdi_scsi_device *device,
+                        struct cdi_scsi_packet *packet)
+{
+    cdi_usb_transmission_result_t res;
+    usb_mass_storage_device_t *dev = CDI_UPCAST(device,
+                                                usb_mass_storage_device_t, dev);
+
+    struct msd_bo_cbw cbw = {
+        .signature      = CBW_SIGNATURE,
+        .tag            = 42,
+        .data_length    = packet->bufsize,
+        .flags          = packet->direction == CDI_SCSI_READ ? CBW_IN : CBW_OUT,
+        .lun            = packet->lun,
+        .cb_length      = packet->cmdsize
+    };
+    memcpy(cbw.cb, packet->command, sizeof(cbw.cb));
+
+    res = cdi_usb_bulk_transfer(dev->usb, dev->bulk_out, &cbw, sizeof(cbw));
+    if (res != CDI_USB_OK) {
+        bulk_only_reset(dev);
+        return -1;
+    }
+
+    if (packet->direction == CDI_SCSI_READ) {
+        res = cdi_usb_bulk_transfer(dev->usb, dev->bulk_in,
+                                    packet->buffer, packet->bufsize);
+    } else if (packet->direction == CDI_SCSI_WRITE) {
+        res = cdi_usb_bulk_transfer(dev->usb, dev->bulk_out,
+                                    packet->buffer, packet->bufsize);
+    }
+
+    if (res != CDI_USB_OK) {
+        bulk_only_reset(dev);
+        return -1;
+    }
+
+    struct msd_bo_csw csw;
+    res = cdi_usb_bulk_transfer(dev->usb, dev->bulk_in, &csw, sizeof(csw));
+
+    if (res != CDI_USB_OK || csw.signature != CSW_SIGNATURE || csw.tag != 42 ||
+        csw.data_residue || csw.status)
+    {
+        bulk_only_reset(dev);
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/usb-storage/usb-storage.h b/usb-storage/usb-storage.h
new file mode 100644
index 0000000..eed3462
--- /dev/null
+++ b/usb-storage/usb-storage.h
@@ -0,0 +1,60 @@
+/******************************************************************************
+ * Copyright (c) 2015 Max Reitz                                               *
+ *                                                                            *
+ * Permission is hereby granted,  free of charge,  to any person  obtaining a *
+ * copy of this software and associated documentation files (the "Software"), *
+ * to deal in the Software without restriction,  including without limitation *
+ * the rights to use, copy,  modify, merge, publish,  distribute, sublicense, *
+ * and/or  sell copies of the  Software,  and to permit  persons to whom  the *
+ * Software is furnished to do so, subject to the following conditions:       *
+ *                                                                            *
+ * The above copyright notice and this permission notice shall be included in *
+ * all copies or substantial portions of the Software.                        *
+ *                                                                            *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
+ * IMPLIED,  INCLUDING BUT NOT LIMITED TO THE WARRANTIES  OF MERCHANTABILITY, *
+ * FITNESS  FOR A PARTICULAR  PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL *
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
+ * LIABILITY,  WHETHER IN AN ACTION OF CONTRACT,  TORT OR OTHERWISE,  ARISING *
+ * FROM,  OUT OF  OR IN CONNECTION  WITH  THE SOFTWARE  OR THE  USE OR  OTHER *
+ * DEALINGS IN THE SOFTWARE.                                                  *
+ ******************************************************************************/
+
+#ifndef USB_STORAGE_H
+#define USB_STORAGE_H
+
+#include <cdi.h>
+#include <cdi/scsi.h>
+
+
+// Mass storage subclass codes
+#define USBMS_SUBCLS_SCSILEG    0x00 // SCSI legacy ("command set not reported")
+#define USBMS_SUBCLS_RBC        0x01 // RBC
+#define USBMS_SUBCLS_MMC5       0x02 // MMC-5 (ATAPI)
+#define USBMS_SUBCLS_UFI        0x04 // UFI for Floppy Disk Drives
+#define USBMS_SUBCLS_SCSI       0x06 // SCSI transparent command set
+#define USBMS_SUBCLS_LSDFS      0x07 // LSD FS for negotiating SCSI access
+#define USBMS_SUBCLS_IEEE1667   0x08 // IEEE 1667
+#define USBMS_SUBCLS_VENDOR     0xff // Vendor-specific
+
+// Mass storage protocol codes
+#define USBMS_PROTO_CBI_CCI     0x00 // Control/Bulk/Interrupt with
+                                     // Command Completion Interrupt
+#define USBMS_PROTO_CBI         0x01 // CBI without CCI
+#define USBMS_PROTO_BBB         0x50 // Bulk-only
+#define USBMS_PROTO_UAS         0x62 // UAS
+#define USBMS_PROTO_VENDOR      0xff // Vendor-specific
+
+// Mass storage request codes
+#define USBMS_REQ_ADSC  0x00 // Accept Device-Specific Command (CBI)
+#define USBMS_REQ_GETRQ 0xfc // Get Requests (LSD FS)
+#define USBMS_REQ_PUTRQ 0xfd // Put Requests (LSD FS)
+#define USBMS_REQ_GML   0xfe // Get Max LUN (Bulk-only)
+#define USBMS_REQ_BOMSR 0xff // Bulk-Only Mass Storage Reset (Bulk-only)
+
+
+struct cdi_device *init_usb_device(struct cdi_bus_data *bus_data);
+int usb_storage_request(struct cdi_scsi_device *device,
+                        struct cdi_scsi_packet *packet);
+
+#endif
-- 
2.4.0