This is where we interface with the existing USB stack and implement the functionality of a USB host controller driver. From the host's perspective, we appear as just another USB host controller. However, instead of passing traffic along a wired USB bus, the driver hands USB packets off for transport per Media Agnostic USB protocol.
Signed-off-by: Sean O. Stalley <sean.stal...@intel.com> Signed-off-by: Stephanie Wallick <stephanie.s.wall...@intel.com> --- drivers/staging/mausb/drivers/mausb_hcd.c | 970 ++++++++++++++++++++++++++++++ drivers/staging/mausb/drivers/mausb_hcd.h | 171 ++++++ 2 files changed, 1141 insertions(+) create mode 100755 drivers/staging/mausb/drivers/mausb_hcd.c create mode 100644 drivers/staging/mausb/drivers/mausb_hcd.h diff --git a/drivers/staging/mausb/drivers/mausb_hcd.c b/drivers/staging/mausb/drivers/mausb_hcd.c new file mode 100755 index 0000000..03e8f0f --- /dev/null +++ b/drivers/staging/mausb/drivers/mausb_hcd.c @@ -0,0 +1,970 @@ +/* Name: mausb_hcd.c + * Description: Creates and initializes a virtual USB host controller driver + * for the Media Agnostic USB host driver. + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2014 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * 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. + * + * Contact Information: + * Sean Stalley, sean.stal...@intel.com + * Stephanie Wallick, stephanie.s.wall...@intel.com + * 2111 NE 25th Avenue + * Hillsboro, Oregon 97124 + * + * BSD LICENSE + * + * Copyright(c) 2014 Intel Corporation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Intel Corporation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define DEBUG + +#include <linux/init.h> +#include <linux/wait.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/platform_device.h> +#include <linux/usb/ch11.h> +#include <linux/pm_runtime.h> +#include <linux/usb/gadget.h> +#include <linux/kthread.h> +#include <linux/random.h> + +#include "mausb_hcd.h" +#include "mausb_hub.h" +#include "mausb_pkt.h" +#include "mausb_mem-host.h" +#include "mausb_msapi.h" +#include "mausb_mgmt.h" +#include "mausb_state.h" +#include "mausb_tx.h" + +static struct platform_device mausb_pdev; + +struct api_context { + struct completion done; + int status; +}; + +/* + * pointer conversion functions + */ +inline struct mausb_hcd *usb_hcd_to_mausb_hcd(struct usb_hcd *hcd) +{ + if (usb_hcd_is_primary_hcd(hcd)) + return *((struct mausb_hcd **) (hcd->hcd_priv)); + else + return *((struct mausb_hcd **) (hcd->primary_hcd->hcd_priv)); +} + +inline struct usb_hcd *mausb_hcd_to_usb_hcd(struct mausb_hcd *mhcd) +{ + if (mhcd->shared_hcd && !usb_hcd_is_primary_hcd(mhcd->shared_hcd)) + return mhcd->shared_hcd; + else + return mhcd->usb_hcd; +} + +inline struct device *mausb_hcd_to_dev(struct mausb_hcd *mhcd) +{ + return mausb_hcd_to_usb_hcd(mhcd)->self.controller; +} + +inline struct mausb_urb *usb_urb_to_mausb_urb(struct urb *urb) +{ + return (struct mausb_urb *) urb->hcpriv; +} +/* ----------------------------------------------------------------- */ + +/** + * @maurb: Media agnostic structure with URB to release. + * @status: Status for URB that is getting released. + * + * Removes an URB from the queue, deletes the media agnostic information in + * the urb, and gives the URB back to the HCD. Caller must be holding the + * driver's spinlock. + */ +void mausb_unlink_giveback_urb(struct mausb_urb *maurb, int status) +{ + struct urb *urb; + struct usb_hcd *hcd; + struct api_context *ctx = NULL; + struct mausb_hcd *mhcd = platform_get_drvdata(&mausb_pdev); + unsigned long irq_flags; + + if (!mhcd) { + printk(KERN_ERR "%s: No MA USB HCD\n", __func__); + return; + } + + hcd = mausb_hcd_to_usb_hcd(mhcd); + + spin_lock_irqsave(&mhcd->giveback_lock, irq_flags); + + if (!maurb) { + mausb_err(mhcd, "%s: no maurb\n", __func__); + spin_unlock_irqrestore(&mhcd->giveback_lock, irq_flags); + return; + } else { + urb = maurb->urb; + ctx = urb->context; + } + + if (!urb) { + mausb_err(mhcd, "%s: no urb\n", __func__); + mausb_internal_drop_maurb(maurb, mhcd); + spin_unlock_irqrestore(&mhcd->giveback_lock, irq_flags); + return; + } + + mausb_dbg(mhcd, "%s: returning urb with status %i\n", __func__, status); + + usb_hcd_unlink_urb_from_ep(hcd, urb); + usb_hcd_giveback_urb(hcd, urb, status); + + /* remove the mausb-specific data */ + mausb_internal_drop_maurb(maurb, mhcd); + + spin_unlock_irqrestore(&mhcd->giveback_lock, irq_flags); +} + +/** + * Adds an URB to the endpoint queue then calls the URB handler. URB is wrapped + * in media agnostic structure before being enqueued. + */ +static int mausb_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t memflags) +{ + int ret = 0; + struct mausb_urb *maurb; + struct mausb_hcd *mhcd; + struct mausb_host_ep *ep; + unsigned long irq_flags; + + if (!hcd || !urb) { + printk(KERN_ERR "%s: no %s\n", __func__, + (hcd ? "urb" : "USB hcd")); + } + + mhcd = usb_hcd_to_mausb_hcd(hcd); + ep = usb_to_ma_endpoint(urb->ep); + + if (!ep || !mhcd) { + mausb_err(mhcd, "%s: no %s\n", __func__, + (ep ? "MA USB HCD" : "endpoint")); + return -EINVAL; + } + + if (urb->status != -EINPROGRESS) { + mausb_err(mhcd, "%s: urb already unlinked, status is %i\n", + __func__, urb->status); + return urb->status; + } + + /* If the endpoint isn't activated, we can't enqueue anything. */ + if (MAUSB_EP_HANDLE_UNASSIGNED == ep->ep_handle_state) { + mausb_err(mhcd, "%s: endpoint handle unassigned\n", __func__); + return -EPIPE; + } + + if (USB_SPEED_FULL != urb->dev->speed) /* suppress checks */ + ep->max_pkt = usb_endpoint_maxp(&urb->ep->desc); + + /* initialize the maurb */ + maurb = mausb_alloc_maurb(ep, memflags); + if (!maurb) { + mausb_err(mhcd, "could not allocate memory for MA USB urb\n"); + return -ENOMEM; + } + + /* set maurb member values */ + maurb->urb = urb; + urb->hcpriv = maurb; + + /* submit urb to hcd and add to endpoint queue */ + ret = usb_hcd_link_urb_to_ep(hcd, urb); + if (ret < 0) { + mausb_err(mhcd, "urb enqueue failed: error %d\n", ret); + usb_hcd_unlink_urb_from_ep(hcd, urb); + return ret; + } + + /* get usb device and increment reference counter */ + if (!mhcd->udev) { + mhcd->udev = urb->dev; + usb_get_dev(mhcd->udev); + } + + /* add urb to queue list */ + spin_lock_irqsave(&ep->ep_lock, irq_flags); + list_add_tail(&maurb->urb_list, &ep->urb_list); + spin_unlock_irqrestore(&ep->ep_lock, irq_flags); + + /* add urb to ma hcd urb list */ + spin_lock_irqsave(&mhcd->urb_list_lock, irq_flags); + list_add_tail(&maurb->ma_hcd_urb_list, &mhcd->enqueue_urb_list); + spin_unlock_irqrestore(&mhcd->urb_list_lock, irq_flags); + + /* send to MA transfer process */ + wake_up(&mhcd->waitq); + + return ret; +} + +/** + * Dequeues an URB. + */ +static int mausb_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + int ret = 0; + struct mausb_hcd *mhcd = usb_hcd_to_mausb_hcd(hcd); + struct mausb_host_ep *ep = usb_to_ma_endpoint(urb->ep); + struct mausb_urb *maurb = usb_urb_to_mausb_urb(urb); + unsigned long irq_flags; + + /* For debugging - we want to know who initiated URB dequeue. */ + dump_stack(); + + if (ep->active_transfer == maurb) { + ret = mausb_tx_dev_mgmt_req_ep(CancelTransferReq, + &mhcd->ma_dev.mgmt, maurb->dev, true, ep); + + spin_lock_irqsave(&ep->ep_lock, irq_flags); + ep->active_transfer = NULL; + ep->state.tx_pending = 0; + spin_unlock_irqrestore(&ep->ep_lock, irq_flags); + } + + /* + * Make sure urb hasn't been unlinked or already completed. + * Dequeue must fail if usb_hcd_check_unlink_urb() fails. + */ + spin_lock_irqsave(&ep->ep_lock, irq_flags); + ret = usb_hcd_check_unlink_urb(hcd, urb, status); + + if (ret < 0) { + spin_unlock_irqrestore(&ep->ep_lock, irq_flags); + mausb_err(mhcd, "%s: urb already unlinked or completed, %d\n", + __func__, ret); + return ret; + } + + /* check to make sure our urb queue is not empty */ + if (list_empty(&ep->urb_list)) { + spin_unlock_irqrestore(&ep->ep_lock, irq_flags); + mausb_err(mhcd, "%s: urb queue is empty\n", __func__); + return -ENXIO; + } + + spin_unlock_irqrestore(&ep->ep_lock, irq_flags); + + /* now we can give back URB */ + spin_lock_irqsave(&mhcd->hcd_lock, irq_flags); + mausb_unlink_giveback_urb(maurb, -EINPROGRESS); + spin_unlock_irqrestore(&mhcd->hcd_lock, irq_flags); + + return ret; +} + +/** + * Called by usb core to suspend the bus. Once suspended, hcd waits in + * that state until it is resumed by mausb_bus_resume(). + * + * Note: sections are commented out to accomidate devices that don't yet + * support SleepReq packets. + * + * Always returns zero. + */ +static int mausb_bus_suspend(struct usb_hcd *hcd) +{ +/* int ret = 0; */ + struct mausb_hcd *mhcd = usb_hcd_to_mausb_hcd(hcd); +/* struct mausb_dev *top_dev = mhcd->ma_dev.top_dev; */ + struct mausb_root_hub *roothub = usb_hcd_to_roothub(hcd); + +/* if (NULL != top_dev) { + ret = mausb_tx_dev_mgmt_req(SleepReq, + &mhcd->ma_dev.mgmt, top_dev, true); + if (ret < 0) + return ret; + } +*/ + /* stop polling */ + clear_bit(HCD_FLAG_POLL_RH, &hcd->flags); + + /* + * Avoid suspend/resume race condition by making sure there aren't any + * devices in the middle of resuming. If a connected device is + * resuming, suspend should fail. + */ + if (mhcd->resuming) { + mausb_err(mhcd, "%s: suspend failed, device is resuming\n", + __func__); + return -EBUSY; + } + + mausb_dbg(mhcd, "%s: suspending mausb driver\n", __func__); + + roothub->rh_state = MAUSB_RH_SUSPEND; + hcd->state = HC_STATE_SUSPENDED; + + return 0; +} + +/** + * Called by usb core to resume hcd after bus suspension. + * + * Returns 0 if resume was successful, otherwise returns negative error + * value. + */ +static int mausb_bus_resume(struct usb_hcd *hcd) +{ + int ret = 0; + struct mausb_hcd *mhcd = usb_hcd_to_mausb_hcd(hcd); + struct mausb_dev *top_dev = mhcd->ma_dev.top_dev; + struct mausb_root_hub *roothub = usb_hcd_to_roothub(hcd); + + if (!HCD_HW_ACCESSIBLE(hcd)) { + mausb_err(mhcd, "%s: hcd not fully powered\n", __func__); + return -ESHUTDOWN; + } + + mausb_dbg(mhcd, "%s: resuming mausb driver\n", __func__); + + hcd->state = HC_STATE_RUNNING; + roothub->rh_state = MAUSB_RH_RUNNING; + + if (NULL != top_dev) { + ret = mausb_tx_dev_mgmt_req(WakeReq, &mhcd->ma_dev.mgmt, + top_dev, true); + + if (ret < 0) { + mausb_err(mhcd, "%s: WakeReq was unsuccessful" + " (error %i)\n", __func__, ret); + return ret; + } + } + + return 0; +} + +/** + * Returns the hardware-chosen device address. + */ +static int mausb_address_device(struct usb_hcd *hcd, struct usb_device *udev) +{ + int ret = 0; + struct ma_dev *ma_dev; + struct mausb_dev *mausb_dev; + struct mausb_host_ep *ma_ep; + struct mausb_hcd *mhcd = usb_hcd_to_mausb_hcd(hcd); + + if (NULL == mhcd) + return -EINVAL; + + ma_dev = &mhcd->ma_dev; + if (NULL == ma_dev) + return -EINVAL; + + mausb_dev = mausb_find_dev_host(ma_dev, udev); + if (NULL == mausb_dev) + return -ENODEV; + + ret = mausb_tx_dev_mgmt_req(USBDevHandleReq, &ma_dev->mgmt, + mausb_dev, true); + + if (-ETIMEDOUT == ret) { + mausb_err(mhcd, "USBDevHandleReq timed out\n"); + } else if (0 > ret) { + mausb_err(mhcd, "USBDevHandleReq failed with error %i\n", ret); + } else { + ret = mausb_tx_dev_mgmt_req(EPHandleReq, &ma_dev->mgmt, + mausb_dev, true); + + if (-ETIMEDOUT == ret) { + mausb_err(mhcd, "EPHandleReq timed out\n"); + } else if (0 > ret) { + mausb_err(mhcd, "EPHandleReq failed with error %i\n", + ret); + } else { + ret = mausb_tx_dev_mgmt_req(SetUSBDevAddrReq, + &ma_dev->mgmt, mausb_dev, true); + + if (-ETIMEDOUT == ret) { + mausb_err(mhcd, "SetUSBDevAddrReq timed out\n"); + } else if (0 > ret) { + mausb_err(mhcd, "SetUSBDevAddrReq failed with" + " error %i\n", ret); + } else { + ma_ep = mausb_find_ep_host(&udev->ep0, + mausb_dev); + ret = mausb_tx_dev_mgmt_req_ep(ModifyEP0Req, + &ma_dev->mgmt, mausb_dev, true, ma_ep); + } + } + } + + /* TODO: handle failed enumeration */ + + return 0; +} + +/** + * Resets everything to default values. Always returns zero. + */ +static int mausb_reset(struct usb_hcd *hcd) +{ + struct mausb_hcd *mhcd = usb_hcd_to_mausb_hcd(hcd); + struct mausb_root_hub *roothub = usb_hcd_to_roothub(hcd); + + mausb_dbg(mhcd, "%s: resetting MA USB host driver . . .\n", __func__); + + roothub->rh_state = MAUSB_RH_RESETTING; + + if (mausb_is_ss_hcd(hcd)) { + hcd->speed = HCD_USB3; + hcd->self.root_hub->speed = USB_SPEED_SUPER; + } else { + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_HIGH; + hcd->has_tt = 1; + } + + hcd->uses_new_polling = 1; + hcd->self.sg_tablesize = 0; + + mausb_init_port_status(&mhcd->root_hub); + mausb_init_port_status(&mhcd->shared_root_hub); + + return 0; +} + +int mausb_hcd_thread(void *data) +{ + struct mausb_hcd *mhcd = (struct mausb_hcd *)data; + struct mausb_urb *ma_urb, *next_ma_urb; + struct mausb_host_ep *ma_ep; + unsigned long irq_flags; + + while (!kthread_should_stop()) { + spin_lock_irqsave(&mhcd->urb_list_lock, irq_flags); + list_for_each_entry_safe(ma_urb, next_ma_urb, + &mhcd->enqueue_urb_list, ma_hcd_urb_list) { + + list_move(&ma_urb->ma_hcd_urb_list, + &mhcd->transfer_urb_list); + + spin_unlock_irqrestore(&mhcd->urb_list_lock, irq_flags); + + ma_ep = ma_urb->ep; + start_ma_transfer(ma_ep, ma_urb, + usb_pipein(ma_urb->urb->pipe)); + + spin_lock_irqsave(&mhcd->urb_list_lock, irq_flags); + } + spin_unlock_irqrestore(&mhcd->urb_list_lock, irq_flags); + + wait_event_interruptible(mhcd->waitq, + !list_empty(&mhcd->enqueue_urb_list) || + kthread_should_stop()); + } + + do_exit(0); + return 0; +} + +/** + * Tells the hcd to start running. Always returns zero. + */ +static int mausb_start(struct usb_hcd *hcd) +{ + struct mausb_hcd *mhcd = usb_hcd_to_mausb_hcd(hcd); + struct mausb_root_hub *roothub = usb_hcd_to_roothub(hcd); + + mausb_dbg(mhcd, "%s: starting MA USB driver . . .\n", __func__); + + hcd->state = HC_STATE_RUNNING; + roothub->rh_state = MAUSB_RH_RUNNING; + + spin_lock_init(&mhcd->hcd_lock); + INIT_LIST_HEAD(&mhcd->ma_dev.dev_list); + INIT_LIST_HEAD(&mhcd->enqueue_urb_list); + spin_lock_init(&mhcd->urb_list_lock); + INIT_LIST_HEAD(&mhcd->transfer_urb_list); + spin_lock_init(&mhcd->giveback_lock); + init_waitqueue_head(&mhcd->waitq); + mhcd->ma_hcd_task_data = (void *)mhcd; + mhcd->ma_hcd_task = kthread_run(mausb_hcd_thread, + mhcd->ma_hcd_task_data, "mausb_hcd_thread"); + + return 0; +} + +/** + * Called when driver is removed (specifically during usb_remove_hcd). This is + * where we tell the hcd to stop writing memory and doing I/O. + */ +static void mausb_stop(struct usb_hcd *hcd) +{ + struct mausb_hcd *mhcd = usb_hcd_to_mausb_hcd(hcd); + + if (mhcd->ma_hcd_task != NULL) { + kthread_stop(mhcd->ma_hcd_task); + mhcd->ma_hcd_task = NULL; + } + + mausb_dbg(mhcd, "%s: stopping mausb driver . . .\n", __func__); +} + +static const struct hc_driver mausb_hc_driver = { + .description = "mausb-hcd", + .product_desc = "mausb pseudo-host controller", + .hcd_priv_size = sizeof(struct mausb_hcd), + .irq = NULL, + .flags = HCD_USB3, + .reset = &mausb_reset, + .start = &mausb_start, + .stop = &mausb_stop, + .urb_enqueue = &mausb_urb_enqueue, + .urb_dequeue = &mausb_urb_dequeue, + .hub_status_data = &mausb_hub_status_data, + .hub_control = &mausb_hub_control, + .bus_suspend = &mausb_bus_suspend, + .bus_resume = &mausb_bus_resume, + .alloc_dev = &mausb_alloc_dev, + .free_dev = &mausb_free_dev, + .add_endpoint = &mausb_add_endpoint, + .drop_endpoint = &mausb_drop_endpoint, + .check_bandwidth = &mausb_check_bandwidth, + .reset_bandwidth = &mausb_reset_bandwidth, + .address_device = &mausb_address_device, +}; + +/** + * Sets the host_bos fields to the proper initial values per USB3 spec, + * section 10.13.1. + */ +static int mausb_set_host_bos(struct mausb_host_bos *host_bos) +{ + /* BOS Descriptor */ + host_bos->bos_des.bLength = USB_DT_BOS_SIZE; + host_bos->bos_des.bDescriptorType = USB_DT_BOS; + host_bos->bos_des.wTotalLength = sizeof(struct mausb_host_bos); + host_bos->bos_des.bNumDeviceCaps = MAUSB_HOST_NUM_DEV_CAPS; + + /* SuperSpeed USB Device Capability */ + host_bos->ss_cap_des.bLength = USB_DT_USB_SS_CAP_SIZE; + host_bos->ss_cap_des.bDescriptorType = USB_DT_DEVICE_CAPABILITY; + host_bos->ss_cap_des.bDevCapabilityType = USB_SS_CAP_TYPE; + host_bos->ss_cap_des.bmAttributes = USB_LTM_SUPPORT; + host_bos->ss_cap_des.wSpeedSupported = USB_5GBPS_OPERATION; + host_bos->ss_cap_des.bFunctionalitySupport = MAUSB_SUPER_SPEED; + host_bos->ss_cap_des.bU1devExitLat = MAUSB_HOST_U1_DEV_EXIT_LAT; + host_bos->ss_cap_des.bU2DevExitLat = MAUSB_HOST_U2_DEV_EXIT_LAT; + + return 0; +} + +/** + * @req: The incoming MA USB management request packet to handle. + * @resp: The MA USB management response packet to send out. + * @mgmt: The MA USB HCD's management structure. + * + * This function handles management requests to the HCD. It parses the packet + * and makes the necessary function calls into the system. + * + * Note: this function only completes the packet type, status and + * request-specific data-fields. the remainder of the fields are set + * by mausb_rx_mgmt_req + */ +static int hcd_mgmt_req_switch(struct mausb_mgmt_pkt *req, + struct mausb_mgmt_pkt *resp, struct mausb_mgmt *mgmt) +{ + int ret = 0; + + switch (req->common.pkt_type) { + + /* TODO: handle unimplemented request types */ + case RemoteWakeReq: + case DevInitDisconnectReq: + break; + case PingReq: + resp->common.pkt_type = PingResp; + resp->common.pkt_status = NO_ERROR; + break; + /* invalid packet type */ + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int mausb_transfer_mgmt_packet(struct ms_pkt *ms_pkt, void *context); + +/** + * Generates the mass_id used to initialize an MA USB device. Uses a random + * number generator to choose a random id between 1 and 254. + * + * Returns the random id chosen. + */ +static __u8 get_mass_id(void) +{ + __u8 id; + + get_random_bytes(&id, sizeof(id)); + + /* Note: this function is twice as likely to pick 1 or 2 */ + id = (id % (MASS_ID_MAX - MASS_ID_MIN + 1) + MASS_ID_MIN); + + return id; +} + +/** + * Initializes MA USB hcd roothub descriptors and ports. + * + * Always returns zero. + */ +static int create_mausb_hcd(struct mausb_hcd *mausb_hcd) +{ + if (mausb_hcd == NULL) + return -ENOMEM; + + /* set the roothub descriptor to the proper initial values */ + mausb_set_hub_descriptor(&mausb_hcd->root_hub.descriptor); + mausb_set_ss_hub_descriptor(&mausb_hcd->shared_root_hub.descriptor); + + /* set the host_bos to the proper values */ + mausb_set_host_bos(&mausb_hcd->host_bos); + + /* initialize port status values */ + mausb_init_port_status(&mausb_hcd->root_hub); + mausb_init_ss_port_status(&mausb_hcd->shared_root_hub); + + /* initialize the MA USB device structure */ + mausb_init_ma_device(&mausb_hcd->ma_dev, MAUSB_DEV_ADDR, get_mass_id(), + mausb_hcd, NULL, &hcd_mgmt_req_switch, &mausb_device_connect); + + return 0; +} + +/** + * probe function + */ +static int mausb_probe(struct platform_device *mausb_pdev) +{ + int ret = 0; + const char *bus_name = "MAUSB"; + unsigned int mausb_irqnum = 0; + struct usb_hcd *usb_hcd; + struct mausb_hcd *mhcd; + + /* create our USB 2.0 roothub */ + usb_hcd = usb_create_hcd(&mausb_hc_driver, &mausb_pdev->dev, bus_name); + if (usb_hcd == NULL) { + dev_err(&mausb_pdev->dev, "%s: could not create USB HCD\n", + __func__); + return -ENOMEM; + } + + /* create our data structure that holds MA USB data */ + mhcd = kzalloc(sizeof(struct mausb_hcd), GFP_KERNEL); + ret = create_mausb_hcd(mhcd); + if (ret < 0) { + dev_err(&mausb_pdev->dev, "%s: could not create MA USB HCD" + " (error %i)\n", __func__, ret); + + if (ret != -ENOMEM) + kfree(mhcd); + + return ret; + } + + /* + * Everyone exchanges pointers so we can move between data types with + * minimal pointer juju. + */ + platform_set_drvdata(mausb_pdev, mhcd); + *((struct mausb_hcd **) usb_hcd->hcd_priv) = mhcd; + mhcd->usb_hcd = usb_hcd; + + /* + * Finish initializing generic members of USB 2.0 HCD, register the bus, + * request IRQ line, call driver's reset() and start() routines. + */ + ret = usb_add_hcd(usb_hcd, mausb_irqnum, IRQF_SHARED); + if (ret) { + dev_err(&mausb_pdev->dev, "%s: could not add USB HCD" + " (error %i)\n", __func__, ret); + + usb_remove_hcd(usb_hcd); + kfree(mhcd); + + return ret; + } + + /* add our USB 3.0 roothub */ + mhcd->shared_hcd = usb_create_shared_hcd(&mausb_hc_driver, + &mausb_pdev->dev, bus_name, usb_hcd); + + if (mhcd->shared_hcd == NULL) { + dev_dbg(&mausb_pdev->dev, + "%s: could not create shared USB HCD\n", __func__); + + usb_remove_hcd(usb_hcd); + kfree(mhcd); + + return -ENOMEM; + } + + ret = usb_add_hcd(mhcd->shared_hcd, mausb_irqnum, IRQF_SHARED); + + if (ret) { + dev_err(&mausb_pdev->dev, "%s: could not add shared" + " USB HCD (error %i)\n", __func__, ret); + + usb_remove_hcd(usb_hcd); + kfree(mhcd); + } + + return ret; +} + +/** + * remove function + */ +static int mausb_remove(struct platform_device *mausb_pdev) +{ + int ret = 0; + struct usb_hcd *hcd, *shared_hcd; + struct mausb_hcd *mhcd = platform_get_drvdata(mausb_pdev); + + if (mhcd == NULL) { + dev_err(&mausb_pdev->dev, + "%s:MA USB HCD not found\n", __func__); + return 0; + } + + hcd = mhcd->usb_hcd; + shared_hcd = mhcd->shared_hcd; + + if (mhcd->shared_hcd) { + usb_remove_hcd(mhcd->shared_hcd); + usb_put_hcd(mhcd->shared_hcd); + mhcd->shared_hcd = NULL; + } + + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + + kfree(mhcd); + + return ret; +} + +/** + * Release function. + */ +static void mausb_dev_release(struct device *dev) +{ + /* TODO: if we dynamically allocate anything, free it here */ +} + +static struct platform_driver mausb_driver = { + .probe = mausb_probe, + .remove = mausb_remove, + .driver = { + .name = MAUSB_NAME, + .owner = THIS_MODULE, + }, +}; + +static struct platform_device mausb_pdev = { + .name = MAUSB_NAME, + .id = 0, +}; + +/** + * @pkt The incoming MA USB packet to parse. + * @context The MA USB HCD's management structure. + * + * Used to transfer MA USB management packets to and from an MA USB device. + */ +static int mausb_transfer_mgmt_packet(struct ms_pkt *ms_pkt, void *context) +{ + int ret; + struct mausb_mgmt *mgmt; + struct mausb_hcd *mhcd = platform_get_drvdata(&mausb_pdev); + struct mausb_pkt *pkt = mausb_pkt_from_ms_pkt_ma_dev(ms_pkt, + &mhcd->ma_dev, GFP_ATOMIC); + + if ((NULL == context) || (NULL == pkt)) { + mausb_err(mhcd, "%s: received NULL input\n", __func__); + return -EFAULT; + } + + mgmt = (struct mausb_mgmt *) context; + + ret = mausb_rx_mgmt(pkt, mgmt); + + return ret; +} + +/** + * @drv: The media specific driver structure that provides an interface + * for the media agnostic driver. + * + * Registers a media specific driver with the MA USB HCD. + */ +struct mausb_ma_drv *mausb_register_ms_driver(struct mausb_ms_drv *drv) +{ + struct mausb_hcd *mhcd = platform_get_drvdata(&mausb_pdev); + unsigned long irq_flags; + + spin_lock_irqsave(&mhcd->hcd_lock, irq_flags); + mhcd->ma_dev.ms_driver = drv; + spin_unlock_irqrestore(&mhcd->hcd_lock, irq_flags); + + /* Open the management channel */ + mausb_add_mgmt_channel(&mhcd->ma_dev.mgmt, drv, + &mausb_transfer_mgmt_packet, &mausb_mgmt_pkt_sent); + + return &mhcd->ma_dev.ma_drv; +} +EXPORT_SYMBOL(mausb_register_ms_driver); + +/** + * Called by a media specific driver to indicate that an MA USB device has been + * connected and is ready to receive MA USB packets. + */ +int mausb_device_connect(int on) +{ + int status = 0; + /* TODO: don't want portnum hard coded when we add more ports */ + int portnum = 0; + struct mausb_hcd *mhcd = platform_get_drvdata(&mausb_pdev); + + /* connection event */ + if (on) { + /* reset MA USB device */ + status = mausb_tx_dev_mgmt_req(DevResetReq, &mhcd->ma_dev.mgmt, + NULL, true); + + if (status < 0) { + mausb_err(mhcd, "%s: cannot reset MA device," + " error %i\n", __func__, status); + return status; + } + + /* send CapResp packet */ + if (0 <= status) { + status = mausb_tx_dev_mgmt_req(CapReq, + &mhcd->ma_dev.mgmt, NULL, true); + } + + if (status < 0) { + mausb_err(mhcd, "%s: cannot get device capabilities," + " error %i\n", __func__, status); + return status; + } + } + + status = mausb_connect(mhcd, portnum, on); + + return status; +} + +/** + * initialization function + */ +static int mausb_hcd_init(void) +{ + int ret; + + /* register HCD driver */ + ret = platform_driver_register(&mausb_driver); + if (ret < 0) { + printk(KERN_DEBUG "%s: failed to register HC driver: " + " error number %d\n", __func__, ret); + + } else { + /* register HCD device */ + ret = platform_device_register(&mausb_pdev); + + if (ret < 0) { + printk(KERN_DEBUG "%s: failed to register HC device:" + "error number %d\n", __func__, ret); + platform_driver_unregister(&mausb_driver); + } else { + /* direct the release function (for exiting) */ + mausb_pdev.dev.release = &mausb_dev_release; + + if (ret < 0) { + printk(KERN_DEBUG "failed to register HC" + " chardev: error number %d\n", ret); + platform_driver_unregister(&mausb_driver); + platform_device_unregister(&mausb_pdev); + } + } + } + + return ret; +} +module_init(mausb_hcd_init); + +/** + * Exit function. + */ +static void mausb_hcd_exit(void) +{ + /* deregister HC device */ + platform_device_unregister(&mausb_pdev); + + /* deregister HC driver */ + platform_driver_unregister(&mausb_driver); +} +module_exit(mausb_hcd_exit); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_AUTHOR("Intel"); +MODULE_DESCRIPTION("MA USB driver"); diff --git a/drivers/staging/mausb/drivers/mausb_hcd.h b/drivers/staging/mausb/drivers/mausb_hcd.h new file mode 100644 index 0000000..29c431a --- /dev/null +++ b/drivers/staging/mausb/drivers/mausb_hcd.h @@ -0,0 +1,171 @@ +/* Name: mausb_hcd.h + * Description: header file for mausb_hcd.c + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2014 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * 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. + * + * Contact Information: + * Sean Stalley, sean.stal...@intel.com + * Stephanie Wallick, stephanie.s.wall...@intel.com + * 2111 NE 25th Avenue + * Hillsboro, Oregon 97124 + * + * BSD LICENSE + * + * Copyright(c) 2014 Intel Corporation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Intel Corporation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __MAUSB_HCD_H +#define __MAUSB_HCD_H + +#include <linux/spinlock.h> + +#include "mausb_hub.h" +#include "mausb_pkt.h" +#include "mausb_mem.h" +#include "mausb_const.h" + +#define MAUSB_NAME "mausb" + +#define mausb_info(mhcd, fmt, args...) \ + dev_info(mausb_hcd_to_usb_hcd(mhcd)->self.controller , fmt , ## args) +#define mausb_dbg(mhcd, fmt, args...) \ + dev_dbg(mausb_hcd_to_usb_hcd(mhcd)->self.controller , fmt , ## args) +#define mausb_warn(mhcd, fmt, args...) \ + dev_warn(mausb_hcd_to_usb_hcd(mhcd)->self.controller , fmt , ## args) +#define mausb_err(mhcd, fmt, args...) \ + dev_err(mausb_hcd_to_usb_hcd(mhcd)->self.controller , fmt , ## args) + +/* Transfer directions */ +#define PIPE_DIR_OUT 0 +#define PIPE_DIR_IN 1 + +/* Host BOS parameters */ +#define MAUSB_HOST_NUM_DEV_CAPS 1 +#define MAUSB_HOST_U1_DEV_EXIT_LAT 0 +#define MAUSB_HOST_U2_DEV_EXIT_LAT 0 + +/* TEMPORARY: the mausb device will have this handle */ +#define MAUSB_DEV_ADDR 0x54 +#define MAUSB_MASS_ID 0x25 + +struct mausb_ms_drv; + +/** + * This structure holds the information set in a get bos descriptor request. + * + * @bos_des: Binary Object Store Descriptor, per USB2.0 Spec + * @ss_cap_des: SuperSpeed Capability Descriptor, per USB3.0 Spec + */ +struct __attribute__((__packed__)) mausb_host_bos { + struct usb_bos_descriptor bos_des; + struct usb_ss_cap_descriptor ss_cap_des; +}; + +/** + * This structure holds all of the MA USB-specific data. + * + * All data listed in this structure is protected by the hcd_lock. + * if you want to use any of this data, you need to be in possession + * of this lock. + * + * @root_hub: Contains all of the data for the USB 2.0 roothub. + * This includes status, state and descriptors. + * @shared_root_hub: Contains all of the data for the USB 3.0 roothub. + * This includes status, state and descriptors + * @host_bos: Stores the host's BOS descriptor + * @udev: Pointer to the usb device we are currently sending + * a packet to. + * @shared_hcd: The second host controller structure for the MAUSB + * driver. We need this because we do SuperSpeed, which + * requires 2 root hubs. + * @ma_dev: The connected MA USB device + * @ma_hcd_task: Task for handling urb queue + * @ma_hcd_task_data: Context for ma_hcd_task + * @waitq: Event trigger for dequeue + * @enqueue_urb_list: List of enqueued urbs from core + * @transfer_urb_list: List of urbs awaiting ma transfer + * @urb_list_lock: Lock for accessing urb lists + * @hcd_lock: Lock for accessing data in this structure. note that + * endpoints have their own locks. When accessing + * endpoint data, this lock should NOT be held + * @giveback_lock: spin lock to prevent preemption when give back an urb + * @resuming: Set to 1 when HCD is resuming + * @disabled: Set to 1 when hcd is disabled + */ +struct __attribute__((__packed__)) mausb_hcd { + struct mausb_root_hub root_hub; + struct mausb_root_hub shared_root_hub; + + struct mausb_host_bos host_bos; + + struct usb_device *udev; + + struct usb_hcd *usb_hcd; + struct usb_hcd *shared_hcd; + + struct ma_dev ma_dev; + struct task_struct *ma_hcd_task; + void *ma_hcd_task_data; + wait_queue_head_t waitq; + struct list_head enqueue_urb_list; + struct list_head transfer_urb_list; + spinlock_t urb_list_lock; + spinlock_t hcd_lock; + spinlock_t giveback_lock; + unsigned int resuming:1; + unsigned int disabled:1; +}; + +/* Pointer conversion functions */ +inline struct mausb_hcd *usb_hcd_to_mausb_hcd(struct usb_hcd *hcd); +inline struct usb_hcd *mausb_hcd_to_usb_hcd(struct mausb_hcd *mhcd); +inline struct device *mausb_hcd_to_dev(struct mausb_hcd *mhcd); +inline struct mausb_urb *usb_urb_to_mausb_urb(struct urb *urb); + +/* URB handling functions */ +void mausb_unlink_giveback_urb(struct mausb_urb *maurb, int status); + +/* Connection event function */ +int mausb_device_connect(int); + +#endif -- 1.9.1 _______________________________________________ devel mailing list de...@linuxdriverproject.org http://driverdev.linuxdriverproject.org/mailman/listinfo/driverdev-devel