USB: g_file_storage: add CD-ROM emulation
authorAlan Stern <stern@rowland.harvard.edu>
Thu, 20 Nov 2008 19:13:12 +0000 (14:13 -0500)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 7 Jan 2009 17:59:57 +0000 (09:59 -0800)
This patch (as1172) adds the ability to emulate a CD-ROM drive to
g_file_storage.  The emulation is limited, since it presents as a disc
containing a single data track and no audio tracks.  Still, it may
come in useful on occasion.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/gadget/file_storage.c

index 2e71368f45b4c6d423f27c11ab11aa14bc4dd06c..93933155e81c66894d35f74976cdb84146bb5c6c 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * file_storage.c -- File-backed USB Storage Gadget, for USB development
  *
- * Copyright (C) 2003-2007 Alan Stern
+ * Copyright (C) 2003-2008 Alan Stern
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
 
 /*
  * The File-backed Storage Gadget acts as a USB Mass Storage device,
- * appearing to the host as a disk drive.  In addition to providing an
- * example of a genuinely useful gadget driver for a USB device, it also
- * illustrates a technique of double-buffering for increased throughput.
- * Last but not least, it gives an easy way to probe the behavior of the
- * Mass Storage drivers in a USB host.
+ * appearing to the host as a disk drive or as a CD-ROM drive.  In addition
+ * to providing an example of a genuinely useful gadget driver for a USB
+ * device, it also illustrates a technique of double-buffering for increased
+ * throughput.  Last but not least, it gives an easy way to probe the
+ * behavior of the Mass Storage drivers in a USB host.
  *
  * Backing storage is provided by a regular file or a block device, specified
  * by the "file" module parameter.  Access can be limited to read-only by
- * setting the optional "ro" module parameter.  The gadget will indicate that
- * it has removable media if the optional "removable" module parameter is set.
+ * setting the optional "ro" module parameter.  (For CD-ROM emulation,
+ * access is always read-only.)  The gadget will indicate that it has
+ * removable media if the optional "removable" module parameter is set.
  *
  * The gadget supports the Control-Bulk (CB), Control-Bulk-Interrupt (CBI),
  * and Bulk-Only (also known as Bulk-Bulk-Bulk or BBB) transports, selected
  * The default number of LUNs is taken from the number of "file" elements;
  * it is 1 if "file" is not given.  If "removable" is not set then a backing
  * file must be specified for each LUN.  If it is set, then an unspecified
- * or empty backing filename means the LUN's medium is not loaded.
+ * or empty backing filename means the LUN's medium is not loaded.  Ideally
+ * each LUN would be settable independently as a disk drive or a CD-ROM
+ * drive, but currently all LUNs have to be the same type.  The CD-ROM
+ * emulation includes a single data track and no audio tracks; hence there
+ * need be only one backing file per LUN.  Note also that the CD-ROM block
+ * length is set to 512 rather than the more common value 2048.
  *
  * Requirements are modest; only a bulk-in and a bulk-out endpoint are
  * needed (an interrupt-out endpoint is also needed for CBI).  The memory
@@ -91,6 +97,8 @@
  *                                     USB device controller (usually true),
  *                                     boolean to permit the driver to halt
  *                                     bulk endpoints
+ *     cdrom                   Default false, boolean for whether to emulate
+ *                                     a CD-ROM drive
  *     transport=XXX           Default BBB, transport name (CB, CBI, or BBB)
  *     protocol=YYY            Default SCSI, protocol name (RBC, 8020 or
  *                                     ATAPI, QIC, UFI, 8070, or SCSI;
  *                                     PAGE_CACHE_SIZE)
  *
  * If CONFIG_USB_FILE_STORAGE_TEST is not set, only the "file", "ro",
- * "removable", "luns", and "stall" options are available; default values
- * are used for everything else.
+ * "removable", "luns", "stall", and "cdrom" options are available; default
+ * values are used for everything else.
  *
  * The pathnames of the backing files and the ro settings are available in
  * the attribute files "file" and "ro" in the lun<n> subdirectory of the
  * gadget's sysfs directory.  If the "removable" option is set, writing to
  * these files will simulate ejecting/loading the medium (writing an empty
  * line means eject) and adjusting a write-enable tab.  Changes to the ro
- * setting are not allowed when the medium is loaded.
+ * setting are not allowed when the medium is loaded or if CD-ROM emulation
+ * is being used.
  *
  * This gadget driver is heavily based on "Gadget Zero" by David Brownell.
  * The driver's SCSI command interface was based on the "Information
 
 #define DRIVER_DESC            "File-backed Storage Gadget"
 #define DRIVER_NAME            "g_file_storage"
-#define DRIVER_VERSION         "7 August 2007"
+#define DRIVER_VERSION         "20 November 2008"
 
 static const char longname[] = DRIVER_DESC;
 static const char shortname[] = DRIVER_NAME;
@@ -341,6 +350,7 @@ static struct {
 
        int             removable;
        int             can_stall;
+       int             cdrom;
 
        char            *transport_parm;
        char            *protocol_parm;
@@ -359,6 +369,7 @@ static struct {
        .protocol_parm          = "SCSI",
        .removable              = 0,
        .can_stall              = 1,
+       .cdrom                  = 0,
        .vendor                 = DRIVER_VENDOR_ID,
        .product                = DRIVER_PRODUCT_ID,
        .release                = 0xffff,       // Use controller chip type
@@ -382,6 +393,9 @@ MODULE_PARM_DESC(removable, "true to simulate removable media");
 module_param_named(stall, mod_data.can_stall, bool, S_IRUGO);
 MODULE_PARM_DESC(stall, "false to prevent bulk stalls");
 
+module_param_named(cdrom, mod_data.cdrom, bool, S_IRUGO);
+MODULE_PARM_DESC(cdrom, "true to emulate cdrom instead of disk");
+
 
 /* In the non-TEST version, only the module parameters listed above
  * are available. */
@@ -411,6 +425,10 @@ MODULE_PARM_DESC(buflen, "I/O buffer size");
 
 /*-------------------------------------------------------------------------*/
 
+/* SCSI device types */
+#define TYPE_DISK      0x00
+#define TYPE_CDROM     0x05
+
 /* USB protocol value = the transport method */
 #define USB_PR_CBI     0x00            // Control/Bulk/Interrupt
 #define USB_PR_CB      0x01            // Control/Bulk w/o interrupt
@@ -487,6 +505,8 @@ struct interrupt_data {
 #define SC_READ_12                     0xa8
 #define SC_READ_CAPACITY               0x25
 #define SC_READ_FORMAT_CAPACITIES      0x23
+#define SC_READ_HEADER                 0x44
+#define SC_READ_TOC                    0x43
 #define SC_RELEASE                     0x17
 #define SC_REQUEST_SENSE               0x03
 #define SC_RESERVE                     0x16
@@ -2006,23 +2026,28 @@ static int do_inquiry(struct fsg_dev *fsg, struct fsg_buffhd *bh)
        u8      *buf = (u8 *) bh->buf;
 
        static char vendor_id[] = "Linux   ";
-       static char product_id[] = "File-Stor Gadget";
+       static char product_disk_id[] = "File-Stor Gadget";
+       static char product_cdrom_id[] = "File-CD Gadget  ";
 
        if (!fsg->curlun) {             // Unsupported LUNs are okay
                fsg->bad_lun_okay = 1;
                memset(buf, 0, 36);
                buf[0] = 0x7f;          // Unsupported, no device-type
+               buf[4] = 31;            // Additional length
                return 36;
        }
 
-       memset(buf, 0, 8);      // Non-removable, direct-access device
+       memset(buf, 0, 8);
+       buf[0] = (mod_data.cdrom ? TYPE_CDROM : TYPE_DISK);
        if (mod_data.removable)
                buf[1] = 0x80;
        buf[2] = 2;             // ANSI SCSI level 2
        buf[3] = 2;             // SCSI-2 INQUIRY data format
        buf[4] = 31;            // Additional length
                                // No special options
-       sprintf(buf + 8, "%-8s%-16s%04x", vendor_id, product_id,
+       sprintf(buf + 8, "%-8s%-16s%04x", vendor_id,
+                       (mod_data.cdrom ? product_cdrom_id :
+                               product_disk_id),
                        mod_data.release);
        return 36;
 }
@@ -2101,6 +2126,75 @@ static int do_read_capacity(struct fsg_dev *fsg, struct fsg_buffhd *bh)
 }
 
 
+static void store_cdrom_address(u8 *dest, int msf, u32 addr)
+{
+       if (msf) {
+               /* Convert to Minutes-Seconds-Frames */
+               addr >>= 2;             /* Convert to 2048-byte frames */
+               addr += 2*75;           /* Lead-in occupies 2 seconds */
+               dest[3] = addr % 75;    /* Frames */
+               addr /= 75;
+               dest[2] = addr % 60;    /* Seconds */
+               addr /= 60;
+               dest[1] = addr;         /* Minutes */
+               dest[0] = 0;            /* Reserved */
+       } else {
+               /* Absolute sector */
+               put_be32(dest, addr);
+       }
+}
+
+static int do_read_header(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+       struct lun      *curlun = fsg->curlun;
+       int             msf = fsg->cmnd[1] & 0x02;
+       u32             lba = get_be32(&fsg->cmnd[2]);
+       u8              *buf = (u8 *) bh->buf;
+
+       if ((fsg->cmnd[1] & ~0x02) != 0) {              /* Mask away MSF */
+               curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+               return -EINVAL;
+       }
+       if (lba >= curlun->num_sectors) {
+               curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+               return -EINVAL;
+       }
+
+       memset(buf, 0, 8);
+       buf[0] = 0x01;          /* 2048 bytes of user data, rest is EC */
+       store_cdrom_address(&buf[4], msf, lba);
+       return 8;
+}
+
+
+static int do_read_toc(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+       struct lun      *curlun = fsg->curlun;
+       int             msf = fsg->cmnd[1] & 0x02;
+       int             start_track = fsg->cmnd[6];
+       u8              *buf = (u8 *) bh->buf;
+
+       if ((fsg->cmnd[1] & ~0x02) != 0 ||              /* Mask away MSF */
+                       start_track > 1) {
+               curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+               return -EINVAL;
+       }
+
+       memset(buf, 0, 20);
+       buf[1] = (20-2);                /* TOC data length */
+       buf[2] = 1;                     /* First track number */
+       buf[3] = 1;                     /* Last track number */
+       buf[5] = 0x16;                  /* Data track, copying allowed */
+       buf[6] = 0x01;                  /* Only track is number 1 */
+       store_cdrom_address(&buf[8], msf, 0);
+
+       buf[13] = 0x16;                 /* Lead-out track is data */
+       buf[14] = 0xAA;                 /* Lead-out track number */
+       store_cdrom_address(&buf[16], msf, curlun->num_sectors);
+       return 20;
+}
+
+
 static int do_mode_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh)
 {
        struct lun      *curlun = fsg->curlun;
@@ -2848,6 +2942,26 @@ static int do_scsi_command(struct fsg_dev *fsg)
                        reply = do_read_capacity(fsg, bh);
                break;
 
+       case SC_READ_HEADER:
+               if (!mod_data.cdrom)
+                       goto unknown_cmnd;
+               fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]);
+               if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+                               (3<<7) | (0x1f<<1), 1,
+                               "READ HEADER")) == 0)
+                       reply = do_read_header(fsg, bh);
+               break;
+
+       case SC_READ_TOC:
+               if (!mod_data.cdrom)
+                       goto unknown_cmnd;
+               fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]);
+               if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+                               (7<<6) | (1<<1), 1,
+                               "READ TOC")) == 0)
+                       reply = do_read_toc(fsg, bh);
+               break;
+
        case SC_READ_FORMAT_CAPACITIES:
                fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]);
                if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
@@ -2933,6 +3047,7 @@ static int do_scsi_command(struct fsg_dev *fsg)
                // Fall through
 
        default:
+ unknown_cmnd:
                fsg->data_size_from_cmnd = 0;
                sprintf(unknown, "Unknown x%02x", fsg->cmnd[0]);
                if ((reply = check_command(fsg, fsg->cmnd_size,
@@ -3498,6 +3613,7 @@ static int open_backing_file(struct lun *curlun, const char *filename)
        struct inode                    *inode = NULL;
        loff_t                          size;
        loff_t                          num_sectors;
+       loff_t                          min_sectors;
 
        /* R/W if we can, R/O if we must */
        ro = curlun->ro;
@@ -3541,8 +3657,19 @@ static int open_backing_file(struct lun *curlun, const char *filename)
                rc = (int) size;
                goto out;
        }
-       num_sectors = size >> 9;        // File size in 512-byte sectors
-       if (num_sectors == 0) {
+       num_sectors = size >> 9;        // File size in 512-byte blocks
+       min_sectors = 1;
+       if (mod_data.cdrom) {
+               num_sectors &= ~3;      // Reduce to a multiple of 2048
+               min_sectors = 300*4;    // Smallest track is 300 frames
+               if (num_sectors >= 256*60*75*4) {
+                       num_sectors = (256*60*75 - 1) * 4;
+                       LINFO(curlun, "file too big: %s\n", filename);
+                       LINFO(curlun, "using only first %d blocks\n",
+                                       (int) num_sectors);
+               }
+       }
+       if (num_sectors < min_sectors) {
                LINFO(curlun, "file too small: %s\n", filename);
                rc = -ETOOSMALL;
                goto out;
@@ -3845,9 +3972,12 @@ static int __init fsg_bind(struct usb_gadget *gadget)
                goto out;
 
        if (mod_data.removable) {       // Enable the store_xxx attributes
-               dev_attr_ro.attr.mode = dev_attr_file.attr.mode = 0644;
-               dev_attr_ro.store = store_ro;
+               dev_attr_file.attr.mode = 0644;
                dev_attr_file.store = store_file;
+               if (!mod_data.cdrom) {
+                       dev_attr_ro.attr.mode = 0644;
+                       dev_attr_ro.store = store_ro;
+               }
        }
 
        /* Find out how many LUNs there should be */
@@ -3872,6 +4002,8 @@ static int __init fsg_bind(struct usb_gadget *gadget)
        for (i = 0; i < fsg->nluns; ++i) {
                curlun = &fsg->luns[i];
                curlun->ro = mod_data.ro[i];
+               if (mod_data.cdrom)
+                       curlun->ro = 1;
                curlun->dev.release = lun_release;
                curlun->dev.parent = &gadget->dev;
                curlun->dev.driver = &fsg_driver.driver;
@@ -4031,9 +4163,9 @@ static int __init fsg_bind(struct usb_gadget *gadget)
                        mod_data.protocol_name, mod_data.protocol_type);
        DBG(fsg, "VendorID=x%04x, ProductID=x%04x, Release=x%04x\n",
                        mod_data.vendor, mod_data.product, mod_data.release);
-       DBG(fsg, "removable=%d, stall=%d, buflen=%u\n",
+       DBG(fsg, "removable=%d, stall=%d, cdrom=%d, buflen=%u\n",
                        mod_data.removable, mod_data.can_stall,
-                       mod_data.buflen);
+                       mod_data.cdrom, mod_data.buflen);
        DBG(fsg, "I/O thread pid: %d\n", task_pid_nr(fsg->thread_task));
 
        set_bit(REGISTERED, &fsg->atomic_bitflags);