From bfb879efc0a6e96a7b7fe44d7904ced3895e1e8d Mon Sep 17 00:00:00 2001 From: patacongo Date: Wed, 15 Dec 2010 22:28:35 +0000 Subject: [PATCH] Extend USB host mass storage class git-svn-id: svn://svn.code.sf.net/p/nuttx/code/trunk@3183 42af7a65-404d-4744-a932-0658087f49c3 --- drivers/usbhost/usbhost_storage.c | 455 +++++++++++++++++++++++++++--- include/nuttx/usb/usbhost.h | 16 +- 2 files changed, 423 insertions(+), 48 deletions(-) diff --git a/drivers/usbhost/usbhost_storage.c b/drivers/usbhost/usbhost_storage.c index dd3d44a9cb..618333b2f3 100644 --- a/drivers/usbhost/usbhost_storage.c +++ b/drivers/usbhost/usbhost_storage.c @@ -45,6 +45,7 @@ #include #include +#include #include #include @@ -54,6 +55,10 @@ /* Configuration ************************************************************/ +#ifndef CONFIG_SCHED_WORKQUEUE +# error "Worker thread support is required (CONFIG_SCHED_WORKQUEUE)" +#endif + /* If the create() method is called by the USB host device driver from an * interrupt handler, then it will be unable to call malloc() in order to * allocate a new class instance. If the create() method is called from the @@ -64,11 +69,22 @@ # define CONFIG_USBHOST_NPREALLOC 0 #endif +#if CONFIG_USBHOST_NPREALLOC > 26 +# error "Currently limited to 26 devices /dev/sda-z" +#endif + +/* This format is used to construct the /dev/sd[n] device driver path. It + * defined here so that it will be used consistently in all places. + */ + +#define DEV_FORMAT "/dev/sd%c" +#define DEV_NAMELEN 10 + /**************************************************************************** * Private Types ****************************************************************************/ -/* This structur contains the internal, private state of the USB host mass +/* This structure contains the internal, private state of the USB host mass * storage class. */ @@ -84,18 +100,40 @@ struct usbhost_state_s /* The remainder of the fields are provide o the mass storage class */ - int crefs; /* Reference count on the driver instance */ + int16_t crefs; /* Reference count on the driver instance */ + char sdchar; /* Character identifying the /dev/sd[n] device */ uint16_t blocksize; /* Block size of USB mass storage device */ uint32_t nblocks; /* Number of blocks on the USB mass storage device */ + struct work_s work; /* For interacting with the worker thread */ + struct usbhost_epdesc_s bulkin; /* Bulk IN endpoint */ + struct usbhost_epdesc_s bulkout; /* Bulk OUT endpoint */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ +/* Memory allocation services */ + +static inline struct usbhost_state_s *usbhost_allocclass(void); +static inline usbhost_freeclass(struct usbhost_state_s *class); + +/* Device name management */ + +static int usbhost_allocdevno(FAR struct usbhost_state_s *priv); +static void usbhost_freedevno(FAR struct usbhost_state_s *priv); +static inline void usbhost_mkdevname(FAR struct usbhost_state_s *priv, char *devname); + +/* Worker thread actions */ + +static void usbhost_destroy(FAR void *arg); + +/* Data helpers */ + +static inline uint16_t usbhost_getint16(const uint8_t *val); + /* struct usbhost_registry_s methods */ -static inline struct usbhost_state_s *usbhost_allocclass(void); static struct usbhost_class_s *usbhost_create(FAR struct usbhost_driver_s *drvr, FAR const struct usbhost_id_s *id); @@ -116,10 +154,10 @@ static ssize_t usbhost_write(FAR struct inode *inode, FAR const unsigned char *buffer, size_t startsector, unsigned int nsectors); #endif -static int usbhost_geometry(FAR struct inode *inode, - FAR struct geometry *geometry); -static int usbhost_ioctl(FAR struct inode *inode, int cmd, - unsigned long arg); +static int usbhost_geometry(FAR struct inode *inode, + FAR struct geometry *geometry); +static int usbhost_ioctl(FAR struct inode *inode, int cmd, + unsigned long arg); /**************************************************************************** * Private Data @@ -179,19 +217,19 @@ static struct usbhost_state_s g_prealloc[CONFIG_USBHOST_NPREALLOC]; static struct usbhost_state_s *g_freelist; #endif -/**************************************************************************** - * Private Functions - ****************************************************************************/ +/* This is a bitmap that is used to allocate device names /dev/sda-z. */ + +static uint32_t g_devinuse; /**************************************************************************** - * struct usbhost_registry_s methods + * Private Functions ****************************************************************************/ /**************************************************************************** * Name: usbhost_allocclass * * Description: - * This is really part of the logic that implementes the create() method + * This is really part of the logic that implements the create() method * of struct usbhost_registry_s. This function allocates memory for one * new class instance. * @@ -206,32 +244,186 @@ static struct usbhost_state_s *g_freelist; * ****************************************************************************/ +#if CONFIG_USBHOST_NPREALLOC > 0 static inline struct usbhost_state_s *usbhost_allocclass(void) { struct usbhost_state_s *priv; + irqstate_t flags; -#if CONFIG_USBHOST_NPREALLOC > 0 - /* We are executing from an interrupt handler so we need to take one of our - * pre-allocated class instances from the free list. No special protection - * is needed if we are in an interrupt handler. + /* We may be executing from an interrupt handler so we need to take one of + * our pre-allocated class instances from the free list. */ + flags = irqsave(); priv = g_freelist; if (priv) { g_freelist = priv->class.flink; priv->class.flink = NULL; } + irqrestore(flags); + return priv; +} #else +static inline struct usbhost_state_s *usbhost_allocclass(void) +{ /* We are not executing from an interrupt handler so we can just call * malloc() to get memory for the class instance. */ DEBUGASSERT(!up_interrupt_context()); - priv = (struct usbhost_state_s *)malloc(sizeof(struct usbhost_state_s)); -#endif - return priv; + return (struct usbhost_state_s *)malloc(sizeof(struct usbhost_state_s)); } +#endif + +/**************************************************************************** + * Name: usbhost_freeclass + * + * Description: + * Free a class instance previously allocated by usbhost_allocclass(). + * + * Input Parameters: + * class - A reference to the class instance to be freed. + * + * Returned Values: + * None + * + ****************************************************************************/ + +#if CONFIG_USBHOST_NPREALLOC > 0 +static inline usbhost_freeclass(struct usbhost_state_s *class) +{ + irqstate_t flags; + DEBUGASSERT(class != NULL); + + /* Just put the pre-allocated class structure back on the freelist */ + + flags = irqsave(); + class->class.flink = g_freelist; + g_freelist = class; + irqrestore(flags); +} +#else +static inline usbhost_freeclass(struct usbhost_state_s *class) +{ + DEBUGASSERT(class != NULL); + + /* Free the class instance (calling sched_free() in case we are executing + * from an interrupt handler. + */ + + free(class); +} +#endif + +/**************************************************************************** + * Name: Device name management + * + * Description: + * Some tiny functions to coordinate management of mass storage device names. + * + ****************************************************************************/ + +static int usbhost_allocdevno(FAR struct usbhost_state_s *priv) +{ + irqstate_t flags; + int devno; + + flags = irqsave(); + for (devno = 0; devno < 26; devno++) + { + uint32_t bitno = 1 << devno; + if ((g_devinuse & bitno) == 0) + { + g_devinuse |= bitno; + priv->sdchar = 'a' + devno; + irqrestore(flags); + return OK; + } + } + + irqrestore(flags); + return -EMFILE; +} + +static void usbhost_freedevno(FAR struct usbhost_state_s *priv) +{ + int devno = 'a' - priv->sdchar; + + if (devno >= 0 && devno < 26) + { + irqstate_t flags = irqsave(); + g_devinuse &= ~(1 << devno); + irqrestore(flags); + } +} + +static inline void usbhost_mkdevname(FAR struct usbhost_state_s *priv, char *devname); +{ + (void)snprintf(devname, DEV_NAMELEN, DEV_FORMAT, priv->sdchar); +} + +/**************************************************************************** + * Name: usbhost_destroy + * + * Description: + * The USB mass storage device has been disconnected and the refernce count + * on the USB host class instance has gone to 1.. Time to destroy the USB + * host class instance. + * + * Input Parameters: + * arg - A reference to the class instance to be freed. + * + * Returned Values: + * None + * + ****************************************************************************/ + +static void usbhost_destroy(FAR void *arg) +{ + FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)arg; + char devname[DEV_NAMELEN]; + + DEBUGASSERT(priv != NULL); + + /* Unregister the block driver */ + + usbhost_mkdevname(priv, devname); + (void)unregister_blockdriver(devname); + + /* Release the device name used by this connection */ + + usbhost_freedevno(priv); + + /* And free the class instance. Hmmm.. this may execute on the worker + * thread and the work structure is part of what is getting freed. + */ + + usbhost_freeclass(priv); +} + +/**************************************************************************** + * Name: usbhost_getint16 + * + * Description: + * Get a (possibly unaligned) 16-bit little endian value. + * + * Input Parameters: + * val - A pointer to the first byte of the little endian value. + * + * Returned Values: + * A uin16_t representing the whole 16-bit integer value + * + ****************************************************************************/ + +static inline uint16_t usbhost_getint16(const uint8_t *val) +{ + return (uint16_t)val[1] << 8 + (uint16_t)val[0]; +} + +/**************************************************************************** + * struct usbhost_registry_s methods + ****************************************************************************/ /**************************************************************************** * Name: usbhost_create @@ -273,23 +465,33 @@ static FAR struct usbhost_class_s *usbhost_create(FAR struct usbhost_driver_s *d /* Initialize the allocated storage class instance */ memset(priv, 0, sizeof(struct usbhost_state_s); - priv->class.configdesc = usbhost_configdesc; - priv->class.disconnected = usbhost_disconnected; - priv->crefs = 1; - /* Bind the driver to the storage class instance */ + /* Assign a device number to this class instance */ - priv->drvr = drvr; + if (usbhost_allocdevno(priv) == OK) + { + priv->class.configdesc = usbhost_configdesc; + priv->class.disconnected = usbhost_disconnected; + priv->crefs = 1; - /* NOTE: We do not yet know the geometry of the USB mass storage device */ - - /* Return the instance of the USB mass storage class */ + /* Bind the driver to the storage class instance */ + + priv->drvr = drvr; + + /* NOTE: We do not yet know the geometry of the USB mass storage device */ - return &priv->class; + /* Return the instance of the USB mass storage class */ + + return &priv->class; + } } - /* Return NULL on all failures */ + /* An error occurred. Free the allocation and return NULL on all failures */ + if (priv) + { + usbhost_free(priv); + } return NULL; } @@ -316,11 +518,142 @@ static FAR struct usbhost_class_s *usbhost_create(FAR struct usbhost_driver_s *d * ****************************************************************************/ -static int usbhost_configdesc(struct usbhost_class_s *class, - const uint8_t *configdesc, int desclen) +static int usbhost_configdesc(FAR struct usbhost_class_s *class, + FAR const uint8_t *configdesc, int desclen) { -#warning "Missing Implementation" - return -ENOSYS; + FAR struct usb_cfgdesc_s *cfgdesc; + FAR struct usb_desc_s *desc; + int remaining; + bool iffound = false; + bool boutfound = false; + bool binfound = false; + + /* Verify that we were passed a configuration descriptor */ + + cfgdesc = (FAR struct usb_cfgdesc_s *)configdesc; + if (desc->type != USB_DESC_TYPE_CONFIG) + { + return -EINVAL; + } + + /* Get the total length of the configuration descriptor (little endian). + * It might be a good check to get the number of interfaces here too. + */ + + remaining = (int)usbhost_getint16(cfgdesc->totallen); + + /* Skip to the next entry descriptor */ + + configdesc += desc->len; + remaining -= desc->len; + + /* Loop where there are more dscriptors to examine */ + + while (remaining >= sizeof(struct usb_desc_s)) + { + /* What is the next descriptor? */ + + desc = (FAR struct usb_desc_s *)configdesc; + switch (desc->type) + { + /* Interface descriptor. We really should get the number of endpoints + * from this descriptor too. + */ + + case USB_DESC_TYPE_INTERFACE: + { + DEBUGASSERT(remaining >= sizeof(struct usb_ifdesc_s)); + if (iffound) + { + /* Oops.. more than one interface. We don't know what to do with this. */ + + return -ENOSYS; + } + iffound = true; + } + break; + + /* Endpoint descriptor. We expect two bulk endpoints, an IN and an OUT */ + case USB_DESC_TYPE_ENDPOINT: + { + FAR struct usb_epdesc_s *epdesc = (FAR struct usb_epdesc_s *)configdesc; + DEBUGASSERT(remaining >= sizeof(struct usb_epdesc_s)); + + /* Check for a bulk endpoint. We only support the bulk-only + * protocol so I suppose anything else should really be an error. + */ + + if ((epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) == USB_EP_ATTR_XFER_BULK) + { + /* Yes.. it is a bulk endpoint. IN or OUT? */ + + if (USB_ISEPOUT(epdesc->addr)) + { + /* It is an OUT bulk endpoint. There should be only one bulk OUT endpoint. */ + + if (boutfound) + { + /* Oops.. more than one interface. We don't know what to do with this. */ + + return -EINVAL; + } + boutfound = true; + + /* Save the bulk OUT endpoint information */ + + priv->bulkout.addr = epdesc->addr & USB_EP_ADDR_NUMBER_MASK; + priv->bulkout.in = false; + priv->bulkout.mxpacketsize = usbhost_getint16(pdesc->mxpacketsize); + } + else + { + /* It is an IN bulk endpoint. There should be only one bulk IN endpoint. */ + + if (binfound) + { + /* Oops.. more than one interface. We don't know what to do with this. */ + + return -EINVAL; + } + binfound = true; + + /* Save the bulk IN endpoint information */ + + priv->bulkin.addr = epdesc->addr & USB_EP_ADDR_NUMBER_MASK; + priv->bulkin.in = true; + priv->bulkin.mxpacketsize = usbhost_getint16(pdesc->mxpacketsize); + } + } + } + break; + + /* Other descriptors are just ignored for now */ + + default: + break; + } + + /* Increment the address of the next descriptor */ + + configdesc += desc->len; + remaining -= desc->len; + } + + /* Sanity checking... did we find all of things that we need? Hmmm.. I wonder.. + * can we work read-only or write-only if only one bulk endpoint found? + */ + + if (!iffound || !binfound || !boutfound) + { + ulldbg("ERROR: Found IF:%s BIN:%s BOUT:%s\n", + iffound ? "YES" : "NO", + binfound ? "YES" : "NO", + outfound ? "YES" : "NO"); + return -EINVAL; + } + + ullvdbg("Mass Storage device connected\n"); + return OK; } /**************************************************************************** @@ -345,12 +678,15 @@ static int usbhost_configdesc(struct usbhost_class_s *class, static int usbhost_disconnected(struct usbhost_class_s *class) { FAR struct usbhost_state_s *priv = (FAR struct usbhost_state_s *)class; + irqstate_t flags; + DEBUGASSERT(priv != NULL); /* Nullify the driver instance. This will be our indication to any users * of the mass storage device that the device is no longer available. */ + flags = irqsave(); priv->drvr = NULL; /* Now check the number of references on the class instance. If it is one, @@ -359,12 +695,25 @@ static int usbhost_disconnected(struct usbhost_class_s *class) * block driver. */ -#warning "Missing Implementation" + if (priv->crefs == 1) + { + /* Destroy the class instance */ + /* Are we in an interrupt handler? */ - /* Unregister the block device */ + if (up_interrupt_context()) + { + /* Yes.. do the destruction on the worker thread */ -#warning "Missing Implementation" - return -ENOSYS; + (void)work_queue(&priv->work, usbhost_destroy, priv, 0); + } + else + { + /* No.. destroy the class now */ + + usbhost_destroy(priv); + } + irqrestore(flags); + return OK; } /**************************************************************************** @@ -391,7 +740,8 @@ static int usbhost_open(FAR struct inode *inode) if (!priv->drvr) { /* No... the block driver is no longer bound to the class. That means that - * the USB storage device is no longer connected. + * the USB storage device is no longer connected. Refuse any further + * attempts to open the driver. */ ret = -ENODEV; @@ -431,14 +781,25 @@ static int usbhost_close(FAR struct inode *inode) usbhost_takesem(priv); priv->crefs--; + /* Release the semaphore. The following operations when crefs == 1 are + * safe because we know that there is no outstanding open references to + * the block driver. + */ + + usbhost_givesem(priv); + /* Check if the USB mass storage device is still connected. If the * storage device is not connected and the reference count just * decremented to one, then unregister the block driver and free * the class instance. */ -#warning "Missing Implementation" - - usbhost_givesem(priv); + + if (priv->crefs <= 1 && priv->drvr == NULL) + { + /* Destroy the class instance */ + + usbhost_destroy(priv); + } return OK; } @@ -467,7 +828,8 @@ static ssize_t usbhost_read(FAR struct inode *inode, unsigned char *buffer, if (!priv->drvr) { /* No... the block driver is no longer bound to the class. That means that - * the USB storage device is no longer connected. + * the USB storage device is no longer connected. Refuse any attempt to read + * from the device. */ ret = -ENODEV; @@ -509,7 +871,8 @@ static ssize_t usbhost_write(FAR struct inode *inode, const unsigned char *buffe if (!priv->drvr) { /* No... the block driver is no longer bound to the class. That means that - * the USB storage device is no longer connected. + * the USB storage device is no longer connected. Refuse any attempt to + * write to the device. */ ret = -ENODEV; @@ -547,7 +910,8 @@ static int usbhost_geometry(FAR struct inode *inode, struct geometry *geometry) if (!priv->drvr) { /* No... the block driver is no longer bound to the class. That means that - * the USB storage device is no longer connected. + * the USB storage device is no longer connected. Refuse to return any + * geometry info. */ ret = -ENODEV; @@ -600,7 +964,8 @@ static int usbhost_ioctl(FAR struct inode *inode, int cmd, unsigned long arg) if (!priv->drvr) { /* No... the block driver is no longer bound to the class. That means that - * the USB storage device is no longer connected. + * the USB storage device is no longer connected. Refuse to process any + * ioctl commands. */ ret = -ENODEV; diff --git a/include/nuttx/usb/usbhost.h b/include/nuttx/usb/usbhost.h index b4d51e2580..a3a356df5d 100644 --- a/include/nuttx/usb/usbhost.h +++ b/include/nuttx/usb/usbhost.h @@ -200,19 +200,29 @@ struct usbhost_class_s * class implementation. */ +struct usbhost_epdesc_s; struct usbhost_driver_s { /* Receive a process a transfer descriptor */ - int (*transfer)(); + int (*transfer)(FAR struct usbhost_epdesc_s *ed); /* Enumerate the connected device */ - int (*enumerate)(); + int (*enumerate)(FAR struct usbhost_epdesc_s *ed); /* Receive control information */ - int (*rcvctrl)(); + int (*rcvctrl)(FAR struct usbhost_epdesc_s *ed); +}; + +/* This structure describes one endpoint */ + +struct usbhost_epdesc_s +{ + uint8_t addr; /* Endpoint address */ + bool in; /* Direction: TRUE = IN */ + uint16_t mxpacketsize; /* Max packetsize */ }; /************************************************************************************