diff options
author | Chris Kelly <ckelly@ozmodevices.com> | 2012-02-20 21:11:53 +0000 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-02-24 09:26:51 -0800 |
commit | ae926051d7eb8f80dba9513db70d2e2fc8385d3a (patch) | |
tree | b3ff68744d42b77076aaa8333ddf0e0a76d659fe /drivers/staging/ozwpan | |
parent | bc3157dde35ac3b2ec12229edcfaca58cd7925c0 (diff) |
staging: ozwpan: Added USB HCD implementation
Added the implementation of the virtual USB HCD that is used to
present devices connected via the network to the USB subsystem.
Signed-off-by: Chris Kelly <ckelly@ozmodevices.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/staging/ozwpan')
-rw-r--r-- | drivers/staging/ozwpan/ozhcd.c | 2257 | ||||
-rw-r--r-- | drivers/staging/ozwpan/ozhcd.h | 15 |
2 files changed, 2272 insertions, 0 deletions
diff --git a/drivers/staging/ozwpan/ozhcd.c b/drivers/staging/ozwpan/ozhcd.c new file mode 100644 index 000000000000..775dfe9c7b85 --- /dev/null +++ b/drivers/staging/ozwpan/ozhcd.c @@ -0,0 +1,2257 @@ +/* ----------------------------------------------------------------------------- + * Copyright (c) 2011 Ozmo Inc + * Released under the GNU General Public License Version 2 (GPLv2). + * + * This file provides the implementation of a USB host controller device that + * does not have any associated hardware. Instead the virtual device is + * connected to the WiFi network and emulates the operation of a USB hcd by + * receiving and sending network frames. + * Note: + * We take great pains to reduce the amount of code where interrupts need to be + * disabled and in this respect we are different from standard HCD's. In + * particular we don't want in_irq() code bleeding over to the protocol side of + * the driver. + * The troublesome functions are the urb enqueue and dequeue functions both of + * which can be called in_irq(). So for these functions we put the urbs into a + * queue and request a tasklet to process them. This means that a spinlock with + * interrupts disabled must be held for insertion and removal but most code is + * is in tasklet or soft irq context. The lock that protects this list is called + * the tasklet lock and serves the purpose of the 'HCD lock' which must be held + * when calling the following functions. + * usb_hcd_link_urb_to_ep() + * usb_hcd_unlink_urb_from_ep() + * usb_hcd_flush_endpoint() + * usb_hcd_check_unlink_urb() + * ----------------------------------------------------------------------------- + */ +#include <linux/platform_device.h> +#include <linux/usb.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/export.h> +#include "linux/usb/hcd.h" +#include <asm/unaligned.h> +#include "ozconfig.h" +#include "ozusbif.h" +#include "oztrace.h" +#include "ozalloc.h" +#include "ozurbparanoia.h" +#include "ozevent.h" +/*------------------------------------------------------------------------------ + * Number of units of buffering to capture for an isochronous IN endpoint before + * allowing data to be indicated up. + */ +#define OZ_IN_BUFFERING_UNITS 50 +/* Name of our platform device. + */ +#define OZ_PLAT_DEV_NAME "ozwpan" +/* Maximum number of free urb links that can be kept in the pool. + */ +#define OZ_MAX_LINK_POOL_SIZE 16 +/* Get endpoint object from the containing link. + */ +#define ep_from_link(__e) container_of((__e), struct oz_endpoint, link) +/*------------------------------------------------------------------------------ + * Used to link urbs together and also store some status information for each + * urb. + * A cache of these are kept in a pool to reduce number of calls to kmalloc. + */ +struct oz_urb_link { + struct list_head link; + struct urb *urb; + struct oz_port *port; + u8 req_id; + u8 ep_num; + unsigned long submit_jiffies; +}; + +/* Holds state information about a USB endpoint. + */ +struct oz_endpoint { + struct list_head urb_list; /* List of oz_urb_link items. */ + struct list_head link; /* For isoc ep, links in to isoc + lists of oz_port. */ + unsigned long last_jiffies; + int credit; + int credit_ceiling; + u8 ep_num; + u8 attrib; + u8 *buffer; + int buffer_size; + int in_ix; + int out_ix; + int buffered_units; + unsigned flags; + int start_frame; +}; +/* Bits in the flags field. */ +#define OZ_F_EP_BUFFERING 0x1 +#define OZ_F_EP_HAVE_STREAM 0x2 + +/* Holds state information about a USB interface. + */ +struct oz_interface { + unsigned ep_mask; + u8 alt; +}; + +/* Holds state information about an hcd port. + */ +#define OZ_NB_ENDPOINTS 16 +struct oz_port { + unsigned flags; + unsigned status; + void *hpd; + struct oz_hcd *ozhcd; + spinlock_t port_lock; + u8 bus_addr; + u8 next_req_id; + u8 config_num; + int num_iface; + struct oz_interface *iface; + struct oz_endpoint *out_ep[OZ_NB_ENDPOINTS]; + struct oz_endpoint *in_ep[OZ_NB_ENDPOINTS]; + struct list_head isoc_out_ep; + struct list_head isoc_in_ep; +}; +#define OZ_PORT_F_PRESENT 0x1 +#define OZ_PORT_F_CHANGED 0x2 +#define OZ_PORT_F_DYING 0x4 + +/* Data structure in the private context area of struct usb_hcd. + */ +#define OZ_NB_PORTS 8 +struct oz_hcd { + spinlock_t hcd_lock; + struct list_head urb_pending_list; + struct list_head urb_cancel_list; + struct list_head orphanage; + int conn_port; /* Port that is currently connecting, -1 if none.*/ + struct oz_port ports[OZ_NB_PORTS]; + uint flags; + struct usb_hcd *hcd; +}; +/* Bits in flags field. + */ +#define OZ_HDC_F_SUSPENDED 0x1 + +/*------------------------------------------------------------------------------ + * Static function prototypes. + */ +static int oz_hcd_start(struct usb_hcd *hcd); +static void oz_hcd_stop(struct usb_hcd *hcd); +static void oz_hcd_shutdown(struct usb_hcd *hcd); +static int oz_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags); +static int oz_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status); +static void oz_hcd_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep); +static void oz_hcd_endpoint_reset(struct usb_hcd *hcd, + struct usb_host_endpoint *ep); +static int oz_hcd_get_frame_number(struct usb_hcd *hcd); +static int oz_hcd_hub_status_data(struct usb_hcd *hcd, char *buf); +static int oz_hcd_hub_control(struct usb_hcd *hcd, u16 req_type, u16 wvalue, + u16 windex, char *buf, u16 wlength); +static int oz_hcd_bus_suspend(struct usb_hcd *hcd); +static int oz_hcd_bus_resume(struct usb_hcd *hcd); +static int oz_plat_probe(struct platform_device *dev); +static int oz_plat_remove(struct platform_device *dev); +static void oz_plat_shutdown(struct platform_device *dev); +static int oz_plat_suspend(struct platform_device *dev, pm_message_t msg); +static int oz_plat_resume(struct platform_device *dev); +static void oz_urb_process_tasklet(unsigned long unused); +static int oz_build_endpoints_for_config(struct usb_hcd *hcd, + struct oz_port *port, struct usb_host_config *config, + gfp_t mem_flags); +static void oz_clean_endpoints_for_config(struct usb_hcd *hcd, + struct oz_port *port); +static int oz_build_endpoints_for_interface(struct usb_hcd *hcd, + struct oz_port *port, + struct usb_host_interface *intf, gfp_t mem_flags); +static void oz_clean_endpoints_for_interface(struct usb_hcd *hcd, + struct oz_port *port, int if_ix); +static void oz_process_ep0_urb(struct oz_hcd *ozhcd, struct urb *urb, + gfp_t mem_flags); +static struct oz_urb_link *oz_remove_urb(struct oz_endpoint *ep, + struct urb *urb); +static void oz_hcd_clear_orphanage(struct oz_hcd *ozhcd, int status); +/*------------------------------------------------------------------------------ + * Static external variables. + */ +static struct platform_device *g_plat_dev; +static struct oz_hcd *g_ozhcd; +static DEFINE_SPINLOCK(g_hcdlock); /* Guards g_ozhcd. */ +static const char g_hcd_name[] = "Ozmo WPAN"; +static struct list_head *g_link_pool; +static int g_link_pool_size; +static DEFINE_SPINLOCK(g_link_lock); +static DEFINE_SPINLOCK(g_tasklet_lock); +static struct tasklet_struct g_urb_process_tasklet; +static struct tasklet_struct g_urb_cancel_tasklet; +static atomic_t g_pending_urbs = ATOMIC_INIT(0); +static const struct hc_driver g_oz_hc_drv = { + .description = g_hcd_name, + .product_desc = "Ozmo Devices WPAN", + .hcd_priv_size = sizeof(struct oz_hcd), + .flags = HCD_USB11, + .start = oz_hcd_start, + .stop = oz_hcd_stop, + .shutdown = oz_hcd_shutdown, + .urb_enqueue = oz_hcd_urb_enqueue, + .urb_dequeue = oz_hcd_urb_dequeue, + .endpoint_disable = oz_hcd_endpoint_disable, + .endpoint_reset = oz_hcd_endpoint_reset, + .get_frame_number = oz_hcd_get_frame_number, + .hub_status_data = oz_hcd_hub_status_data, + .hub_control = oz_hcd_hub_control, + .bus_suspend = oz_hcd_bus_suspend, + .bus_resume = oz_hcd_bus_resume, +}; + +static struct platform_driver g_oz_plat_drv = { + .probe = oz_plat_probe, + .remove = oz_plat_remove, + .shutdown = oz_plat_shutdown, + .suspend = oz_plat_suspend, + .resume = oz_plat_resume, + .driver = { + .name = OZ_PLAT_DEV_NAME, + .owner = THIS_MODULE, + }, +}; +/*------------------------------------------------------------------------------ + * Gets our private context area (which is of type struct oz_hcd) from the + * usb_hcd structure. + * Context: any + */ +static inline struct oz_hcd *oz_hcd_private(struct usb_hcd *hcd) +{ + return (struct oz_hcd *)hcd->hcd_priv; +} +/*------------------------------------------------------------------------------ + * Searches list of ports to find the index of the one with a specified USB + * bus address. If none of the ports has the bus address then the connection + * port is returned, if there is one or -1 otherwise. + * Context: any + */ +static int oz_get_port_from_addr(struct oz_hcd *ozhcd, u8 bus_addr) +{ + int i; + for (i = 0; i < OZ_NB_PORTS; i++) { + if (ozhcd->ports[i].bus_addr == bus_addr) + return i; + } + return ozhcd->conn_port; +} +/*------------------------------------------------------------------------------ + * Allocates an urb link, first trying the pool but going to heap if empty. + * Context: any + */ +static struct oz_urb_link *oz_alloc_urb_link(void) +{ + struct oz_urb_link *urbl = 0; + unsigned long irq_state; + spin_lock_irqsave(&g_link_lock, irq_state); + if (g_link_pool) { + urbl = container_of(g_link_pool, struct oz_urb_link, link); + g_link_pool = urbl->link.next; + --g_link_pool_size; + } + spin_unlock_irqrestore(&g_link_lock, irq_state); + if (urbl == 0) + urbl = oz_alloc(sizeof(struct oz_urb_link), GFP_ATOMIC); + return urbl; +} +/*------------------------------------------------------------------------------ + * Frees an urb link by putting it in the pool if there is enough space or + * deallocating it to heap otherwise. + * Context: any + */ +static void oz_free_urb_link(struct oz_urb_link *urbl) +{ + if (urbl) { + unsigned long irq_state; + spin_lock_irqsave(&g_link_lock, irq_state); + if (g_link_pool_size < OZ_MAX_LINK_POOL_SIZE) { + urbl->link.next = g_link_pool; + g_link_pool = &urbl->link; + urbl = 0; + g_link_pool_size++; + } + spin_unlock_irqrestore(&g_link_lock, irq_state); + if (urbl) + oz_free(urbl); + } +} +/*------------------------------------------------------------------------------ + * Deallocates all the urb links in the pool. + * Context: unknown + */ +static void oz_empty_link_pool(void) +{ + struct list_head *e; + unsigned long irq_state; + spin_lock_irqsave(&g_link_lock, irq_state); + e = g_link_pool; + g_link_pool = 0; + g_link_pool_size = 0; + spin_unlock_irqrestore(&g_link_lock, irq_state); + while (e) { + struct oz_urb_link *urbl = + container_of(e, struct oz_urb_link, link); + e = e->next; + oz_free(urbl); + } +} +/*------------------------------------------------------------------------------ + * Allocates endpoint structure and optionally a buffer. If a buffer is + * allocated it immediately follows the endpoint structure. + * Context: softirq + */ +static struct oz_endpoint *oz_ep_alloc(gfp_t mem_flags, int buffer_size) +{ + struct oz_endpoint *ep = + oz_alloc(sizeof(struct oz_endpoint)+buffer_size, mem_flags); + if (ep) { + memset(ep, 0, sizeof(*ep)); + INIT_LIST_HEAD(&ep->urb_list); + INIT_LIST_HEAD(&ep->link); + ep->credit = -1; + if (buffer_size) { + ep->buffer_size = buffer_size; + ep->buffer = (u8 *)(ep+1); + } + } + return ep; +} +/*------------------------------------------------------------------------------ + * Pre-condition: Must be called with g_tasklet_lock held and interrupts + * disabled. + * Context: softirq or process + */ +struct oz_urb_link *oz_uncancel_urb(struct oz_hcd *ozhcd, struct urb *urb) +{ + struct oz_urb_link *urbl; + struct list_head *e; + list_for_each(e, &ozhcd->urb_cancel_list) { + urbl = container_of(e, struct oz_urb_link, link); + if (urb == urbl->urb) { + list_del_init(e); + return urbl; + } + } + return 0; +} +/*------------------------------------------------------------------------------ + * This is called when we have finished processing an urb. It unlinks it from + * the ep and returns it to the core. + * Context: softirq or process + */ +static void oz_complete_urb(struct usb_hcd *hcd, struct urb *urb, + int status, unsigned long submit_jiffies) +{ + struct oz_hcd *ozhcd = oz_hcd_private(hcd); + unsigned long irq_state; + struct oz_urb_link *cancel_urbl = 0; + spin_lock_irqsave(&g_tasklet_lock, irq_state); + usb_hcd_unlink_urb_from_ep(hcd, urb); + /* Clear hcpriv which will prevent it being put in the cancel list + * in the event that an attempt is made to cancel it. + */ + urb->hcpriv = 0; + /* Walk the cancel list in case the urb is already sitting there. + * Since we process the cancel list in a tasklet rather than in + * the dequeue function this could happen. + */ + cancel_urbl = oz_uncancel_urb(ozhcd, urb); + /* Note: we release lock but do not enable local irqs. + * It appears that usb_hcd_giveback_urb() expects irqs to be disabled, + * or at least other host controllers disable interrupts at this point + * so we do the same. We must, however, release the lock otherwise a + * deadlock will occur if an urb is submitted to our driver in the urb + * completion function. Because we disable interrupts it is possible + * that the urb_enqueue function can be called with them disabled. + */ + spin_unlock(&g_tasklet_lock); + if (oz_forget_urb(urb)) { + oz_trace("OZWPAN: ERROR Unknown URB %p\n", urb); + } else { + static unsigned long last_time; + atomic_dec(&g_pending_urbs); + oz_trace2(OZ_TRACE_URB, + "%lu: giveback_urb(%p,%x) %lu %lu pending:%d\n", + jiffies, urb, status, jiffies-submit_jiffies, + jiffies-last_time, atomic_read(&g_pending_urbs)); + last_time = jiffies; + oz_event_log(OZ_EVT_URB_DONE, 0, 0, urb, status); + usb_hcd_giveback_urb(hcd, urb, status); + } + spin_lock(&g_tasklet_lock); + spin_unlock_irqrestore(&g_tasklet_lock, irq_state); + if (cancel_urbl) + oz_free_urb_link(cancel_urbl); +} +/*------------------------------------------------------------------------------ + * Deallocates an endpoint including deallocating any associated stream and + * returning any queued urbs to the core. + * Context: softirq + */ +static void oz_ep_free(struct oz_port *port, struct oz_endpoint *ep) +{ + oz_trace("oz_ep_free()\n"); + if (port) { + struct list_head list; + struct oz_hcd *ozhcd = port->ozhcd; + INIT_LIST_HEAD(&list); + if (ep->flags & OZ_F_EP_HAVE_STREAM) + oz_usb_stream_delete(port->hpd, ep->ep_num); + /* Transfer URBs to the orphanage while we hold the lock. */ + spin_lock_bh(&ozhcd->hcd_lock); + /* Note: this works even if ep->urb_list is empty.*/ + list_replace_init(&ep->urb_list, &list); + /* Put the URBs in the orphanage. */ + list_splice_tail(&list, &ozhcd->orphanage); + spin_unlock_bh(&ozhcd->hcd_lock); + } + oz_trace("Freeing endpoint memory\n"); + oz_free(ep); +} +/*------------------------------------------------------------------------------ + * Context: softirq + */ +static int oz_enqueue_ep_urb(struct oz_port *port, u8 ep_addr, int in_dir, + struct urb *urb, u8 req_id) +{ + struct oz_urb_link *urbl; + struct oz_endpoint *ep; + int err = 0; + if (ep_addr >= OZ_NB_ENDPOINTS) { + oz_trace("Invalid endpoint number in oz_enqueue_ep_urb().\n"); + return -EINVAL; + } + urbl = oz_alloc_urb_link(); + if (!urbl) + return -ENOMEM; + urbl->submit_jiffies = jiffies; + urbl->urb = urb; + urbl->req_id = req_id; + urbl->ep_num = ep_addr; + /* Hold lock while we insert the URB into the list within the + * endpoint structure. + */ + spin_lock_bh(&port->ozhcd->hcd_lock); + /* If the urb has been unlinked while out of any list then + * complete it now. + */ + if (urb->unlinked) { + spin_unlock_bh(&port->ozhcd->hcd_lock); + oz_trace("urb %p unlinked so complete immediately\n", urb); + oz_complete_urb(port->ozhcd->hcd, urb, 0, 0); + oz_free_urb_link(urbl); + return 0; + } + if (in_dir) + ep = port->in_ep[ep_addr]; + else + ep = port->out_ep[ep_addr]; + if (ep && port->hpd) { + list_add_tail(&urbl->link, &ep->urb_list); + if (!in_dir && ep_addr && (ep->credit < 0)) { + ep->last_jiffies = jiffies; + ep->credit = 0; + oz_event_log(OZ_EVT_EP_CREDIT, ep->ep_num, + 0, 0, ep->credit); + } + } else { + err = -EPIPE; + } + spin_unlock_bh(&port->ozhcd->hcd_lock); + if (err) + oz_free_urb_link(urbl); + return err; +} +/*------------------------------------------------------------------------------ + * Removes an urb from the queue in the endpoint. + * Returns 0 if it is found and -EIDRM otherwise. + * Context: softirq + */ +static int oz_dequeue_ep_urb(struct oz_port *port, u8 ep_addr, int in_dir, + struct urb *urb) +{ + struct oz_urb_link *urbl = 0; + struct oz_endpoint *ep; + spin_lock_bh(&port->ozhcd->hcd_lock); + if (in_dir) + ep = port->in_ep[ep_addr]; + else + ep = port->out_ep[ep_addr]; + if (ep) { + struct list_head *e; + list_for_each(e, &ep->urb_list) { + urbl = container_of(e, struct oz_urb_link, link); + if (urbl->urb == urb) { + list_del_init(e); + break; + } + urbl = 0; + } + } + spin_unlock_bh(&port->ozhcd->hcd_lock); + if (urbl) + oz_free_urb_link(urbl); + return urbl ? 0 : -EIDRM; +} +/*------------------------------------------------------------------------------ + * Finds an urb given its request id. + * Context: softirq + */ +static struct urb *oz_find_urb_by_id(struct oz_port *port, int ep_ix, + u8 req_id) +{ + struct oz_hcd *ozhcd = port->ozhcd; + struct urb *urb = 0; + struct oz_urb_link *urbl = 0; + struct oz_endpoint *ep; + + spin_lock_bh(&ozhcd->hcd_lock); + ep = port->out_ep[ep_ix]; + if (ep) { + struct list_head *e; + list_for_each(e, &ep->urb_list) { + urbl = container_of(e, struct oz_urb_link, link); + if (urbl->req_id == req_id) { + urb = urbl->urb; + list_del_init(e); + break; + } + } + } + spin_unlock_bh(&ozhcd->hcd_lock); + /* If urb is non-zero then we we must have an urb link to delete. + */ + if (urb) + oz_free_urb_link(urbl); + return urb; +} +/*------------------------------------------------------------------------------ + * Pre-condition: Port lock must be held. + * Context: softirq + */ +static void oz_acquire_port(struct oz_port *port, void *hpd) +{ + INIT_LIST_HEAD(&port->isoc_out_ep); + INIT_LIST_HEAD(&port->isoc_in_ep); + port->flags |= OZ_PORT_F_PRESENT | OZ_PORT_F_CHANGED; + port->status |= USB_PORT_STAT_CONNECTION | + (USB_PORT_STAT_C_CONNECTION << 16); + oz_usb_get(hpd); + port->hpd = hpd; +} +/*------------------------------------------------------------------------------ + * Context: softirq + */ +static struct oz_hcd *oz_hcd_claim(void) +{ + struct oz_hcd *ozhcd; + spin_lock_bh(&g_hcdlock); + ozhcd = g_ozhcd; + if (ozhcd) + usb_get_hcd(ozhcd->hcd); + spin_unlock_bh(&g_hcdlock); + return ozhcd; +} +/*------------------------------------------------------------------------------ + * Context: softirq + */ +static inline void oz_hcd_put(struct oz_hcd *ozhcd) +{ + if (ozhcd) + usb_put_hcd(ozhcd->hcd); +} +/*------------------------------------------------------------------------------ + * This is called by the protocol handler to notify that a PD has arrived. + * We allocate a port to associate with the PD and create a structure for + * endpoint 0. This port is made the connection port. + * In the event that one of the other port is already a connection port then + * we fail. + * TODO We should be able to do better than fail and should be able remember + * that this port needs configuring and make it the connection port once the + * current connection port has been assigned an address. Collisions here are + * probably very rare indeed. + * Context: softirq + */ +void *oz_hcd_pd_arrived(void *hpd) +{ + int i; + void *hport = 0; + struct oz_hcd *ozhcd = 0; + struct oz_endpoint *ep; + oz_trace("oz_hcd_pd_arrived()\n"); + ozhcd = oz_hcd_claim(); + if (ozhcd == 0) + return 0; + /* Allocate an endpoint object in advance (before holding hcd lock) to + * use for out endpoint 0. + */ + ep = oz_ep_alloc(GFP_ATOMIC, 0); + spin_lock_bh(&ozhcd->hcd_lock); + if (ozhcd->conn_port >= 0) { + spin_unlock_bh(&ozhcd->hcd_lock); + oz_trace("conn_port >= 0\n"); + goto out; + } + for (i = 0; i < OZ_NB_PORTS; i++) { + struct oz_port *port = &ozhcd->ports[i]; + spin_lock(&port->port_lock); + if ((port->flags & OZ_PORT_F_PRESENT) == 0) { + oz_acquire_port(port, hpd); + spin_unlock(&port->port_lock); + break; + } + spin_unlock(&port->port_lock); + } + if (i < OZ_NB_PORTS) { + oz_trace("Setting conn_port = %d\n", i); + ozhcd->conn_port = i; + /* Attach out endpoint 0. + */ + ozhcd->ports[i].out_ep[0] = ep; + ep = 0; + hport = &ozhcd->ports[i]; + spin_unlock_bh(&ozhcd->hcd_lock); + if (ozhcd->flags & OZ_HDC_F_SUSPENDED) { + oz_trace("Resuming root hub\n"); + usb_hcd_resume_root_hub(ozhcd->hcd); + } + usb_hcd_poll_rh_status(ozhcd->hcd); + } else { + spin_unlock_bh(&ozhcd->hcd_lock); + } +out: + if (ep) /* ep is non-null if not used. */ + oz_ep_free(0, ep); + oz_hcd_put(ozhcd); + return hport; +} +/*------------------------------------------------------------------------------ + * This is called by the protocol handler to notify that the PD has gone away. + * We need to deallocate all resources and then request that the root hub is + * polled. We release the reference we hold on the PD. + * Context: softirq + */ +void oz_hcd_pd_departed(void *hport) +{ + struct oz_port *port = (struct oz_port *)hport; + struct oz_hcd *ozhcd; + void *hpd; + struct oz_endpoint *ep = 0; + + oz_trace("oz_hcd_pd_departed()\n"); + if (port == 0) { + oz_trace("oz_hcd_pd_departed() port = 0\n"); + return; + } + ozhcd = port->ozhcd; + if (ozhcd == 0) + return; + /* Check if this is the connection port - if so clear it. + */ + spin_lock_bh(&ozhcd->hcd_lock); + if ((ozhcd->conn_port >= 0) && + (port == &ozhcd->ports[ozhcd->conn_port])) { + oz_trace("Clearing conn_port\n"); + ozhcd->conn_port = -1; + } + spin_lock(&port->port_lock); + port->flags |= OZ_PORT_F_DYING; + spin_unlock(&port->port_lock); + spin_unlock_bh(&ozhcd->hcd_lock); + + oz_clean_endpoints_for_config(ozhcd->hcd, port); + spin_lock_bh(&port->port_lock); + hpd = port->hpd; + port->hpd = 0; + port->bus_addr = 0xff; + port->flags &= ~(OZ_PORT_F_PRESENT | OZ_PORT_F_DYING); + port->flags |= OZ_PORT_F_CHANGED; + port->status &= ~USB_PORT_STAT_CONNECTION; + port->status |= (USB_PORT_STAT_C_CONNECTION << 16); + /* If there is an endpont 0 then clear the pointer while we hold + * the spinlock be we deallocate it after releasing the lock. + */ + if (port->out_ep[0]) { + ep = port->out_ep[0]; + port->out_ep[0] = 0; + } + spin_unlock_bh(&port->port_lock); + if (ep) + oz_ep_free(port, ep); + usb_hcd_poll_rh_status(ozhcd->hcd); + oz_usb_put(hpd); +} +/*------------------------------------------------------------------------------ + * Context: softirq + */ +void oz_hcd_pd_reset(void *hpd, void *hport) +{ + /* Cleanup the current configuration and report reset to the core. + */ + struct oz_port *port = (struct oz_port *)hport; + struct oz_hcd *ozhcd = port->ozhcd; + oz_trace("PD Reset\n"); + spin_lock_bh(&port->port_lock); + port->flags |= OZ_PORT_F_CHANGED; + port->status |= USB_PORT_STAT_RESET; + port->status |= (USB_PORT_STAT_C_RESET << 16); + spin_unlock_bh(&port->port_lock); + oz_clean_endpoints_for_config(ozhcd->hcd, port); + usb_hcd_poll_rh_status(ozhcd->hcd); +} +/*------------------------------------------------------------------------------ + * Context: softirq + */ +void oz_hcd_get_desc_cnf(void *hport, u8 req_id, int status, u8 *desc, + int length, int offset, int total_size) +{ + struct oz_port *port = (struct oz_port *)hport; + struct urb *urb; + int err = 0; + + oz_event_log(OZ_EVT_CTRL_CNF, 0, req_id, 0, status); + oz_trace("oz_hcd_get_desc_cnf length = %d offs = %d tot_size = %d\n", + length, offset, total_size); + urb = oz_find_urb_by_id(port, 0, req_id); + if (!urb) + return; + if (status == 0) { + int copy_len; + int required_size = urb->transfer_buffer_length; + if (required_size > total_size) + required_size = total_size; + copy_len = required_size-offset; + if (length <= copy_len) + copy_len = length; + memcpy(urb->transfer_buffer+offset, desc, copy_len); + offset += copy_len; + if (offset < required_size) { + struct usb_ctrlrequest *setup = + (struct usb_ctrlrequest *)urb->setup_packet; + unsigned wvalue = le16_to_cpu(setup->wValue); + if (oz_enqueue_ep_urb(port, 0, 0, urb, req_id)) + err = -ENOMEM; + else if (oz_usb_get_desc_req(port->hpd, req_id, + setup->bRequestType, (u8)(wvalue>>8), + (u8)wvalue, setup->wIndex, offset, + required_size-offset)) { + oz_dequeue_ep_urb(port, 0, 0, urb); + err = -ENOMEM; + } + if (err == 0) + return; + } + } + urb->actual_length = total_size; + oz_complete_urb(port->ozhcd->hcd, urb, 0, 0); +} +/*------------------------------------------------------------------------------ + * Context: softirq + */ +#ifdef WANT_TRACE +static void oz_display_conf_type(u8 t) +{ + switch (t) { + case USB_REQ_GET_STATUS: + oz_trace("USB_REQ_GET_STATUS - cnf\n"); + break; + case USB_REQ_CLEAR_FEATURE: + oz_trace("USB_REQ_CLEAR_FEATURE - cnf\n"); + break; + case USB_REQ_SET_FEATURE: + oz_trace("USB_REQ_SET_FEATURE - cnf\n"); + break; + case USB_REQ_SET_ADDRESS: + oz_trace("USB_REQ_SET_ADDRESS - cnf\n"); + break; + case USB_REQ_GET_DESCRIPTOR: + oz_trace("USB_REQ_GET_DESCRIPTOR - cnf\n"); + break; + case USB_REQ_SET_DESCRIPTOR: + oz_trace("USB_REQ_SET_DESCRIPTOR - cnf\n"); + break; + case USB_REQ_GET_CONFIGURATION: + oz_trace("USB_REQ_GET_CONFIGURATION - cnf\n"); + break; + case USB_REQ_SET_CONFIGURATION: + oz_trace("USB_REQ_SET_CONFIGURATION - cnf\n"); + break; + case USB_REQ_GET_INTERFACE: + oz_trace("USB_REQ_GET_INTERFACE - cnf\n"); + break; + case USB_REQ_SET_INTERFACE: + oz_trace("USB_REQ_SET_INTERFACE - cnf\n"); + break; + case USB_REQ_SYNCH_FRAME: + oz_trace("USB_REQ_SYNCH_FRAME - cnf\n"); + break; + } +} +#else +#define oz_display_conf_type(__x) +#endif /* WANT_TRACE */ +/*------------------------------------------------------------------------------ + * Context: softirq + */ +static void oz_hcd_complete_set_config(struct oz_port *port, struct urb *urb, + u8 rcode, u8 config_num) +{ + int rc = 0; + struct usb_hcd *hcd = port->ozhcd->hcd; + if (rcode == 0) { + port->config_num = config_num; + oz_clean_endpoints_for_config(hcd, port); + if (oz_build_endpoints_for_config(hcd, port, + &urb->dev->config[port->config_num-1], GFP_ATOMIC)) { + rc = -ENOMEM; + } + } else { + rc = -ENOMEM; + } + oz_complete_urb(hcd, urb, rc, 0); +} +/*------------------------------------------------------------------------------ + * Context: softirq + */ +static void oz_hcd_complete_set_interface(struct oz_port *port, struct urb *urb, + u8 rcode, u8 if_num, u8 alt) +{ + struct usb_hcd *hcd = port->ozhcd->hcd; + int rc = 0; + if (rcode == 0) { + struct usb_host_config *config; + struct usb_host_interface *intf; + oz_trace("Set interface %d alt %d\n", if_num, alt); + oz_clean_endpoints_for_interface(hcd, port, if_num); + config = &urb->dev->config[port->config_num-1]; + intf = &config->intf_cache[if_num]->altsetting[alt]; + if (oz_build_endpoints_for_interface(hcd, port, intf, + GFP_ATOMIC)) + rc = -ENOMEM; + else + port->iface[if_num].alt = alt; + } else { + rc = -ENOMEM; + } + oz_complete_urb(hcd, urb, rc, 0); +} +/*------------------------------------------------------------------------------ + * Context: softirq + */ +void oz_hcd_control_cnf(void *hport, u8 req_id, u8 rcode, u8 *data, + int data_len) +{ + struct oz_port *port = (struct oz_port *)hport; + struct urb *urb; + struct usb_ctrlrequest *setup; + struct usb_hcd *hcd = port->ozhcd->hcd; + unsigned windex; + unsigned wvalue; + + oz_event_log(OZ_EVT_CTRL_CNF, 0, req_id, 0, rcode); + oz_trace("oz_hcd_control_cnf rcode=%u len=%d\n", rcode, data_len); + urb = oz_find_urb_by_id(port, 0, req_id); + if (!urb) { + oz_trace("URB not found\n"); + return; + } + setup = (struct usb_ctrlrequest *)urb->setup_packet; + windex = le16_to_cpu(setup->wIndex); + wvalue = le16_to_cpu(setup->wValue); + if ((setup->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + /* Standard requests */ + oz_display_conf_type(setup->bRequest); + switch (setup->bRequest) { + case USB_REQ_SET_CONFIGURATION: + oz_hcd_complete_set_config(port, urb, rcode, + (u8)wvalue); + break; + case USB_REQ_SET_INTERFACE: + oz_hcd_complete_set_interface(port, urb, rcode, + (u8)windex, (u8)wvalue); + break; + default: + oz_complete_urb(hcd, urb, 0, 0); + } + + } else { + int copy_len; + oz_trace("VENDOR-CLASS - cnf\n"); + if (data_len <= urb->transfer_buffer_length) + copy_len = data_len; + else + copy_len = urb->transfer_buffer_length; + if (copy_len) + memcpy(urb->transfer_buffer, data, copy_len); + urb->actual_length = copy_len; + oz_complete_urb(hcd, urb, 0, 0); + } +} +/*------------------------------------------------------------------------------ + * Context: softirq-serialized + */ +static int oz_hcd_buffer_data(struct oz_endpoint *ep, u8 *data, int data_len) +{ + int space; + int copy_len; + if (!ep->buffer) + return -1; + space = ep->out_ix-ep->in_ix-1; + if (space < 0) + space += ep->buffer_size; + if (space < (data_len+1)) { + oz_trace("Buffer full\n"); + return -1; + } + ep->buffer[ep->in_ix] = (u8)data_len; + if (++ep->in_ix == ep->buffer_size) + ep->in_ix = 0; + copy_len = ep->buffer_size - ep->in_ix; + if (copy_len > data_len) + copy_len = data_len; + memcpy(&ep->buffer[ep->in_ix], data, copy_len); + + if (copy_len < data_len) { + memcpy(ep->buffer, data+copy_len, data_len-copy_len); + ep->in_ix = data_len-copy_len; + } else { + ep->in_ix += copy_len; + } + if (ep->in_ix == ep->buffer_size) + ep->in_ix = 0; + ep->buffered_units++; + return 0; +} +/*------------------------------------------------------------------------------ + * Context: softirq-serialized + */ +void oz_hcd_data_ind(void *hport, u8 endpoint, u8 *data, int data_len) +{ + struct oz_port *port = (struct oz_port *)hport; + struct oz_endpoint *ep; + struct oz_hcd *ozhcd = port->ozhcd; + spin_lock_bh(&ozhcd->hcd_lock); + ep = port->in_ep[endpoint & USB_ENDPOINT_NUMBER_MASK]; + if (ep == 0) + goto done; + switch (ep->attrib & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_INT: + case USB_ENDPOINT_XFER_BULK: + if (!list_empty(&ep->urb_list)) { + struct oz_urb_link *urbl = + list_first_entry(&ep->urb_list, + struct oz_urb_link, link); + struct urb *urb; + int copy_len; + list_del_init(&urbl->link); + spin_unlock_bh(&ozhcd->hcd_lock); + urb = urbl->urb; + oz_free_urb_link(urbl); + if (data_len <= urb->transfer_buffer_length) + copy_len = data_len; + else + copy_len = urb->transfer_buffer_length; + memcpy(urb->transfer_buffer, data, copy_len); + urb->actual_length = copy_len; + oz_complete_urb(port->ozhcd->hcd, urb, 0, 0); + return; + } + break; + case USB_ENDPOINT_XFER_ISOC: + oz_hcd_buffer_data(ep, data, data_len); + break; + } +done: + spin_unlock_bh(&ozhcd->hcd_lock); +} +/*------------------------------------------------------------------------------ + * Context: unknown + */ +static inline int oz_usb_get_frame_number(void) +{ + return jiffies_to_msecs(get_jiffies_64()); +} +/*------------------------------------------------------------------------------ + * Context: softirq + */ +int oz_hcd_heartbeat(void *hport) +{ + int rc = 0; + struct oz_port *port = (struct oz_port *)hport; + struct oz_hcd *ozhcd = port->ozhcd; + struct oz_urb_link *urbl; + struct list_head xfr_list; + struct list_head *e; + struct list_head *n; + struct urb *urb; + struct oz_endpoint *ep; + unsigned long now = jiffies; + INIT_LIST_HEAD(&xfr_list); + /* Check the OUT isoc endpoints to see if any URB data can be sent. + */ + spin_lock_bh(&ozhcd->hcd_lock); + list_for_each(e, &port->isoc_out_ep) { + ep = ep_from_link(e); + if (ep->credit < 0) + continue; + ep->credit += (now - ep->last_jiffies); + if (ep->credit > ep->credit_ceiling) + ep->credit = ep->credit_ceiling; + oz_event_log(OZ_EVT_EP_CREDIT, ep->ep_num, 0, 0, ep->credit); + ep->last_jiffies = now; + while (ep->credit && !list_empty(&ep->urb_list)) { + urbl = list_first_entry(&ep->urb_list, + struct oz_urb_link, link); + urb = urbl->urb; + if (ep->credit < urb->number_of_packets) + break; + ep->credit -= urb->number_of_packets; + oz_event_log(OZ_EVT_EP_CREDIT, ep->ep_num, 0, 0, + ep->credit); + list_del(&urbl->link); + list_add_tail(&urbl->link, &xfr_list); + } + } + spin_unlock_bh(&ozhcd->hcd_lock); + /* Send to PD and complete URBs. + */ + list_for_each_safe(e, n, &xfr_list) { + unsigned long t; + urbl = container_of(e, struct oz_urb_link, link); + urb = urbl->urb; + t = urbl->submit_jiffies; + list_del_init(e); + urb->error_count = 0; + urb->start_frame = oz_usb_get_frame_number(); + oz_usb_send_isoc(port->hpd, urbl->ep_num, urb); + oz_free_urb_link(urbl); + oz_complete_urb(port->ozhcd->hcd, urb, 0, t); + } + /* Check the IN isoc endpoints to see if any URBs can be completed. + */ + spin_lock_bh(&ozhcd->hcd_lock); + list_for_each(e, &port->isoc_in_ep) { + struct oz_endpoint *ep = ep_from_link(e); + if (ep->flags & OZ_F_EP_BUFFERING) { + if (ep->buffered_units * OZ_IN_BUFFERING_UNITS) { + ep->flags &= ~OZ_F_EP_BUFFERING; + ep->credit = 0; + oz_event_log(OZ_EVT_EP_CREDIT, + ep->ep_num | USB_DIR_IN, + 0, 0, ep->credit); + ep->last_jiffies = now; + ep->start_frame = 0; + oz_event_log(OZ_EVT_EP_BUFFERING, + ep->ep_num | US |