Commit 6e2bf99f authored by Max Reitz's avatar Max Reitz
Browse files

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's avatarMax Reitz <max@tyndur.org>
Acked-by: Kevin Wolf's avatarKevin Wolf <kevin@tyndur.org>
parent 86713d0b
No related merge requests found
Showing with 352 additions and 0 deletions
+352 -0
/******************************************************************************
* 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)
/******************************************************************************
* 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;
}
if (!dev->bulk_out && !is_in) {
dev->bulk_out = ep;
}
}
}
if (!dev->bulk_in || !dev->bulk_out) {
free(dev);
return NULL;
}
}
asprintf((char **)&dev->dev.dev.name, "msd%i", 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;
}
/******************************************************************************
* 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
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment