www.pudn.com > Linux2410_device.rar > storage.c


/*
 * storage_fd/storage.c
 *
 * Copyright (c) 2000, 2001, 2002 Lineo
 * Copyright (c) 2001 Hewlett Packard
 *
 * By: 
 *      Stuart Lynne , 
 *      Tom Rushworth , 
 *      Bruce Balden 
 *
 * Copyright (C) 2002 Toshiba Corporation
 *
 * Changes copyright (C) 2003 MontaVista Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
 * This module implements USB Mass Storage Class
 * (SubClass: RBC, Transport: Bulk-Only)
 *
 * Usage:
 */

#include 
#include 

#include "../usbd-export.h"
#include "../usbd-build.h"
#include "../usbd-module.h"


MODULE_AUTHOR("sl@lineo.com, tbr@lineo.com, TOSHIBA Corporation");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("USB Device Mass Storage Function");

#define STORAGE_MOD_NAME	"storage_fd"
USBD_MODULE_INFO(STORAGE_MOD_NAME " 0.1-beta");

#ifndef MODULE
#undef GET_USE_COUNT
#define GET_USE_COUNT(foo) 1
#endif

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define __KERNEL_SYSCALLS__
static int errno;
#include 

#include "../usbd.h"
#include "../usbd-func.h"
#include "../usbd-bus.h"
#include "../usbd-debug.h"
#include "../usbd-inline.h"
#include "../usbd-arch.h"

#define MAX_STORAGES	1
#define STORAGE_DEFAULT_FILENAME	"/dev/ram0"
#define STORAGE_BLOCK_SIZE	512
#define STORAGE_PROC_NAME	"driver/" STORAGE_MOD_NAME

#define USB_REQ_BO_MASS_STORAGE_RESET	0xFF
#define USB_REQ_GET_MAX_LUN		0xFE

#if 0
/* RBC 6.1 states "RBC devices are identified by a PERIPHERAL DEVICE
 * TYPE 0Eh" */
#define PERIPHERAL_DEVICE_TYPE	0x0e
#else
/* XXX: Linux sd,scsi stack seems to expect this value... */
#define PERIPHERAL_DEVICE_TYPE	0x00
#endif

#if !defined (CONFIG_USBD_VENDORID) && !defined(CONFIG_USBD_STORAGE_VENDORID)
        #error No Vendor ID
#endif
#if !defined (CONFIG_USBD_PRODUCTID) && !defined(CONFIG_USBD_STORAGE_PRODUCTID)
        #error No Product ID
#endif


#if CONFIG_USBD_STORAGE_VENDORID
        #undef CONFIG_USBD_VENDORID
        #define CONFIG_USBD_VENDORID CONFIG_USBD_STORAGE_VENDORID
#endif

#if CONFIG_USBD_STORAGE_PRODUCTID
        #undef CONFIG_USBD_PRODUCTID
        #define CONFIG_USBD_PRODUCTID CONFIG_USBD_STORAGE_PRODUCTID
#endif

#ifndef CONFIG_USBD_SERIAL_NUMBER_STR
        #define CONFIG_USBD_SERIAL_NUMBER_STR	"000000000000"
#endif


#ifdef CONFIG_USBD_SELFPOWERED
        #define BMATTRIBUTE BMATTRIBUTE_RESERVED | BMATTRIBUTE_SELF_POWERED
        #define BMAXPOWER 0
#else
        #define BMATTRIBUTE BMATTRIBUTE_RESERVED
        #define BMAXPOWER CONFIG_USBD_MAXPOWER
#endif


/*
 * setup some default values for pktsizes and endpoint addresses.
 */

#ifndef CONFIG_USBD_STORAGE_OUT_PKTSIZE
        #define CONFIG_USBD_STORAGE_OUT_PKTSIZE             64
#endif

#ifndef CONFIG_USBD_STORAGE_IN_PKTSIZE
        #define CONFIG_USBD_STORAGE_IN_PKTSIZE              64
#endif

#ifndef CONFIG_USBD_STORAGE_OUT_ENDPOINT
    #define CONFIG_USBD_STORAGE_OUT_ENDPOINT                1
#endif

#ifndef CONFIG_USBD_STORAGE_IN_ENDPOINT
    #define CONFIG_USBD_STORAGE_IN_ENDPOINT                 2
#endif


/*
 * check for architecture specific endpoint configurations
 */

#if     defined(ABS_OUT_ADDR) 
        #warning
        #warning USING ABS ENDPOINT OUT ADDRESS
        #undef CONFIG_USBD_STORAGE_OUT_ENDPOINT 

        #if     ABS_OUT_ADDR
                #define CONFIG_USBD_STORAGE_OUT_ENDPOINT        ABS_OUT_ADDR
        #endif

#if     defined(ABS_IN_ADDR) 
        #warning
        #warning USING ABS ENDPOINT IN ADDRESS
        #undef CONFIG_USBD_STORAGE_IN_ENDPOINT 

        #if     ABS_IN_ADDR
                #define CONFIG_USBD_STORAGE_IN_ENDPOINT         ABS_IN_ADDR
        #endif

#elif   defined(MAX_OUT_ADDR) && defined(CONFIG_USBD_STORAGE_OUT_ENDPOINT) && (CONFIG_USBD_STORAGE_OUT_ENDPOINT > MAX_OUT_ADDR)
        #warning
        #warning USING DEFAULT ENDPOINT OUT ADDRESS
        #undef CONFIG_USBD_STORAGE_OUT_ENDPOINT 
        #define CONFIG_USBD_STORAGE_OUT_ENDPOINT            DFL_OUT_ADDR
#endif

#elif   defined(MAX_IN_ADDR) && defined(CONFIG_USBD_STORAGE_IN_ENDPOINT) && (CONFIG_USBD_STORAGE_IN_ENDPOINT > MAX_IN_ADDR)
        #warning
        #warning USING DEFAULT ENDPOINT IN ADDRESS
        #undef CONFIG_USBD_STORAGE_IN_ENDPOINT 
        #define CONFIG_USBD_STORAGE_IN_ENDPOINT             DFL_IN_ADDR
#endif

#if     defined(MAX_OUT_PKTSIZE) && defined(CONFIG_USBD_STORAGE_OUT_PKTSIZE) && CONFIG_USBD_STORAGE_OUT_PKTSIZE > MAX_OUT_PKTSIZE
        #warning
        #warning OVERIDING ENDPOINT OUT PKTSIZE
        #undef CONFIG_USBD_STORAGE_OUT_PKTSIZE
        #define CONFIG_USBD_STORAGE_OUT_PKTSIZE             MAX_OUT_PKTSIZE
#endif

#if     defined(MAX_IN_PKTSIZE) && defined(CONFIG_USBD_STORAGE_IN_PKTSIZE) && CONFIG_USBD_STORAGE_IN_PKTSIZE > MAX_IN_PKTSIZE
        #warning
        #warning OVERIDING ENDPOINT IN PKTSIZE
        #undef CONFIG_USBD_STORAGE_IN_PKTSIZE
        #define CONFIG_USBD_STORAGE_IN_PKTSIZE              MAX_IN_PKTSIZE
#endif

enum usb_storage_device_state {
    STATE_INVALID,
    STATE_IDLE,	/* Device intends to receive command */
    STATE_DN,	/* Device intends to transfer no data */
    STATE_DI,	/* Device intends to send data to the host */
    STATE_DO,	/* Device intends to receive data from the host */
};

#define CSW_STAT_GOOD	0x00
#define CSW_STAT_FAILED	0x01
#define CSW_STAT_PERR	0x02	/* Phase Error */

/* Command Block Wrapper */
#define MAX_CBLENGTH	16
struct CBW {
    u32	dSignature;
    u32 dTag;
    u32 dDataTransferLength;
    u8	bmFlags;
    u8	bLUN;
    u8	bCBLength;
    u8	CB[MAX_CBLENGTH];
} __attribute__((packed));
#define CBW_SIGNATURE	0x43425355

/* Command Status Wrapper */
struct CSW {
    u32	dSignature;
    u32	dTag;
    u32	dDataResidue;
    u8	bStatus;
} __attribute__((packed));
#define CSW_SIGNATURE	0x53425355

struct usb_storage_threaddata {
    int busy;
    struct CBW cbw;	/* cpu endian */
    struct CSW csw;	/* cpu endian */
    u8 *data_buf;
    /* NOTE: data_len CAN exceed cbw.dDataTransferLength */
    unsigned int data_len;
    unsigned int data_alloclen;

    /* sense data */
    struct {
	u8 key;
	u32 info;
	u32 cmdinfo;
	u8 code;
    } sense;

    /* real storage */
    struct file  *real_fd;
    char filename[128];
    unsigned int num_blocks;

    /* statistics */
    struct {
	/* transmit statistics */
	unsigned int read_blocks;
	unsigned int write_blocks;
	/* command statictics */
	unsigned int inquiry;
	unsigned int mode_select;
	unsigned int mode_sense;
	unsigned int read_10;
	unsigned int read_capacity;
	unsigned int request_sense;
	unsigned int start_stop;
	unsigned int test_unit_ready;
	unsigned int verify;
	unsigned int write_10;
	unsigned int write_buffer;
	unsigned int unsupported;
    } stat;
};

struct usb_storage_private {
    struct usb_device_instance *device;
    enum usb_storage_device_state devstate;

    struct usb_storage_threaddata tdata;
};

static spinlock_t storage_lock;
static struct usb_storage_private storage_private;	// one and only storage

static DECLARE_MUTEX_LOCKED(storage_sem_start);
static DECLARE_MUTEX_LOCKED(storage_sem_work);
static int storage_thread_terminating;

static void storage_thread_poke(void)
{
    up(&storage_sem_work);
}

/* SCSI related definitions */
/*
 *	Sense codes
 */
 
#define SENCODE_NO_SENSE                        0x00
#define SENCODE_END_OF_DATA                     0x00
#define SENCODE_BECOMING_READY                  0x04
#define SENCODE_INIT_CMD_REQUIRED               0x04
#define SENCODE_PARAM_LIST_LENGTH_ERROR         0x1A
#define SENCODE_INVALID_COMMAND                 0x20
#define SENCODE_LBA_OUT_OF_RANGE                0x21
#define SENCODE_INVALID_CDB_FIELD               0x24
#define SENCODE_LUN_NOT_SUPPORTED               0x25
#define SENCODE_INVALID_PARAM_FIELD             0x26
#define SENCODE_PARAM_NOT_SUPPORTED             0x26
#define SENCODE_PARAM_VALUE_INVALID             0x26
#define SENCODE_RESET_OCCURRED                  0x29
#define SENCODE_LUN_NOT_SELF_CONFIGURED_YET     0x3E
#define SENCODE_INQUIRY_DATA_CHANGED            0x3F
#define SENCODE_SAVING_PARAMS_NOT_SUPPORTED     0x39
#define SENCODE_DIAGNOSTIC_FAILURE              0x40
#define SENCODE_INTERNAL_TARGET_FAILURE         0x44
#define SENCODE_INVALID_MESSAGE_ERROR           0x49
#define SENCODE_LUN_FAILED_SELF_CONFIG          0x4c
#define SENCODE_OVERLAPPED_COMMAND              0x4E

/* Module Parameters ************************************************************************* */

static char *dbg = NULL;
static u32 vendor_id;
static u32 product_id;
static char *serial_number = NULL;
static char *filename = NULL;

MODULE_PARM(dbg, "s");
MODULE_PARM(vendor_id, "i");
MODULE_PARM(product_id, "i");
MODULE_PARM(serial_number, "s");
MODULE_PARM(filename, "s");

MODULE_PARM_DESC(dbg, "USB Device Debug options");
MODULE_PARM_DESC(vendor_id, "USB Device Vendor ID");
MODULE_PARM_DESC(product_id, "USB Device Product ID");
MODULE_PARM_DESC(serial_number, "USB Device Serial Number");
MODULE_PARM_DESC(filename, "USB Device Storage Filename");



/* Debug switches (module parameter "dbg=...") *********************************************** */

extern int dbgflg_usbdfd_init;
int      dbgflg_usbdfd_ep0;
int      dbgflg_usbdfd_usbe;
int      dbgflg_usbdfd_tx;
int      dbgflg_usbdfd_rx;

static debug_option dbg_table[] = {
    {&dbgflg_usbdfd_init,NULL,"init","initialization and termination"},
    {&dbgflg_usbdfd_ep0,NULL,"ep0","End Point 0 (setup) packet handling"},
    {&dbgflg_usbdfd_usbe,NULL,"usbe","USB events"},
    {&dbgflg_usbdfd_tx,NULL,"tx","transmit (to host)"},
    {&dbgflg_usbdfd_rx,NULL,"rx","receive (from host)"},
    {NULL,NULL,NULL,NULL}
};

#define dbg_init(lvl,fmt,args...) dbgPRINT(dbgflg_usbdfd_init,lvl,fmt,##args)
#define dbg_ep0(lvl,fmt,args...) dbgPRINT(dbgflg_usbdfd_ep0,lvl,fmt,##args)
#define dbg_usbe(lvl,fmt,args...) dbgPRINT(dbgflg_usbdfd_usbe,lvl,fmt,##args)
#define dbg_tx(lvl,fmt,args...) dbgPRINT(dbgflg_usbdfd_tx,lvl,fmt,##args)
#define dbg_rx(lvl,fmt,args...) dbgPRINT(dbgflg_usbdfd_rx,lvl,fmt,##args)

/* ******************************************************************************************* */

/* Mass Storage Class descriptions 
 */

#define STORAGE_TRANSFER_SIZE	2048	/* XXX max 0xfff. why? (see usbd-bi.c) */
static struct usb_endpoint_description storage_default[] = {
    { bEndpointAddress: CONFIG_USBD_STORAGE_OUT_ENDPOINT,
        bmAttributes: BULK,
        wMaxPacketSize: CONFIG_USBD_STORAGE_OUT_PKTSIZE,
        bInterval: 0,
        direction: OUT,
        transferSize: STORAGE_TRANSFER_SIZE, },

    { bEndpointAddress: CONFIG_USBD_STORAGE_IN_ENDPOINT,
        bmAttributes: BULK,
        wMaxPacketSize: CONFIG_USBD_STORAGE_IN_PKTSIZE,
        bInterval: 0,
        direction: IN,
        transferSize: STORAGE_TRANSFER_SIZE, },

};

/* Data Interface Alternate description(s)
 */
static __initdata struct usb_alternate_description storage_data_alternate_descriptions[] = {
    {   iInterface: "Simple Mass Storage Data Interface - Bulk-Only", 
        bAlternateSetting: 0,
	classes: 0,
	class_list: NULL,
        endpoints: sizeof(storage_default)/sizeof(struct usb_endpoint_description),
        endpoint_list: storage_default, },
};

/* Interface description(s)
 */
static __initdata struct usb_interface_description storage_interfaces[] = {
    {   iInterface: "Simple Mass Storage Data Interface", 
        bInterfaceClass: USB_CLASS_MASS_STORAGE,
	bInterfaceSubClass: 0x05, /*removeble*/
        bInterfaceProtocol: 0x50, /* Bulk-Only Transport */
        alternates: sizeof(storage_data_alternate_descriptions)/sizeof(struct usb_alternate_description),
        alternate_list: storage_data_alternate_descriptions, },
};


/* Configuration description(s)
 */
struct __initdata usb_configuration_description storage_description[] = {
    {   iConfiguration: "USB Simple Mass Storage Configuration", 
        bmAttributes: BMATTRIBUTE,
        bMaxPower: BMAXPOWER,
        interfaces: sizeof(storage_interfaces)/sizeof(struct usb_interface_description),
        interface_list: storage_interfaces, },
};

/* Device Description
 */
struct __initdata usb_device_description storage_device_description = {
    bDeviceClass:       0, 
    bDeviceSubClass:    0,
    bDeviceProtocol:    0,
    idVendor:           CONFIG_USBD_VENDORID,
    idProduct:          CONFIG_USBD_PRODUCTID,
    iManufacturer:      CONFIG_USBD_MANUFACTURER,
    iProduct:           CONFIG_USBD_PRODUCT_NAME,
    iSerialNumber:      CONFIG_USBD_SERIAL_NUMBER_STR,
};

static int storage_receive_CBW(struct usb_storage_private *private,
			       u8 *buf, unsigned int len)
{
    struct CBW *cbwp = (struct CBW *)buf;
    struct usb_storage_threaddata *tdata = &private->tdata;

#if 0	/* bad bi driver */
    if (len < sizeof(struct CBW)) {
	printk(KERN_ERR STORAGE_MOD_NAME ": bad CBW length (%d)\n", len);
	return -1;
    }
#else
    if (len != sizeof(struct CBW)) {
	printk(KERN_ERR STORAGE_MOD_NAME ": bad CBW length (%d)\n", len);
	return -1;
    }
#endif
    if (le32_to_cpu(cbwp->dSignature) != CBW_SIGNATURE) {
	printk(KERN_ERR STORAGE_MOD_NAME ": bad CBW signature (%x)\n",
	       le32_to_cpu(cbwp->dSignature));
	return -1;
    }
    if (cbwp->bLUN != 0) {
	printk(KERN_ERR STORAGE_MOD_NAME ": bad CBW LUN (%x)\n", cbwp->bLUN);
	return -1;
    }

    tdata->cbw = *cbwp;
    tdata->cbw.dSignature = le32_to_cpu(tdata->cbw.dSignature);
    tdata->cbw.dTag = le32_to_cpu(tdata->cbw.dTag);
    tdata->cbw.dDataTransferLength = le32_to_cpu(tdata->cbw.dDataTransferLength);
    tdata->csw.dSignature = CSW_SIGNATURE;
    tdata->csw.dTag = tdata->cbw.dTag;
    tdata->csw.dDataResidue = tdata->cbw.dDataTransferLength;
    tdata->csw.bStatus = CSW_STAT_GOOD;

    return len - sizeof(struct CBW);
}

static int storage_send_CSW(struct usb_storage_private *private)
{
    struct urb *urb;
    int port = 0; // XXX compound device
    struct usb_storage_threaddata *tdata = &private->tdata;

    urb = usbd_alloc_urb(private->device,
			 private->device->function_instance_array+port,
			 CONFIG_USBD_STORAGE_IN_ENDPOINT | IN, sizeof (struct CSW));
    if (!urb) {
	printk(KERN_ERR STORAGE_MOD_NAME ": failed to alloc CSW urb\n");
	return -EINVAL;
    }


    tdata->csw.dSignature = cpu_to_le32(tdata->csw.dSignature);
    tdata->csw.dTag = cpu_to_le32(tdata->csw.dTag);
    tdata->csw.dDataResidue = cpu_to_le32(tdata->csw.dDataResidue);
    memcpy(urb->buffer, &tdata->csw, sizeof(struct CSW));
    urb->actual_length = sizeof(struct CSW);
    dbg_rx(3,"CSW (length %d)", urb->actual_length);
    dbgPRINTmem(dbgflg_usbdfd_tx,3,urb->buffer,min(urb->actual_length, 32u));
    if (usbd_send_urb(urb))
	return -EINVAL;
    return 0;
}

/* storage_urb_sent - called to indicate URB transmit finished
 * @urb: pointer to struct urb
 * @rc: result
 */
int storage_urb_sent (struct urb *urb, int status)
{
    dbg_tx(2,"%s length: %d status : %x",urb->device->name, urb->actual_length, status);
    usbd_dealloc_urb(urb);

    return 0;
}

/* storage_recv_urb - called to indicate URB has been received
 * @urb - pointer to struct urb
 *
 * Return non-zero if we failed and urb is still valid (not disposed)
 * NOTE: This function is called in bottom half context.
 */
int storage_recv_urb (struct urb *urb)
{
    int port = 0; // XXX compound device
    struct usb_device_instance *device = urb->device;
    struct usb_storage_private *private = (device->function_instance_array+port)->privdata;
    struct usb_storage_threaddata *tdata = &private->tdata;
#if 0	/* bad bi driver */
    unsigned int datalen;
#endif

    if (urb->status != RECV_OK)
	return 1;
    dbg_rx(2,"length=%d",urb->actual_length);
    dbgPRINTmem(dbgflg_usbdfd_rx,3,urb->buffer,min(urb->actual_length, 32u));

    // push the data up
    spin_lock(&storage_lock);
    if (private->tdata.busy) {
	printk(KERN_ERR STORAGE_MOD_NAME ": receive in busy state (%d)\n",
	       private->devstate);
	private->devstate = STATE_INVALID;
	spin_unlock(&storage_lock);
	usbd_recycle_urb(urb);	// free urb
	return 0;
    }
    switch (private->devstate) {
    case STATE_IDLE:
	tdata->data_len = 0;
	if (storage_receive_CBW(private, urb->buffer, urb->actual_length) < 0) {
	    private->devstate = STATE_INVALID;
	    break;
	}
#if 0	/* bad bi driver */
	datalen = 0;
	if (urb->actual_length > sizeof(struct CBW)) {
	    datalen = urb->actual_length - sizeof(struct CBW);
	    /* buf contains CBW and (part of) DATA */
	    if (tdata->cbw.dDataTransferLength == 0 ||
		(tdata->cbw.bmFlags & 0x80) ||
		tdata->cbw.dDataTransferLength < datalen) {
		printk(KERN_ERR STORAGE_MOD_NAME ": bad DATA length (%d)\n",
		       datalen);
		private->devstate = STATE_INVALID;
		break;
	    }
	}
#endif

	if (tdata->cbw.dDataTransferLength == 0) {
	    private->devstate = STATE_DN;
	    private->tdata.busy = 1;
	    storage_thread_poke();
	    break;
	}
	if (tdata->data_alloclen < tdata->cbw.dDataTransferLength) {
	    /* expand data buffer */
	    if (tdata->data_buf)
		kfree(tdata->data_buf);
	    tdata->data_buf = kmalloc(tdata->cbw.dDataTransferLength, GFP_ATOMIC);
	    if (!tdata->data_buf) {
		printk(KERN_ERR STORAGE_MOD_NAME ": failed to expand buffer (%d)\n",
		       tdata->cbw.dDataTransferLength);
		tdata->data_alloclen = 0;
		private->devstate = STATE_INVALID;
		break;
	    }
	    tdata->data_alloclen = tdata->cbw.dDataTransferLength;
	}
	if (tdata->cbw.bmFlags & 0x80) {	/* IN */
	    private->devstate = STATE_DI;
	    private->tdata.busy = 1;
	    storage_thread_poke();
	} else {	/* OUT */
#if 0	/* bad bi driver */
	    if (datalen > 0) {
		memcpy(tdata->data_buf, urb->buffer + sizeof(struct CBW),
		       datalen);
		tdata->data_len = datalen;
		if (tdata->data_len >= tdata->cbw.dDataTransferLength) {
		    /* all data received */
		    private->devstate = STATE_DN;
		    private->tdata.busy = 1;
		    storage_thread_poke();
		    break;
		}
	    }
#endif
	    private->devstate = STATE_DO;
	}
	break;
    case STATE_DO:
	if (tdata->data_len + urb->actual_length > tdata->cbw.dDataTransferLength) {
	    printk(KERN_ERR STORAGE_MOD_NAME ": bad DATA length (%d + %d > %d)\n",
		   tdata->data_len, urb->actual_length,
		   tdata->cbw.dDataTransferLength);
	    private->devstate = STATE_INVALID;
	    break;
	}
	memcpy(tdata->data_buf + tdata->data_len,
	       urb->buffer, urb->actual_length);
	tdata->data_len += urb->actual_length;
	if (tdata->data_len >= tdata->cbw.dDataTransferLength) {
	    /* all data received */
	    private->devstate = STATE_DN;
	    private->tdata.busy = 1;
	    storage_thread_poke();
	}
	break;
    case STATE_DI:
    default:
	printk(KERN_ERR STORAGE_MOD_NAME ": receive in bad state (%d)\n",
	       private->devstate);
	private->devstate = STATE_INVALID;
    }
    spin_unlock(&storage_lock);

    usbd_recycle_urb(urb);	// free urb
    return(0);
}

/**
 * storage_recv_setup - called with a control URB 
 * @urb - pointer to struct urb
 *
 * Check if this is a setup packet, process the device request, put results
 * back into the urb and return zero or non-zero to indicate success (DATA)
 * or failure (STALL).
 *
 * This routine IS called at interrupt time. Please use the usual precautions.
 *
 */
int storage_recv_setup (struct urb *urb)
{
    struct usb_device_request *request;
    struct usb_device_instance *device = urb->device;
    int port = 0;
    struct usb_storage_private *private = (device->function_instance_array+port)->privdata;

    request = &urb->device_request;

    // handle Mass Storage Class-Specific Request
    // c.f. USB Mass Storage Class Bulk-Only Transport 3.1, 3.2
    if ((request->bmRequestType & (USB_REQ_TYPE_MASK|USB_REQ_RECIPIENT_MASK)) !=
	 (USB_REQ_TYPE_CLASS | USB_REQ_RECIPIENT_INTERFACE)) {
        dbg_ep0(1, "not class/interface request: %x", request->bmRequestType);
        return 0; // XXX
    }

    if ((request->bmRequestType&USB_REQ_DIRECTION_MASK)) {
        dbg_ep0(1, "Device-to-Host");
        switch (request->bRequest) {
        case USB_REQ_GET_MAX_LUN:
	    urb->actual_length = 1;
	    urb->buffer[0] = 0;	// only one LUN
	    break;
	default:
	    dbg_ep0(1, "Unknown request: %x", request->bRequest);
	}
    }
    else {
        dbg_ep0(1, "Host-to-Device");
        switch (request->bRequest) {
        case USB_REQ_BO_MASS_STORAGE_RESET:
	    /* do job in event handler */
	    usbd_device_event(private->device, DEVICE_FUNCTION_PRIVATE, 0);
            break;
	default:
	    dbg_ep0(1, "Unknown request: %x", request->bRequest);
        }
    }
    return 0;
}

/* USB Device Functions ************************************************************************ */

/* proc interface */
static int storage_read_proc(char *page, char **start, off_t off,
			     int count, int *eof, void *data)
{
    char *p = page;
    struct usb_storage_private *private = &storage_private;

    spin_lock_bh(&storage_lock);	/* prevent event handler */
    if (private->device) {
	struct usb_storage_threaddata *tdata = &private->tdata;
	p += sprintf(p, "storage \"%s\" %d blocks\n",
		     tdata->filename, tdata->num_blocks);
	p += sprintf(p, "state: %d\n", private->devstate);
	p += sprintf(p, "transfer statistics:\n");
	p += sprintf(p, "read_blocks\t%u\n", tdata->stat.read_blocks);
	p += sprintf(p, "write_blocks\t%u\n", tdata->stat.write_blocks);
	p += sprintf(p, "command statistics:\n");
	p += sprintf(p, "inquiry\t%u\n", tdata->stat.inquiry);
	p += sprintf(p, "mode_select\t%u\n", tdata->stat.mode_select);
	p += sprintf(p, "mode_sense\t%u\n", tdata->stat.mode_sense);
	p += sprintf(p, "read_10\t%u\n", tdata->stat.read_10);
	p += sprintf(p, "read_capacity\t%u\n", tdata->stat.read_capacity);
	p += sprintf(p, "request_sense\t%u\n", tdata->stat.request_sense);
	p += sprintf(p, "start_stop\t%u\n", tdata->stat.start_stop);
	p += sprintf(p, "test_unit_ready\t%u\n", tdata->stat.test_unit_ready);
	p += sprintf(p, "verify\t%u\n", tdata->stat.verify);
	p += sprintf(p, "write_10\t%u\n", tdata->stat.write_10);
	p += sprintf(p, "write_buffer\t%u\n", tdata->stat.write_buffer);
	p += sprintf(p, "unsupported\t%u\n", tdata->stat.unsupported);
    }
    spin_unlock_bh(&storage_lock);

    return p - page;
}

/* storage_event - process a device event
 *
 * NOTE: This function is called from keventd kernel thread.
 */
void storage_event(struct usb_device_instance *device, usb_device_event_t event, int data)
{
    int port = 0; // XXX compound device
    struct usb_function_instance *function;

    dbg_usbe(5,"%d",event);

    if ((function = device->function_instance_array+port)==NULL){
        dbg_usbe(1,"no function");
        return;
    }

    dbg_usbe(3,"---> %s %d", device->name, event);
    switch (event) {

    case DEVICE_UNKNOWN:
    case DEVICE_INIT:
        dbg_usbe(1,"---> INIT %s %d", device->name, event);
        break;

    case DEVICE_CREATE:
        dbg_usbe(1,"---> CREATE %s %d", device->name, event);
        {
            struct usb_storage_private *private;

            // There is no way to indicate error, so make this unconditional
            // and undo it in the DESTROY event unconditionally as well.
            // It the responsibility of the USBD core and the bus interface
            // to see that there is a matching DESTROY for every CREATE.

	    spin_lock(&storage_lock);
	    if (storage_private.device) {
                dbg_usbe(1,"---> CREATE no free storage");
		spin_unlock(&storage_lock);
		return;
	    }
	    private = &storage_private;
	    private->device = device;
	    private->devstate = STATE_IDLE;
            function->privdata = private;
	    spin_unlock(&storage_lock);

            dbg_usbe(1,"---> START %s privdata assigned: %p", device->name, private); 
            return;
        }
        break;

    case DEVICE_HUB_CONFIGURED:
        break;
    case DEVICE_RESET:
        break;
    case DEVICE_ADDRESS_ASSIGNED:
        break;
    case DEVICE_CONFIGURED:
        dbg_usbe(1,"---> CONFIGURED %s %d", device->name, event);
        break;
    case DEVICE_SET_INTERFACE:
        break;
    case DEVICE_SET_FEATURE:
        break;
    case DEVICE_CLEAR_FEATURE:
        break;
    case DEVICE_DE_CONFIGURED:
        dbg_usbe(1,"---> DECONFIGURED %s %d", device->name, event);
        break;
    case DEVICE_BUS_INACTIVE:
        break;
    case DEVICE_BUS_ACTIVITY:
        break;
    case DEVICE_POWER_INTERRUPTION:
        break;
    case DEVICE_HUB_RESET:
        break;

    case DEVICE_DESTROY:
        dbg_usbe(1, "---> DESTROY %s %d", device->name, event);
        {
            struct usb_storage_private *private;
	    struct usb_storage_threaddata *tdata;

            if ((private = (device->function_instance_array+port)->privdata) == NULL) {
                dbg_usbe(1, "---> DESTROY %s private null", device->name);
                return;
            }
            dbg_usbe(1, "---> DESTROY %s private %p", device->name, private);
	    tdata = &private->tdata;

	    spin_lock(&storage_lock);
	    private->device = NULL;
	    private->devstate = STATE_INVALID;
	    if (tdata->busy) {
		/* free on DEVICE_FUNCTION_PRIVATE */
	    } else {
		if (tdata->data_buf)
		    kfree(tdata->data_buf);
		tdata->data_buf = NULL;
		tdata->data_len = tdata->data_alloclen = 0;
	    }
	    spin_unlock(&storage_lock);

            dbg_usbe(1,"---> STOP %s",device->name); 
            return;
        }
        break;

    case DEVICE_FUNCTION_PRIVATE:
        dbg_usbe(2,"---> FUNCTION_PRIVATE %s %d", device->name, event);
	{
            struct usb_storage_private *private;
	    struct usb_storage_threaddata *tdata;
            if ((private = (device->function_instance_array+port)->privdata) == NULL) {
                dbg_usbe(1, "---> PRIVATE %s private null", device->name);
                return;
            }
            dbg_usbe(2, "---> PRIVATE %s private %p", device->name, private);
	    tdata = &private->tdata;

	    spin_lock_bh(&storage_lock);
	    if (data == 0) {
		dbg_rx(1, "USB_REQ_BO_MASS_STORAGE_RESET received.");
		private->devstate = STATE_IDLE;
	    } else {
		/* kicked by storage_thread */
		struct urb *urb;

		if (!private->tdata.busy) {
		    /* command completed. send data and status */
		    switch (private->devstate) {
		    case STATE_DI:
			urb = usbd_alloc_urb(private->device,
					     private->device->function_instance_array+port,
					     CONFIG_USBD_STORAGE_IN_ENDPOINT | IN,
					     tdata->cbw.dDataTransferLength);
			if (!urb) {
			    printk(KERN_ERR STORAGE_MOD_NAME ": failed to alloc DATA urb\n");
			    private->devstate = STATE_INVALID;
			    break;
			}
#if 0
			if (tdata->data_len < tdata->cbw.dDataTransferLength) {
			    dbg_tx(2, "fill data to pad up (%d < %d)",
				   tdata->data_len,
				   tdata->cbw.dDataTransferLength);
			    memset(tdata->data_buf + tdata->data_len, 0,
				   tdata->cbw.dDataTransferLength - tdata->data_len);
			    tdata->data_len = tdata->cbw.dDataTransferLength;
			}
#endif
			memcpy(urb->buffer, tdata->data_buf, tdata->data_len);
			urb->actual_length = tdata->data_len;
			tdata->csw.dDataResidue -= urb->actual_length;
			dbg_rx(3,"DATA (length %d)", urb->actual_length);
			dbgPRINTmem(dbgflg_usbdfd_tx,3,urb->buffer,min(urb->actual_length, 32u));
			if (usbd_send_urb(urb)) {
			    private->devstate = STATE_INVALID;
			    break;
			}
			/* FALLTHRU */
		    case STATE_DN:
			/* It's important to stall endpoint here */
			if(tdata->sense.key==ILLEGAL_REQUEST)  {
			     if ( tdata->cbw.CB[0] != REQUEST_SENSE  || tdata->sense.code == SENCODE_LBA_OUT_OF_RANGE  ) {
				  usbd_device_feature(private->device, CONFIG_USBD_STORAGE_IN_ENDPOINT | IN, 1);
				  usbd_device_feature(private->device, CONFIG_USBD_STORAGE_IN_ENDPOINT | IN, 0);
			     }

			}
			if (storage_send_CSW(private) < 0) {
			    private->devstate = STATE_INVALID;
			    break;
			}
			private->devstate = STATE_IDLE;
			break;
		    case STATE_IDLE:
			/* USB_REQ_BO_MASS_STORAGE_RESET received during
			   command processing */
			break;
		    default:
			private->devstate = STATE_INVALID;
			if (tdata->data_buf)
			    kfree(tdata->data_buf);
			tdata->data_buf = NULL;
			tdata->data_len = tdata->data_alloclen = 0;
		    }
		}
	    }
	    spin_unlock_bh(&storage_lock);
	}
        break;

    }
}


struct usb_function_operations function_ops = {
    event: storage_event,
    recv_urb: storage_recv_urb,
    urb_sent: storage_urb_sent,
    recv_setup: storage_recv_setup
};

struct usb_function_driver function_driver = {
    name: "usbd storage",
    ops: &function_ops,
    device_description: &storage_device_description,
    configurations: sizeof(storage_description)/sizeof(struct usb_configuration_description),
    configuration_description: storage_description,
    this_module: THIS_MODULE,
};


/*
 * RBC functions
 */

static void add_in_data(struct usb_storage_threaddata *tdata,
			void *buf, unsigned int len)
{
    if (tdata->data_len < tdata->cbw.dDataTransferLength) {
	memcpy(tdata->data_buf + tdata->data_len, buf,
	       min(tdata->cbw.dDataTransferLength - tdata->data_len, len));
    }
    tdata->data_len += len;
}

static int do_vpd(struct usb_storage_threaddata *tdata,
		  int page, unsigned int alloclen)
{
    u8 data[256];
    unsigned int len = 4;

    switch (page) {
    case 0x00:	/* Supported vital product data pages */
	data[4+0] = 0x00;	/* page list */
	data[4+1] = 0x80;
	data[4+2] = 0x83;
	len += 3;
	break;
    case 0x80:	/* Unit Serial Number */
	strcpy((char *)&data[4], storage_device_description.iSerialNumber);
	len += strlen(storage_device_description.iSerialNumber);
	break;
    case 0x83:	/* Vital Product Data Device Identification */
	sprintf(&data[4+4], "%s %s %s",
		storage_device_description.iManufacturer,
		storage_device_description.iProduct,
		storage_device_description.iSerialNumber);
	data[4+0] = 0x02;	/* ASCII */
	data[4+1] = 0x01;
	data[4+3] = strlen((char *)&data[4+4]);
	len += 4 + data[3];
	break;
    default:
	return -EINVAL;
    }
    data[0] = PERIPHERAL_DEVICE_TYPE;
    data[1] = page;	/* page code */
    data[2] = 0;
    data[3] = len - 4;
    add_in_data(tdata, data, min(len, alloclen));
    return 0;
}

static int do_inquiry(struct usb_storage_threaddata *tdata)
{
    u8 *CB = tdata->cbw.CB;
    u8 data[4 + 4 + 8 + 16 + 4];
    unsigned int len = 4 + 4 + 8 + 16 + 4;

    if (CB[1] & 0x02)	/* CMDDT */
	goto invalid_cdb_field;
    if (CB[1] & 0x01) {	/* EVPD */
	if (do_vpd(tdata, CB[2], CB[4]) < 0)
	    goto invalid_cdb_field;
	return 0;
    }
    if (CB[2])
	goto invalid_cdb_field;

    data[0] = PERIPHERAL_DEVICE_TYPE;
    data[1] = 0x80;           /* removal */
    data[2] = 0x00;
    data[3] = 0x01;
    data[4] = 4 + 8 + 16 + 4;
    data[5] = 0x00;
    data[6] = 0x00;
    data[7] = 0x00;
    memset(&data[8], ' ', 8 + 16 + 4);
    memcpy(&data[8], storage_device_description.iManufacturer,
	   min_t(int, 8, strlen(storage_device_description.iManufacturer)));
    memcpy(&data[16], storage_device_description.iProduct,
	   min_t(int, 16, strlen(storage_device_description.iProduct)));
    add_in_data(tdata, data, min(len, (unsigned int)CB[4]));
    return 0;

  invalid_cdb_field:
    printk(KERN_WARNING STORAGE_MOD_NAME ": Invalid CDB field\n");
    tdata->sense.key = ILLEGAL_REQUEST;
    tdata->sense.code = SENCODE_INVALID_CDB_FIELD;
    return -EINVAL;
}

static int do_mode_select(struct usb_storage_threaddata *tdata)
{
    u8 *CB = tdata->cbw.CB;
    if (tdata->data_len < CB[4]) {
	printk(KERN_ERR STORAGE_MOD_NAME ": phase error on MODE_SELECT\n");
	tdata->csw.bStatus = CSW_STAT_PERR;
	return -EINVAL;
    }
    /* nothing changeable */
    tdata->csw.dDataResidue -= CB[4];
    return 0;
}

static int do_mode_sense(struct usb_storage_threaddata *tdata)
{
    u8 *CB = tdata->cbw.CB;
    u8 data[4 + 13];
    unsigned int len = 4 + 13;
    int page = CB[2] & 0x3f;

    if ((CB[2] & 0xc0) == 0x40)	/* PC=01b: changable values */
	goto invalid_cdb_field;
    if (page != 0x06 && page != 0x3f)	/* page 6 or all */
	goto invalid_cdb_field;
    /* mode parameter header (c.f. SPC-2 8.3, RBC 5.8.2) */
    data[0] = len - 1;
    data[1] = 0;
    data[2] = 0;
    data[3] = 0;
    /* parameter page 6 (c.f. RBC 5.8.3) */
    data[4] = 0x80 | 0x06;
    data[5] = 13 - 2;
    data[6] = 0;
    put_unaligned(cpu_to_be16(STORAGE_BLOCK_SIZE), (u16 *)&data[7]);
    data[9] = 0;
    put_unaligned(cpu_to_be32(tdata->num_blocks), (u32 *)&data[10]);
    data[14] = 0;
    data[15] = 0x03;	/* FORMATD,LOCKD */
    data[16] = 0;
    add_in_data(tdata, data, min(len, (unsigned int)CB[4]));
    return 0;

  invalid_cdb_field:
    printk(KERN_WARNING STORAGE_MOD_NAME ": Invalid CDB field\n");
    tdata->sense.key = ILLEGAL_REQUEST;
    tdata->sense.code = SENCODE_INVALID_CDB_FIELD;
    return -EINVAL;
}

static int do_read(struct usb_storage_threaddata *tdata)
{
    u8 *CB = tdata->cbw.CB;
    unsigned int lba = be32_to_cpu(get_unaligned((u32 *)&CB[2]));
    unsigned int llen = be16_to_cpu(get_unaligned((u16 *)&CB[7]));
    unsigned int len = llen * STORAGE_BLOCK_SIZE;
    loff_t  tmp_offset;
    int rlen;
    mm_segment_t fs;

    if (tdata->cbw.dDataTransferLength < len)
	goto phase_error;
    if (lba + llen > tdata->num_blocks)
	goto lba_out_of_range;
    if (  ( tmp_offset =  tdata->real_fd->f_op->llseek(tdata->real_fd, (off_t)lba * STORAGE_BLOCK_SIZE,
	      0 /* SEEK_SET */) )   == (off_t)-1) {
	printk (KERN_ERR STORAGE_MOD_NAME ": %s: lseek error on LBA %u \n",
	       tdata->filename, lba);
	goto medium_error;
    }
    fs = get_fs(); set_fs( KERNEL_DS );
    rlen=tdata->real_fd->f_op->read(tdata->real_fd, tdata->data_buf, len, &tmp_offset );
    set_fs( fs );
    if (rlen != len) {
	printk (KERN_ERR STORAGE_MOD_NAME ": %s: read error on LBA %u (read %d) \n",
	       tdata->filename, lba, rlen);
	goto medium_error;
    }
    tdata->data_len = len;
    tdata->stat.read_blocks += llen;
    return 0;

  phase_error:
    printk(KERN_ERR STORAGE_MOD_NAME ": phase error on READ\n");
    tdata->csw.bStatus = CSW_STAT_PERR;
    return -EINVAL;
  lba_out_of_range:
    printk(KERN_WARNING STORAGE_MOD_NAME ": LBA out of range\n");
    tdata->sense.key = ILLEGAL_REQUEST;
    tdata->sense.code = SENCODE_LBA_OUT_OF_RANGE;
    return -EINVAL;
  medium_error:
    printk(KERN_WARNING STORAGE_MOD_NAME ": Medium error\n");
    tdata->sense.key = MEDIUM_ERROR;
    return -EINVAL;
}

static int do_readcapacity(struct usb_storage_threaddata *tdata)
{
    u32 data[2];
    data[0] = cpu_to_be32(tdata->num_blocks - 1);
    data[1] = cpu_to_be32(STORAGE_BLOCK_SIZE);
    add_in_data(tdata, &data, sizeof(data));
    return 0;
}

static int do_request_sense(struct usb_storage_threaddata *tdata)
{
    u8 *CB = tdata->cbw.CB;
    u8 data[13];
    unsigned int len = sizeof(data);
    data[0] = 0x70;	/* current errors */
    data[1] = 0;
    data[2] = tdata->sense.key;
    put_unaligned(cpu_to_be32(tdata->sense.info), (u32 *)&data[3]);
    data[7] = len - 7;;
    put_unaligned(cpu_to_be32(tdata->sense.cmdinfo), (u32 *)&data[8]);
    data[12] = tdata->sense.code;
    add_in_data(tdata, data, min(len, (unsigned int)CB[4]));
    return 0;
}

static int do_write(struct usb_storage_threaddata *tdata)
{
    u8 *CB = tdata->cbw.CB;
    unsigned int lba = be32_to_cpu(get_unaligned((u32 *)&CB[2]));
    unsigned int llen = be16_to_cpu(get_unaligned((u16 *)&CB[7]));
    unsigned int len = llen * STORAGE_BLOCK_SIZE;
    loff_t  tmp_offset;
    int rlen;
    mm_segment_t fs;

    if (tdata->data_len < len)
	goto phase_error;
    if (lba + llen > tdata->num_blocks)
	goto lba_out_of_range;
    if (  ( tmp_offset = tdata->real_fd->f_op->llseek(tdata->real_fd, (off_t)lba * STORAGE_BLOCK_SIZE,
	      0 /* SEEK_SET */) ) == (off_t)-1) {
	printk (KERN_ERR STORAGE_MOD_NAME ": %s: lseek error on LBA %u (errno %d)\n",
	       tdata->filename, lba, errno);
	goto medium_error;
    }
    fs = get_fs(); set_fs( KERNEL_DS );
    rlen=tdata->real_fd->f_op->write(tdata->real_fd, tdata->data_buf, len, &tmp_offset);
    set_fs( fs );
    if (rlen != len) {
	printk (KERN_ERR STORAGE_MOD_NAME ": %s: write error on LBA %u (errno %d)\n",
	       tdata->filename, lba, errno);
	goto medium_error;
    }
    tdata->csw.dDataResidue -= len;
    tdata->stat.write_blocks += llen;
    return 0;

  phase_error:
    printk(KERN_ERR STORAGE_MOD_NAME ": phase error on WRITE\n");
    tdata->csw.bStatus = CSW_STAT_PERR;
    return -EINVAL;
  lba_out_of_range:
    printk(KERN_WARNING STORAGE_MOD_NAME ": LBA out of range\n");
    tdata->sense.key = ILLEGAL_REQUEST;
    tdata->sense.code = SENCODE_LBA_OUT_OF_RANGE;
    return -EINVAL;
  medium_error:
    printk(KERN_WARNING STORAGE_MOD_NAME ": Medium error\n");
    tdata->sense.key = MEDIUM_ERROR;
    return -EINVAL;
}

/* process RBC Command Block */
static int storage_process_CB(struct usb_storage_threaddata *tdata)
{
    u8 *CB = tdata->cbw.CB;

    if (CB[0] != REQUEST_SENSE) {
	/* initialize sense data */
	tdata->sense.key = NO_SENSE;
	tdata->sense.info = 0;
	tdata->sense.code = SENCODE_NO_SENSE;
	tdata->sense.cmdinfo = 0;
    }

    switch (CB[0]) {
    case FORMAT_UNIT:
	dbg_rx(2, "FORMAT_UNIT");
	goto unsupported;
    case INQUIRY:
	dbg_rx(2, "INQUIRY");
	tdata->stat.inquiry++;
	if (tdata->data_len)
	    goto phase_error;
	return do_inquiry(tdata);
    case MODE_SELECT:
	dbg_rx(2, "MODE_SELECT");
	tdata->stat.mode_select++;
	return do_mode_select(tdata);
    case MODE_SENSE:
	dbg_rx(2, "MODE_SENSE");
	tdata->stat.mode_sense++;
	if (tdata->data_len)
	    goto phase_error;
	return do_mode_sense(tdata);
    case PERSISTENT_RESERVE_IN:
	dbg_rx(2, "PERSISTENT_RESERVE_IN");
	goto unsupported;
    case PERSISTENT_RESERVE_OUT:
	dbg_rx(2, "PERSISTENT_RESERVE_OUT");
	goto unsupported;
    case ALLOW_MEDIUM_REMOVAL:
	dbg_rx(2, "ALLOW_MEDIUM_REMOVAL");
	goto unsupported;
    case READ_10:
	dbg_rx(2, "READ_10");
	tdata->stat.read_10++;
	if (tdata->data_len)
	    goto phase_error;
	return do_read(tdata);
    case READ_CAPACITY:
	dbg_rx(2, "READ_CAPACITY");
	tdata->stat.read_capacity++;
	if (tdata->data_len)
	    goto phase_error;
	return do_readcapacity(tdata);
    case RELEASE:
	dbg_rx(2, "RELEASE");
	goto unsupported;
    case REQUEST_SENSE:
	dbg_rx(2, "REQUEST_SENSE");
	tdata->stat.request_sense++;
	if (tdata->data_len)
	    goto phase_error;
	return do_request_sense(tdata);
    case RESERVE:
	dbg_rx(2, "RESERVE");
	goto unsupported;
    case START_STOP:
	dbg_rx(2, "START_STOP");
	tdata->stat.start_stop++;
	if (tdata->data_len)
	    goto phase_error;
	/* do nothing */
	break;
    case SYNCHRONIZE_CACHE:
	dbg_rx(2, "SYNCHRONIZE_CACHE");
	goto unsupported;
    case TEST_UNIT_READY:
	dbg_rx(2, "TEST_UNIT_READY");
	tdata->stat.test_unit_ready++;
	if (tdata->data_len)
	    goto phase_error;
	/* do nothing */
	break;
    case VERIFY:
	dbg_rx(2, "VERIFY");
	tdata->stat.verify++;
	if (tdata->data_len)
	    goto phase_error;
	/* do nothing */
	break;
    case WRITE_10:
	dbg_rx(2, "WRITE_10");
	tdata->stat.write_10++;
	return do_write(tdata);
    case WRITE_BUFFER:
	dbg_rx(2, "WRITE_BUFFER");
	tdata->stat.write_buffer++;
	if (tdata->data_len)
	    goto phase_error;
	/* do nothing */
	break;
    default:
	printk(KERN_ERR STORAGE_MOD_NAME ": unknown RBC command (%02x)\n",
	       CB[0]);
	goto unsupported;
    }

    return 0;

  phase_error:
    printk(KERN_ERR STORAGE_MOD_NAME ": phase error on RBC command %02x\n", CB[0]);
    tdata->csw.bStatus = CSW_STAT_PERR;
    return -EINVAL;
  unsupported:
    tdata->stat.unsupported++;
    tdata->sense.key = ILLEGAL_REQUEST;
    tdata->sense.code = SENCODE_INVALID_COMMAND;
    return -EINVAL;
}

static int storage_thread(void *data) 
{
    struct usb_storage_private *private;
    off_t off;

    daemonize ();
    reparent_to_init();
    spin_lock_irq(¤t->sigmask_lock);
    sigemptyset(¤t->blocked);
    recalc_sigpending(current);
    spin_unlock_irq(¤t->sigmask_lock);

    strcpy (current->comm, STORAGE_MOD_NAME);

    private = &storage_private;
    memset(private, 0, sizeof(*private));
    if (!filename)
	filename = STORAGE_DEFAULT_FILENAME;
    strcpy(private->tdata.filename, filename);
    private->tdata.real_fd = filp_open (private->tdata.filename, O_RDWR, 0);
    if (IS_ERR(private->tdata.real_fd)) {
	printk(KERN_ERR STORAGE_MOD_NAME ": %s open failed (errno %d)\n",
	       private->tdata.filename, errno);
	storage_thread_terminating = 1;
	goto end;
    }
    if ((off = private->tdata.real_fd->f_op->llseek(private->tdata.real_fd, 0, 2 /* SEEK_END*/)) == (off_t)-1) {
	printk (KERN_ERR STORAGE_MOD_NAME ": %s lseek failed (errno %d)\n",
	       private->tdata.filename, errno);
	filp_close(private->tdata.real_fd,0);
	private->tdata.real_fd = NULL;
	storage_thread_terminating = 1;
	goto end;
    }
    private->tdata.num_blocks = off / STORAGE_BLOCK_SIZE;
    private->devstate = STATE_INVALID;
    printk(KERN_INFO STORAGE_MOD_NAME ": storage filename %s, %d blocks\n",
	   private->tdata.filename, private->tdata.num_blocks);

    // let startup continue
    up(&storage_sem_start);

    // process loop
    for (storage_thread_terminating = 0; !storage_thread_terminating;) {
        // wait for someone to tell us to do something
        down(&storage_sem_work);

	spin_lock_bh(&storage_lock);	/* prevent event handler */
	if (private->device && private->tdata.busy) {
	    spin_unlock_bh(&storage_lock);
	    /* this function CAN sleep */
	    if (storage_process_CB(&private->tdata) < 0) {
		if (private->tdata.csw.bStatus == CSW_STAT_GOOD)
		    private->tdata.csw.bStatus = CSW_STAT_FAILED;
	    }
	    spin_lock_bh(&storage_lock);
	    private->tdata.busy = 0;
	    /* kick event routine */
	    usbd_device_event(private->device, DEVICE_FUNCTION_PRIVATE, 1);
	}
	spin_unlock_bh(&storage_lock);
    }

    /* shutdown */
    if (private->tdata.data_buf)
	kfree(private->tdata.data_buf);
    filp_close(private->tdata.real_fd,0);

    // let the process stopping us know we are done and return
 end:
    up(&storage_sem_start);
    complete_and_exit (NULL, 0);
    return 0;
}

/**
 * storage_thread_kickoff - start command processing thread
 */
static int storage_thread_kickoff(void)
{
    storage_thread_terminating = 0;
    kernel_thread(&storage_thread, NULL, CLONE_FS | CLONE_FILES);
    down(&storage_sem_start);
    if (storage_thread_terminating)
	return -1;
    return 0;
}

/**
 * storage_thread_killoff - stop command processing thread
 */
static void storage_thread_killoff(void)
{
    if (!storage_thread_terminating) {
        storage_thread_terminating = 1;
        up(&storage_sem_work);
        down(&storage_sem_start);
    }
}

#define STORAGE_SERIAL_CHAR_MIN	12
static int __init storage_check_serial(const char *s)
{
    // c.f. USB Mass Storage Class Bulk-Only Transport 4.1.1 Serial Number
    if (strlen(s) < STORAGE_SERIAL_CHAR_MIN)
	return 0;
    while (*s) {
	// '0'-'9', 'A'-'F'
	if (!isxdigit(*s) || islower(*s))
	    return 0;
	s++;
    }
    return 1;
}

/*
 * storage_modinit - module init
 *
 */
static int __init storage_modinit(void)
{
    static char serial_str[STORAGE_SERIAL_CHAR_MIN + 1];
    int i, len;

    printk(KERN_INFO "%s (dbg=\"%s\")\n", __usbd_module_info, dbg?dbg:"");

    if (vendor_id) {
        storage_device_description.idVendor = vendor_id;
    }
    if (product_id) {
        storage_device_description.idProduct = product_id;
    }
    if (!serial_number)
	serial_number = storage_device_description.iSerialNumber;
    len = strlen(serial_number);
    while (len++ < STORAGE_SERIAL_CHAR_MIN)
	strcat(serial_str, "0");
    strcat(serial_str, serial_number);
    for (i = 0; serial_str[i]; i++) {
	if (islower(serial_str[i]))
	    serial_str[i] = toupper(serial_str[i]);
    }
    storage_device_description.iSerialNumber = serial_str;

    printk(KERN_INFO "vendor_id: %04x product_id: %04x, serial_number: %s\n",
	   storage_device_description.idVendor,
	   storage_device_description.idProduct,
	   storage_device_description.iSerialNumber);

    if (0 != scan_debug_options(STORAGE_MOD_NAME, dbg_table,dbg)) {
        return(-EINVAL);
    }

    if (!storage_check_serial(storage_device_description.iSerialNumber)) {
	printk(KERN_ERR "bad serial number (%s)\n",
	       storage_device_description.iSerialNumber);
	return -EINVAL;
    }

    spin_lock_init(&storage_lock);
    create_proc_read_entry(STORAGE_PROC_NAME, 0, NULL, storage_read_proc, 0);

    if (storage_thread_kickoff()) {
	return -ENODEV;
    }

    // register us with the usb device support layer
    //
    if (usbd_register_function(&function_driver)) {
        return -EINVAL;
    }

    // return
    return 0;
}


/* storage_modexit - module cleanup
 */
static void __exit storage_modexit(void)
{
    // de-register us with the usb device support layer
    // 
    usbd_deregister_function(&function_driver);
    remove_proc_entry(STORAGE_PROC_NAME, NULL);
    storage_thread_killoff();
}

module_init(storage_modinit);
module_exit(storage_modexit);

/*
 * Local variables:
 * c-basic-offset: 4
 * End:
 */