From: lsun@mellanox.com (Liming Sun)
To: linux-arm-kernel@lists.infradead.org
Subject: [PATCH v6 7/9] soc: mellanox: host: Add the Rshim USB backend driver
Date: Thu, 1 Nov 2018 12:25:36 -0400 [thread overview]
Message-ID: <1541089538-175682-7-git-send-email-lsun@mellanox.com> (raw)
In-Reply-To: <1541089538-175682-1-git-send-email-lsun@mellanox.com>
This commit adds the USB backend driver to access the Rshim
interface on the BlueField SoC. It can be used when a USB cable
is connected to the Smart NIC or standalone device.
Reviewed-by: David Woods <dwoods@mellanox.com>
Signed-off-by: Liming Sun <lsun@mellanox.com>
---
drivers/soc/mellanox/host/Makefile | 2 +-
drivers/soc/mellanox/host/rshim_usb.c | 1035 +++++++++++++++++++++++++++++++++
2 files changed, 1036 insertions(+), 1 deletion(-)
create mode 100644 drivers/soc/mellanox/host/rshim_usb.c
diff --git a/drivers/soc/mellanox/host/Makefile b/drivers/soc/mellanox/host/Makefile
index 1a282b9..c6703cd 100644
--- a/drivers/soc/mellanox/host/Makefile
+++ b/drivers/soc/mellanox/host/Makefile
@@ -1,2 +1,2 @@
-obj-m := rshim.o rshim_net.o
+obj-m := rshim.o rshim_net.o rshim_usb.o
diff --git a/drivers/soc/mellanox/host/rshim_usb.c b/drivers/soc/mellanox/host/rshim_usb.c
new file mode 100644
index 0000000..aad6250
--- /dev/null
+++ b/drivers/soc/mellanox/host/rshim_usb.c
@@ -0,0 +1,1035 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rshim_usb.c - Mellanox RShim USB host driver
+ *
+ * Copyright 2017 Mellanox Technologies. All Rights Reserved.
+ *
+ * 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, version 2.
+ *
+ * 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, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for
+ * more details.
+ */
+
+/*
+ * This source code was originally derived from:
+ *
+ * USB Skeleton driver - 2.0
+ *
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg at kroah.com)
+ *
+ * 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, version 2.
+ *
+ * Some code was also lifted from the example drivers in "Linux Device
+ * Drivers" by Alessandro Rubini and Jonathan Corbet, published by
+ * O'Reilly & Associates.
+ */
+
+#include <linux/kernel.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/kref.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/version.h>
+#include <linux/uaccess.h>
+#include <linux/ioctl.h>
+#include <linux/termios.h>
+#include <linux/workqueue.h>
+#include <asm/termbits.h>
+#include <linux/circ_buf.h>
+
+#include "rshim.h"
+
+/* Disable RSim access. */
+static int rshim_disable;
+module_param(rshim_disable, int, 0444);
+MODULE_PARM_DESC(rshim_disable, "Disable rshim (obsoleted)");
+
+/* Our USB vendor/product IDs. */
+#define USB_TILERA_VENDOR_ID 0x22dc /* Tilera Corporation */
+#define USB_BLUEFIELD_PRODUCT_ID 0x0004 /* Mellanox Bluefield */
+
+/* Number of retries for the tmfifo read/write path. */
+#define READ_RETRIES 5
+#define WRITE_RETRIES 5
+
+/* Structure to hold all of our device specific stuff. */
+struct rshim_usb {
+ /* RShim backend structure. */
+ struct rshim_backend bd;
+
+ /*
+ * The USB device for this device. We bump its reference count
+ * when the first interface is probed, and drop the ref when the
+ * last interface is disconnected.
+ */
+ struct usb_device *udev;
+
+ /* The USB interfaces for this device. */
+ struct usb_interface *rshim_interface;
+
+ /* State for our outstanding boot write. */
+ struct urb *boot_urb;
+
+ /* Control data. */
+ u64 ctrl_data;
+
+ /* Interrupt data buffer. This is a USB DMA'able buffer. */
+ u64 *intr_buf;
+ dma_addr_t intr_buf_dma;
+
+ /* Read/interrupt urb, retries, and mode. */
+ struct urb *read_or_intr_urb;
+ int read_or_intr_retries;
+ int read_urb_is_intr;
+
+ /* Write urb and retries. */
+ struct urb *write_urb;
+ int write_retries;
+
+ /* The address of the boot FIFO endpoint. */
+ u8 boot_fifo_ep;
+ /* The address of the tile-monitor FIFO interrupt endpoint. */
+ u8 tm_fifo_int_ep;
+ /* The address of the tile-monitor FIFO input endpoint. */
+ u8 tm_fifo_in_ep;
+ /* The address of the tile-monitor FIFO output endpoint. */
+ u8 tm_fifo_out_ep;
+};
+
+/* Table of devices that work with this driver */
+static struct usb_device_id rshim_usb_table[] = {
+ { USB_DEVICE(USB_TILERA_VENDOR_ID, USB_BLUEFIELD_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, rshim_usb_table);
+
+/* Random compatibility hacks. */
+
+/* Arguments to an urb completion handler. */
+#define URB_COMP_ARGS struct urb *urb
+
+static void rshim_usb_delete(struct kref *kref)
+{
+ struct rshim_backend *bd;
+ struct rshim_usb *dev;
+
+ bd = container_of(kref, struct rshim_backend, kref);
+ dev = container_of(bd, struct rshim_usb, bd);
+
+ rshim_deregister(bd);
+ kfree(dev);
+}
+
+/* Rshim read/write routines */
+
+static int rshim_usb_read_rshim(struct rshim_backend *bd, int chan, int addr,
+ u64 *result)
+{
+ struct rshim_usb *dev = container_of(bd, struct rshim_usb, bd);
+ int retval;
+
+ if (!bd->has_rshim)
+ return -ENODEV;
+
+ /* Do a blocking control read and endian conversion. */
+ retval = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
+ 0, /* request */
+ USB_RECIP_ENDPOINT | USB_TYPE_VENDOR |
+ USB_DIR_IN, /* request type */
+ chan, /* value */
+ addr, /* index */
+ &dev->ctrl_data, 8, 2000);
+
+ /*
+ * The RShim HW puts bytes on the wire in little-endian order
+ * regardless of endianness settings either in the host or the ARM
+ * cores.
+ */
+ *result = le64_to_cpu(dev->ctrl_data);
+ if (retval == 8)
+ return 0;
+
+ /*
+ * These are weird error codes, but we want to use something
+ * the USB stack doesn't use so that we can identify short/long
+ * reads.
+ */
+ return retval >= 0 ? (retval > 8 ? -EBADE : -EBADR) : retval;
+}
+
+static int rshim_usb_write_rshim(struct rshim_backend *bd, int chan, int addr,
+ u64 value)
+{
+ struct rshim_usb *dev = container_of(bd, struct rshim_usb, bd);
+ int retval;
+
+ if (!bd->has_rshim)
+ return -ENODEV;
+
+ /* Convert the word to little endian and do blocking control write. */
+ dev->ctrl_data = cpu_to_le64(value);
+ retval = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
+ 0, /* request */
+ USB_RECIP_ENDPOINT | USB_TYPE_VENDOR |
+ USB_DIR_OUT, /* request type */
+ chan, /* value */
+ addr, /* index */
+ &dev->ctrl_data, 8, 2000);
+
+ if (retval == 8)
+ return 0;
+
+ /*
+ * These are weird error codes, but we want to use something
+ * the USB stack doesn't use so that we can identify short/long
+ * writes.
+ */
+ return retval >= 0 ? (retval > 8 ? -EBADE : -EBADR) : retval;
+}
+
+/* Boot routines */
+
+static void rshim_usb_boot_write_callback(URB_COMP_ARGS)
+{
+ struct rshim_usb *dev = urb->context;
+
+ if (urb->status == -ENOENT)
+ pr_debug("boot tx canceled, actual length %d\n",
+ urb->actual_length);
+ else if (urb->status)
+ pr_debug("boot tx failed, status %d, actual length %d\n",
+ urb->status, urb->actual_length);
+
+ complete_all(&dev->bd.boot_write_complete);
+}
+
+static ssize_t rshim_usb_boot_write(struct rshim_usb *dev, const char *buf,
+ size_t count)
+{
+ struct rshim_backend *bd = &dev->bd;
+ int retval = 0;
+ size_t bytes_written = 0;
+
+ /* Create and fill an urb */
+ dev->boot_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (unlikely(!dev->boot_urb)) {
+ pr_debug("boot_write: couldn't allocate urb\n");
+ return -ENOMEM;
+ }
+ usb_fill_bulk_urb(dev->boot_urb, dev->udev,
+ usb_sndbulkpipe(dev->udev, dev->boot_fifo_ep),
+ (char *)buf, count, rshim_usb_boot_write_callback,
+ dev);
+
+ /* Submit the urb. */
+ reinit_completion(&bd->boot_write_complete);
+ retval = usb_submit_urb(dev->boot_urb, GFP_KERNEL);
+ if (retval)
+ goto done;
+
+ /*
+ * Wait until it's done. If anything goes wrong in the USB layer,
+ * the callback function might never get called and cause stuck.
+ * Here we release the mutex so user could use 'ctrl + c' to terminate
+ * the current write. Once the boot file is opened again, the
+ * outstanding urb will be canceled.
+ *
+ * Note: when boot stream starts to write, it will either run to
+ * completion, or be interrupted by user. The urb callback function will
+ * be called during this period. There are no other operations to affect
+ * the boot stream. So unlocking the mutex is considered safe.
+ */
+ mutex_unlock(&bd->mutex);
+ retval = wait_for_completion_interruptible(&bd->boot_write_complete);
+ mutex_lock(&bd->mutex);
+ if (retval) {
+ usb_kill_urb(dev->boot_urb);
+ bytes_written += dev->boot_urb->actual_length;
+ goto done;
+ }
+
+ if (dev->boot_urb->actual_length !=
+ dev->boot_urb->transfer_buffer_length) {
+ pr_debug("length mismatch, exp %d act %d stat %d\n",
+ dev->boot_urb->transfer_buffer_length,
+ dev->boot_urb->actual_length,
+ dev->boot_urb->status);
+ }
+
+#ifdef RSH_USB_BMC
+ /*
+ * The UHCI host controller on the BMC seems to
+ * overestimate the amount of data it's
+ * successfully sent when it sees a babble error.
+ */
+ if (dev->boot_urb->status == -EOVERFLOW &&
+ dev->boot_urb->actual_length >= 64) {
+ dev->boot_urb->actual_length -= 64;
+ pr_debug("saw babble, new length %d\n",
+ dev->boot_urb->actual_length);
+ }
+#endif
+
+ bytes_written = dev->boot_urb->actual_length;
+
+ if (dev->boot_urb->status == -ENOENT &&
+ dev->boot_urb->transfer_buffer_length !=
+ dev->boot_urb->actual_length) {
+ pr_debug("boot_write: urb canceled.\n");
+ } else {
+ if (dev->boot_urb->status) {
+ pr_debug("boot_write: urb failed, status %d\n",
+ dev->boot_urb->status);
+ }
+ if (dev->boot_urb->status != -ENOENT && !retval)
+ retval = dev->boot_urb->status;
+ }
+
+done:
+ usb_free_urb(dev->boot_urb);
+ dev->boot_urb = NULL;
+
+ return bytes_written ? bytes_written : retval;
+}
+
+/* FIFO routines */
+
+static void rshim_usb_fifo_read_callback(URB_COMP_ARGS)
+{
+ struct rshim_usb *dev = urb->context;
+ struct rshim_backend *bd = &dev->bd;
+
+ spin_lock(&bd->spinlock);
+
+ pr_debug("usb_fifo_read_callback: %s urb completed, status %d, "
+ "actual length %d, intr buf %d\n",
+ dev->read_urb_is_intr ? "interrupt" : "read",
+ urb->status, urb->actual_length, (int) *dev->intr_buf);
+
+ bd->spin_flags &= ~RSH_SFLG_READING;
+
+ if (urb->status == 0) {
+ /*
+ * If a read completed, clear the number of bytes available
+ * from the last interrupt, and set up the new buffer for
+ * processing. (If an interrupt completed, there's nothing
+ * to do, since the number of bytes available was already
+ * set by the I/O itself.)
+ */
+ if (!dev->read_urb_is_intr) {
+ *dev->intr_buf = 0;
+ bd->read_buf_bytes = urb->actual_length;
+ bd->read_buf_next = 0;
+ }
+
+ /* Process any data we got, and launch another I/O if needed. */
+ rshim_notify(bd, RSH_EVENT_FIFO_INPUT, 0);
+ } else if (urb->status == -ENOENT) {
+ /*
+ * The urb was explicitly cancelled. The only time we
+ * currently do this is when we close the stream. If we
+ * mark this as an error, tile-monitor --resume won't work,
+ * so we just want to do nothing.
+ */
+ } else if (urb->status == -ECONNRESET ||
+ urb->status == -ESHUTDOWN) {
+ /*
+ * The device went away. We don't want to retry this, and
+ * we expect things to get better, probably after a device
+ * reset, but in the meantime, we should let upper layers
+ * know there was a problem.
+ */
+ rshim_notify(bd, RSH_EVENT_FIFO_ERR, urb->status);
+ } else if (dev->read_or_intr_retries < READ_RETRIES &&
+ urb->actual_length == 0 &&
+ (urb->status == -EPROTO || urb->status == -EILSEQ ||
+ urb->status == -EOVERFLOW)) {
+ /*
+ * We got an error which could benefit from being retried.
+ * Just submit the same urb again. Note that we don't
+ * handle partial reads; it's hard, and we haven't really
+ * seen them.
+ */
+ int retval;
+
+ dev->read_or_intr_retries++;
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval) {
+ pr_debug("fifo_read_callback: resubmitted urb but got error %d",
+ retval);
+ /*
+ * In this case, we won't try again; signal the
+ * error to upper layers.
+ */
+ rshim_notify(bd, RSH_EVENT_FIFO_ERR, retval);
+ } else {
+ bd->spin_flags |= RSH_SFLG_READING;
+ }
+ } else {
+ /*
+ * We got some error we don't know how to handle, or we got
+ * too many errors. Either way we don't retry any more,
+ * but we signal the error to upper layers.
+ */
+ pr_err("fifo_read_callback: %s urb completed abnormally, "
+ "error %d\n",
+ dev->read_urb_is_intr ? "interrupt" : "read",
+ urb->status);
+ rshim_notify(bd, RSH_EVENT_FIFO_ERR, urb->status);
+ }
+
+ spin_unlock(&bd->spinlock);
+}
+
+static void rshim_usb_fifo_read(struct rshim_usb *dev, char *buffer,
+ size_t count)
+{
+ struct rshim_backend *bd = &dev->bd;
+
+ if ((int) *dev->intr_buf || bd->read_buf_bytes) {
+ /* We're doing a read. */
+
+ int retval;
+ struct urb *urb = dev->read_or_intr_urb;
+
+ usb_fill_bulk_urb(urb, dev->udev,
+ usb_rcvbulkpipe(dev->udev,
+ dev->tm_fifo_in_ep),
+ buffer, count,
+ rshim_usb_fifo_read_callback,
+ dev);
+ urb->transfer_dma = dev->bd.read_buf_dma;
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ dev->bd.spin_flags |= RSH_SFLG_READING;
+ dev->read_urb_is_intr = 0;
+ dev->read_or_intr_retries = 0;
+
+ /* Submit the urb. */
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval) {
+ dev->bd.spin_flags &= ~RSH_SFLG_READING;
+ pr_debug("fifo_drain: failed submitting read "
+ "urb, error %d", retval);
+ }
+ pr_debug("fifo_read_callback: resubmitted read urb\n");
+ } else {
+ /* We're doing an interrupt. */
+
+ int retval;
+ struct urb *urb = dev->read_or_intr_urb;
+
+ usb_fill_int_urb(urb, dev->udev,
+ usb_rcvintpipe(dev->udev, dev->tm_fifo_int_ep),
+ dev->intr_buf, sizeof(*dev->intr_buf),
+ rshim_usb_fifo_read_callback,
+ /*
+ * FIXME: is 6 a good interval value? That's
+ * polling at 8000/(1 << 6) == 125 Hz.
+ */
+ dev, 6);
+ urb->transfer_dma = dev->intr_buf_dma;
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ dev->bd.spin_flags |= RSH_SFLG_READING;
+ dev->read_urb_is_intr = 1;
+ dev->read_or_intr_retries = 0;
+
+ /* Submit the urb */
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval) {
+ dev->bd.spin_flags &= ~RSH_SFLG_READING;
+ pr_debug("fifo_read_callback: failed submitting "
+ "interrupt urb, error %d", retval);
+ }
+ pr_debug("fifo_read_callback: resubmitted interrupt urb\n");
+ }
+}
+
+static void rshim_usb_fifo_write_callback(URB_COMP_ARGS)
+{
+ struct rshim_usb *dev = urb->context;
+ struct rshim_backend *bd = &dev->bd;
+
+ spin_lock(&bd->spinlock);
+
+ pr_debug("fifo_write_callback: urb completed, status %d, "
+ "actual length %d, intr buf %d\n",
+ urb->status, urb->actual_length, (int) *dev->intr_buf);
+
+ bd->spin_flags &= ~RSH_SFLG_WRITING;
+
+ if (urb->status == 0) {
+ /* A write completed. */
+ wake_up_interruptible_all(&bd->write_completed);
+ rshim_notify(bd, RSH_EVENT_FIFO_OUTPUT, 0);
+ } else if (urb->status == -ENOENT) {
+ /*
+ * The urb was explicitly cancelled. The only time we
+ * currently do this is when we close the stream. If we
+ * mark this as an error, tile-monitor --resume won't work,
+ * so we just want to do nothing.
+ */
+ } else if (urb->status == -ECONNRESET ||
+ urb->status == -ESHUTDOWN) {
+ /*
+ * The device went away. We don't want to retry this, and
+ * we expect things to get better, probably after a device
+ * reset, but in the meantime, we should let upper layers
+ * know there was a problem.
+ */
+ rshim_notify(bd, RSH_EVENT_FIFO_ERR, urb->status);
+ } else if (dev->write_retries < WRITE_RETRIES &&
+ urb->actual_length == 0 &&
+ (urb->status == -EPROTO || urb->status == -EILSEQ ||
+ urb->status == -EOVERFLOW)) {
+ /*
+ * We got an error which could benefit from being retried.
+ * Just submit the same urb again. Note that we don't
+ * handle partial writes; it's hard, and we haven't really
+ * seen them.
+ */
+ int retval;
+
+ dev->write_retries++;
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval) {
+ pr_err("fifo_write_callback: resubmitted urb but "
+ "got error %d\n", retval);
+ /*
+ * In this case, we won't try again; signal the
+ * error to upper layers.
+ */
+ rshim_notify(bd, RSH_EVENT_FIFO_ERR, retval);
+ } else {
+ bd->spin_flags |= RSH_SFLG_WRITING;
+ }
+ } else {
+ /*
+ * We got some error we don't know how to handle, or we got
+ * too many errors. Either way we don't retry any more,
+ * but we signal the error to upper layers.
+ */
+ pr_err("fifo_write_callback: urb completed abnormally, "
+ "error %d\n", urb->status);
+ rshim_notify(bd, RSH_EVENT_FIFO_ERR, urb->status);
+ }
+
+ spin_unlock(&bd->spinlock);
+}
+
+static int rshim_usb_fifo_write(struct rshim_usb *dev, const char *buffer,
+ size_t count)
+{
+ struct rshim_backend *bd = &dev->bd;
+ int retval;
+
+ WARN_ONCE(count % 8 != 0, "rshim write %d is not multiple of 8 bytes\n",
+ (int)count);
+
+ /* Initialize the urb properly. */
+ usb_fill_bulk_urb(dev->write_urb, dev->udev,
+ usb_sndbulkpipe(dev->udev,
+ dev->tm_fifo_out_ep),
+ (char *)buffer,
+ count,
+ rshim_usb_fifo_write_callback,
+ dev);
+ dev->write_urb->transfer_dma = bd->write_buf_dma;
+ dev->write_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ dev->write_retries = 0;
+
+ /* Send the data out the bulk port. */
+ retval = usb_submit_urb(dev->write_urb, GFP_ATOMIC);
+ if (retval) {
+ bd->spin_flags &= ~RSH_SFLG_WRITING;
+ pr_err("fifo_write: failed submitting write "
+ "urb, error %d\n", retval);
+ return -1;
+ }
+
+ bd->spin_flags |= RSH_SFLG_WRITING;
+ return 0;
+}
+
+/* Probe routines */
+
+/* These make the endpoint test code in rshim_usb_probe() a lot cleaner. */
+#define is_in_ep(ep) (((ep)->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == \
+ USB_DIR_IN)
+#define is_bulk_ep(ep) (((ep)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == \
+ USB_ENDPOINT_XFER_BULK)
+#define is_int_ep(ep) (((ep)->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == \
+ USB_ENDPOINT_XFER_INT)
+#define max_pkt(ep) le16_to_cpu(ep->wMaxPacketSize)
+#define ep_addr(ep) (ep->bEndpointAddress)
+
+static ssize_t rshim_usb_backend_read(struct rshim_backend *bd, int devtype,
+ char *buf, size_t count)
+{
+ struct rshim_usb *dev = container_of(bd, struct rshim_usb, bd);
+
+ switch (devtype) {
+ case RSH_DEV_TYPE_NET:
+ case RSH_DEV_TYPE_CONSOLE:
+ rshim_usb_fifo_read(dev, buf, count);
+ return 0;
+
+ default:
+ pr_err("bad devtype %d\n", devtype);
+ return -EINVAL;
+ }
+}
+
+static ssize_t rshim_usb_backend_write(struct rshim_backend *bd, int devtype,
+ const char *buf, size_t count)
+{
+ struct rshim_usb *dev = container_of(bd, struct rshim_usb, bd);
+
+ switch (devtype) {
+ case RSH_DEV_TYPE_NET:
+ case RSH_DEV_TYPE_CONSOLE:
+ return rshim_usb_fifo_write(dev, buf, count);
+
+ case RSH_DEV_TYPE_BOOT:
+ return rshim_usb_boot_write(dev, buf, count);
+
+ default:
+ pr_err("bad devtype %d\n", devtype);
+ return -EINVAL;
+ }
+}
+
+static void rshim_usb_backend_cancel_req(struct rshim_backend *bd, int devtype,
+ bool is_write)
+{
+ struct rshim_usb *dev = container_of(bd, struct rshim_usb, bd);
+
+ switch (devtype) {
+ case RSH_DEV_TYPE_NET:
+ case RSH_DEV_TYPE_CONSOLE:
+ if (is_write)
+ usb_kill_urb(dev->write_urb);
+ else
+ usb_kill_urb(dev->read_or_intr_urb);
+ break;
+
+ case RSH_DEV_TYPE_BOOT:
+ usb_kill_urb(dev->boot_urb);
+ break;
+
+ default:
+ pr_err("bad devtype %d\n", devtype);
+ break;
+ }
+}
+
+static int rshim_usb_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ char *usb_dev_name;
+ int dev_name_len = 32;
+ struct rshim_usb *dev = NULL;
+ struct rshim_backend *bd;
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *ep;
+ int i;
+ int allocfail = 0;
+ int retval = -ENOMEM;
+
+ /*
+ * Get our device pathname. The usb_make_path interface uselessly
+ * returns -1 if the output buffer is too small, instead of telling
+ * us how big it needs to be, so we just start with a reasonable
+ * size and double it until the name fits.
+ */
+ while (1) {
+ usb_dev_name = kmalloc(dev_name_len, GFP_KERNEL);
+ if (!usb_dev_name)
+ goto error;
+ if (usb_make_path(interface_to_usbdev(interface), usb_dev_name,
+ dev_name_len) >= 0)
+ break;
+ kfree(usb_dev_name);
+ dev_name_len *= 2;
+ }
+
+ pr_debug("probing %s\n", usb_dev_name);
+
+ /*
+ * Now see if we've previously seen this device. If so, we use the
+ * same device number, otherwise we pick the first available one.
+ */
+ rshim_lock();
+
+ /* Find the backend. */
+ bd = rshim_find(usb_dev_name);
+ if (bd) {
+ pr_debug("found previously allocated rshim_usb structure\n");
+ kref_get(&bd->kref);
+ dev = container_of(bd, struct rshim_usb, bd);
+ kfree(usb_dev_name);
+ usb_dev_name = NULL;
+ } else {
+ pr_debug("creating new rshim_usb structure\n");
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (dev == NULL) {
+ pr_err("couldn't get memory for new device\n");
+ rshim_unlock();
+ goto error;
+ }
+
+ bd = &dev->bd;
+ bd->dev_name = usb_dev_name;
+ bd->read = rshim_usb_backend_read;
+ bd->write = rshim_usb_backend_write;
+ bd->cancel = rshim_usb_backend_cancel_req;
+ bd->destroy = rshim_usb_delete;
+ bd->read_rshim = rshim_usb_read_rshim;
+ bd->write_rshim = rshim_usb_write_rshim;
+ bd->has_reprobe = 1;
+ bd->owner = THIS_MODULE;
+ mutex_init(&bd->mutex);
+ }
+
+ /*
+ * This has to be done on the first probe, whether or not we
+ * allocated a new rshim_usb structure, since it's always dropped
+ * on the second disconnect.
+ */
+ if (!bd->has_rshim && !bd->has_tm)
+ dev->udev = usb_get_dev(interface_to_usbdev(interface));
+
+ /*
+ * It would seem more logical to allocate these above when we create
+ * a new rshim_usb structure, but we don't want to do it until we've
+ * upped the usb device reference count.
+ */
+ allocfail |= rshim_fifo_alloc(bd);
+
+ if (!bd->read_buf)
+ bd->read_buf = usb_alloc_coherent(dev->udev, READ_BUF_SIZE,
+ GFP_KERNEL,
+ &bd->read_buf_dma);
+ allocfail |= bd->read_buf == 0;
+
+ if (!dev->intr_buf) {
+ dev->intr_buf = usb_alloc_coherent(dev->udev,
+ sizeof(*dev->intr_buf),
+ GFP_KERNEL,
+ &dev->intr_buf_dma);
+ if (dev->intr_buf != NULL)
+ *dev->intr_buf = 0;
+ }
+ allocfail |= dev->intr_buf == 0;
+
+ if (!bd->write_buf) {
+ bd->write_buf = usb_alloc_coherent(dev->udev,
+ WRITE_BUF_SIZE,
+ GFP_KERNEL,
+ &bd->write_buf_dma);
+ }
+ allocfail |= bd->write_buf == 0;
+
+ if (!dev->read_or_intr_urb)
+ dev->read_or_intr_urb = usb_alloc_urb(0, GFP_KERNEL);
+ allocfail |= dev->read_or_intr_urb == 0;
+
+ if (!dev->write_urb)
+ dev->write_urb = usb_alloc_urb(0, GFP_KERNEL);
+ allocfail |= dev->write_urb == 0;
+
+ if (allocfail) {
+ pr_err("can't allocate buffers or urbs\n");
+ rshim_unlock();
+ goto error;
+ }
+
+ rshim_unlock();
+
+ iface_desc = interface->cur_altsetting;
+
+ /* Make sure this is a vendor-specific interface class. */
+ if (iface_desc->desc.bInterfaceClass != 0xFF)
+ goto error;
+
+ /* See which interface this is, then save the correct data. */
+
+ mutex_lock(&bd->mutex);
+ if (iface_desc->desc.bInterfaceSubClass == 0) {
+ pr_debug("found rshim interface\n");
+ /*
+ * We only expect one endpoint here, just make sure its
+ * attributes match.
+ */
+ if (iface_desc->desc.bNumEndpoints != 1) {
+ pr_err("wrong number of endpoints for rshim "
+ "interface\n");
+ mutex_unlock(&bd->mutex);
+ goto error;
+ }
+ ep = &iface_desc->endpoint[0].desc;
+
+ /* We expect a bulk out endpoint. */
+ if (!is_bulk_ep(ep) || is_in_ep(ep)) {
+ mutex_unlock(&bd->mutex);
+ goto error;
+ }
+
+ bd->has_rshim = 1;
+ dev->rshim_interface = interface;
+ dev->boot_fifo_ep = ep_addr(ep);
+
+ } else if (iface_desc->desc.bInterfaceSubClass == 1) {
+ pr_debug("found tmfifo interface\n");
+ /*
+ * We expect 3 endpoints here. Since they're listed in
+ * random order we have to use their attributes to figure
+ * out which is which.
+ */
+ if (iface_desc->desc.bNumEndpoints != 3) {
+ pr_err("wrong number of endpoints for tm "
+ "interface\n");
+ mutex_unlock(&bd->mutex);
+ goto error;
+ }
+ dev->tm_fifo_in_ep = 0;
+ dev->tm_fifo_int_ep = 0;
+ dev->tm_fifo_out_ep = 0;
+
+ for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
+ ep = &iface_desc->endpoint[i].desc;
+
+ if (is_in_ep(ep)) {
+ if (is_bulk_ep(ep)) {
+ /* Bulk in endpoint. */
+ dev->tm_fifo_in_ep = ep_addr(ep);
+ } else if (is_int_ep(ep)) {
+ /* Interrupt in endpoint. */
+ dev->tm_fifo_int_ep = ep_addr(ep);
+ }
+ } else {
+ if (is_bulk_ep(ep)) {
+ /* Bulk out endpoint. */
+ dev->tm_fifo_out_ep = ep_addr(ep);
+ }
+ }
+ }
+
+ if (!dev->tm_fifo_in_ep || !dev->tm_fifo_int_ep ||
+ !dev->tm_fifo_out_ep) {
+ pr_err("could not find all required endpoints for "
+ "tm interface\n");
+ mutex_unlock(&bd->mutex);
+ goto error;
+ }
+ bd->has_tm = 1;
+ } else {
+ mutex_unlock(&bd->mutex);
+ goto error;
+ }
+
+ /* Save our data pointer in this interface device. */
+ usb_set_intfdata(interface, dev);
+
+ if (!bd->dev)
+ bd->dev = &dev->udev->dev;
+
+ /*
+ * Register rshim here since it needs to detect whether other backend
+ * has already registered or not, which involves reading/writing rshim
+ * registers and has assumption that the under layer is working.
+ */
+ rshim_lock();
+ if (!bd->registered) {
+ retval = rshim_register(bd);
+ if (retval) {
+ rshim_unlock();
+ goto error;
+ }
+ }
+ rshim_unlock();
+
+ /* Notify that device is attached. */
+ retval = rshim_notify(&dev->bd, RSH_EVENT_ATTACH, 0);
+ mutex_unlock(&dev->bd.mutex);
+ if (retval)
+ goto error;
+
+ return 0;
+
+error:
+ if (dev) {
+ usb_free_urb(dev->read_or_intr_urb);
+ dev->read_or_intr_urb = NULL;
+ usb_free_urb(dev->write_urb);
+ dev->write_urb = NULL;
+
+ usb_free_coherent(dev->udev, READ_BUF_SIZE,
+ dev->bd.read_buf, dev->bd.read_buf_dma);
+ dev->bd.read_buf = NULL;
+
+ usb_free_coherent(dev->udev, WRITE_BUF_SIZE,
+ dev->bd.write_buf, dev->bd.write_buf_dma);
+ dev->bd.write_buf = NULL;
+
+ rshim_fifo_free(&dev->bd);
+
+ usb_free_coherent(dev->udev, sizeof(*dev->intr_buf),
+ dev->intr_buf, dev->intr_buf_dma);
+ dev->intr_buf = NULL;
+
+ rshim_lock();
+ kref_put(&dev->bd.kref, rshim_usb_delete);
+ rshim_unlock();
+ }
+
+ kfree(usb_dev_name);
+ return retval;
+}
+
+static void rshim_usb_disconnect(struct usb_interface *interface)
+{
+ struct rshim_usb *dev;
+ struct rshim_backend *bd;
+ int flush_wq = 0;
+
+ dev = usb_get_intfdata(interface);
+ bd = &dev->bd;
+ usb_set_intfdata(interface, NULL);
+
+ rshim_notify(bd, RSH_EVENT_DETACH, 0);
+
+ /*
+ * Clear this interface so we don't unregister our devices next
+ * time.
+ */
+ mutex_lock(&bd->mutex);
+
+ if (dev->rshim_interface == interface) {
+ bd->has_rshim = 0;
+ dev->rshim_interface = NULL;
+ } else {
+ /*
+ * We have to get rid of any USB state, since it may be
+ * tied to the USB device which is going to vanish as soon
+ * as we get both disconnects. We'll reallocate these
+ * on the next probe.
+ *
+ * Supposedly the code which called us already killed any
+ * outstanding URBs, but it doesn't hurt to be sure.
+ */
+
+ /*
+ * We must make sure the console worker isn't running
+ * before we free all these resources, and particularly
+ * before we decrement our usage count, below. Most of the
+ * time, if it's even enabled, it'll be scheduled to run at
+ * some point in the future, and we can take care of that
+ * by asking that it be canceled.
+ *
+ * However, it's possible that it's already started
+ * running, but can't make progress because it's waiting
+ * for the device mutex, which we currently have. We
+ * handle this case by clearing the bit that says it's
+ * enabled. The worker tests this bit as soon as it gets
+ * the mutex, and if it's clear, it just returns without
+ * rescheduling itself. Note that if we didn't
+ * successfully cancel it, we flush the work entry below,
+ * after we drop the mutex, to be sure it's done before we
+ * decrement the device usage count.
+ *
+ * XXX This might be racy; what if something else which
+ * would enable the worker runs after we drop the mutex
+ * but before the worker itself runs?
+ */
+ flush_wq = !cancel_delayed_work(&bd->work);
+ bd->has_cons_work = 0;
+
+ usb_kill_urb(dev->read_or_intr_urb);
+ usb_free_urb(dev->read_or_intr_urb);
+ dev->read_or_intr_urb = NULL;
+ usb_kill_urb(dev->write_urb);
+ usb_free_urb(dev->write_urb);
+ dev->write_urb = NULL;
+
+ usb_free_coherent(dev->udev, READ_BUF_SIZE,
+ bd->read_buf, bd->read_buf_dma);
+ bd->read_buf = NULL;
+
+ usb_free_coherent(dev->udev, sizeof(*dev->intr_buf),
+ dev->intr_buf, dev->intr_buf_dma);
+ dev->intr_buf = NULL;
+
+ usb_free_coherent(dev->udev, WRITE_BUF_SIZE,
+ bd->write_buf, bd->write_buf_dma);
+ bd->write_buf = NULL;
+
+ rshim_fifo_free(bd);
+ }
+
+ if (!bd->has_rshim && !bd->has_tm) {
+ usb_put_dev(dev->udev);
+ dev->udev = NULL;
+ pr_info("now disconnected\n");
+ } else {
+ pr_debug("partially disconnected\n");
+ }
+
+ mutex_unlock(&bd->mutex);
+
+ /* This can't be done while we hold the mutex; see comments above. */
+ if (flush_wq)
+ flush_workqueue(rshim_wq);
+
+ /* decrement our usage count */
+ rshim_lock();
+ kref_put(&bd->kref, rshim_usb_delete);
+ rshim_unlock();
+}
+
+static struct usb_driver rshim_usb_driver = {
+ .name = "rshim_usb",
+ .probe = rshim_usb_probe,
+ .disconnect = rshim_usb_disconnect,
+ .id_table = rshim_usb_table,
+};
+
+static int __init rshim_usb_init(void)
+{
+ int result;
+
+ /* Register this driver with the USB subsystem. */
+ result = usb_register(&rshim_usb_driver);
+ if (result)
+ pr_err("usb_register failed, error number %d\n", result);
+
+ return result;
+}
+
+static void __exit rshim_usb_exit(void)
+{
+ /* Deregister this driver with the USB subsystem. */
+ usb_deregister(&rshim_usb_driver);
+}
+
+module_init(rshim_usb_init);
+module_exit(rshim_usb_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mellanox Technologies");
+MODULE_VERSION("0.6");
--
1.8.3.1
next prev parent reply other threads:[~2018-11-01 16:25 UTC|newest]
Thread overview: 70+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-05-25 16:06 [PATCH v1 1/4] soc: Add TmFifo driver for Mellanox BlueField Soc Liming Sun
2018-05-25 16:06 ` [PATCH v1 2/4] arm64: Add Mellanox BlueField SoC config option Liming Sun
2018-05-25 16:06 ` [PATCH v1 3/4] dt-bindings: soc: Add TmFifo binding for Mellanox BlueField SoC Liming Sun
2018-05-25 16:06 ` [PATCH v1 4/4] MAINTAINERS: Add entry for Mellanox Bluefield Soc Liming Sun
2018-05-25 17:14 ` [PATCH v1 1/4] soc: Add TmFifo driver for Mellanox BlueField Soc Robin Murphy
2018-05-25 20:18 ` Liming Sun
2018-05-25 20:17 ` [PATCH v2 " Liming Sun
2018-05-25 20:17 ` [PATCH v2 2/4] arm64: Add Mellanox BlueField SoC config option Liming Sun
2018-05-25 20:17 ` [PATCH v2 3/4] dt-bindings: soc: Add TmFifo binding for Mellanox BlueField SoC Liming Sun
2018-05-31 3:43 ` Rob Herring
2018-06-01 14:31 ` Liming Sun
2018-05-25 20:17 ` [PATCH v2 4/4] MAINTAINERS: Add entry for Mellanox Bluefield Soc Liming Sun
2018-06-01 14:31 ` [PATCH v3 1/4] soc: Add TmFifo driver for Mellanox BlueField Soc Liming Sun
2018-06-01 14:31 ` [PATCH v3 2/4] arm64: Add Mellanox BlueField SoC config option Liming Sun
2018-06-01 14:31 ` [PATCH v3 3/4] dt-bindings: soc: Add TmFifo binding for Mellanox BlueField SoC Liming Sun
2018-06-11 18:19 ` Rob Herring
2018-06-01 14:31 ` [PATCH v3 4/4] MAINTAINERS: Add entry for Mellanox Bluefield Soc Liming Sun
2018-10-24 17:55 ` [PATCH v4 1/4] soc: Add TmFifo driver for Mellanox BlueField Soc Liming Sun
2018-10-24 17:55 ` [PATCH v4 2/4] arm64: Add Mellanox BlueField SoC config option Liming Sun
2018-10-25 15:38 ` Arnd Bergmann
2018-10-26 19:18 ` Liming Sun
2018-10-26 19:32 ` Arnd Bergmann
2018-10-29 14:58 ` Liming Sun
2018-10-29 15:26 ` Arnd Bergmann
2018-10-29 16:09 ` Liming Sun
2018-10-24 17:55 ` [PATCH v4 3/4] dt-bindings: soc: Add TmFifo binding for Mellanox BlueField SoC Liming Sun
2018-10-25 15:32 ` Arnd Bergmann
2018-10-26 19:36 ` Liming Sun
2018-10-26 20:33 ` Arnd Bergmann
2018-10-29 16:48 ` Liming Sun
2019-01-24 15:07 ` Liming Sun
2018-10-24 17:55 ` [PATCH v4 4/4] MAINTAINERS: Add entry for Mellanox Bluefield Soc Liming Sun
2018-10-25 15:57 ` [PATCH v4 1/4] soc: Add TmFifo driver for Mellanox BlueField Soc Arnd Bergmann
2018-10-26 18:24 ` Liming Sun
2018-10-26 18:35 ` Arnd Bergmann
2018-10-29 14:17 ` Liming Sun
2018-10-29 14:52 ` Arnd Bergmann
2018-12-04 22:12 ` Liming Sun
2018-10-31 18:09 ` [PATCH v5 1/5] " Liming Sun
2018-10-31 18:09 ` [PATCH v5 2/5] arm64: Add Mellanox BlueField SoC config option Liming Sun
2018-10-31 18:09 ` [PATCH v5 3/5] dt-bindings: soc: Add TmFifo binding for Mellanox BlueField SoC Liming Sun
2018-10-31 18:09 ` [PATCH v5 4/5] MAINTAINERS: Add entry for Mellanox Bluefield Soc Liming Sun
2018-11-01 16:23 ` [PATCH v6 1/9] soc: Add TmFifo driver for Mellanox BlueField Soc Liming Sun
2018-12-12 23:07 ` Matthias Brugger
2019-01-03 19:20 ` Liming Sun
2018-11-01 16:25 ` Liming Sun
2018-11-01 16:25 ` [PATCH v6 2/9] arm64: Add Mellanox BlueField SoC config option Liming Sun
2018-11-01 16:25 ` [PATCH v6 3/9] dt-bindings: soc: Add TmFifo binding for Mellanox BlueField SoC Liming Sun
2018-11-01 16:25 ` [PATCH v6 4/9] MAINTAINERS: Add entry for Mellanox Bluefield Soc Liming Sun
2018-11-01 16:25 ` [PATCH v6 5/9] soc: mellanox: host: Add the common host side Rshim driver Liming Sun
2019-01-18 16:02 ` Arnd Bergmann
2019-01-21 19:22 ` Liming Sun
2019-01-22 12:20 ` Vincent Whitchurch
[not found] ` <DB6PR05MB32235BE3E5EBCA60ABDEDB13A1980@DB6PR05MB3223.eurprd05.prod.outlook.com>
2019-01-22 13:36 ` Liming Sun
2018-11-01 16:25 ` [PATCH v6 6/9] soc: mellanox: host: Add networking support over Rshim Liming Sun
2018-11-01 16:25 ` Liming Sun [this message]
2018-11-01 16:25 ` [PATCH v6 8/9] soc: mellanox: host: Add the Rshim PCIe backend driver Liming Sun
2018-11-01 16:25 ` [PATCH v6 9/9] soc: mellanox: host: Add the Rshim PCIe live-fish " Liming Sun
2019-01-03 19:17 ` [PATCH v7 1/9] soc: Add TmFifo driver for Mellanox BlueField Soc Liming Sun
2019-03-15 13:18 ` Matthias Brugger
2019-01-03 19:17 ` [PATCH v7 2/9] arm64: Add Mellanox BlueField SoC config option Liming Sun
2019-01-03 19:17 ` [PATCH v7 3/9] dt-bindings: soc: Add TmFifo binding for Mellanox BlueField SoC Liming Sun
2019-01-03 19:17 ` [PATCH v7 4/9] MAINTAINERS: Add entry for Mellanox Bluefield Soc Liming Sun
2019-01-03 19:17 ` [PATCH v7 5/9] soc: mellanox: host: Add the common host side Rshim driver Liming Sun
2019-01-03 19:17 ` [PATCH v7 6/9] soc: mellanox: host: Add networking support over Rshim Liming Sun
2019-01-03 19:17 ` [PATCH v7 7/9] soc: mellanox: host: Add the Rshim USB backend driver Liming Sun
2019-01-03 19:17 ` [PATCH v7 8/9] soc: mellanox: host: Add the Rshim PCIe " Liming Sun
2019-01-03 19:17 ` [PATCH v7 9/9] soc: mellanox: host: Add the Rshim PCIe live-fish " Liming Sun
2019-01-21 19:17 ` [PATCH v7 0/9] Mellanox BlueField ARM SoC Rshim driver Liming Sun
2019-02-18 13:24 ` Arnd Bergmann
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1541089538-175682-7-git-send-email-lsun@mellanox.com \
--to=lsun@mellanox.com \
--cc=linux-arm-kernel@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).