[PATCH v2 1/2] usb: misc: xapea00x: add driver for Xaptum ENF Access Card

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This commit adds a driver for the Xaptum ENF Access Card, a TPM2.0
hardware module for authenticating IoT devices and gateways.

The card consists of a SPI TPM 2.0 chip and a USB-SPI bridge. This
driver configures the bridge, registers the bridge as an SPI
controller, and adds the TPM 2.0 as an SPI device.  The in-kernel TPM
2.0 driver is then automatically loaded to configure the TPM and
expose it to userspace.

Signed-off-by: David R. Bild <david.bild@xxxxxxxxxx>
---
 MAINTAINERS                                 |   6 +
 drivers/usb/misc/Kconfig                    |   2 +
 drivers/usb/misc/Makefile                   |   1 +
 drivers/usb/misc/xapea00x/Kconfig           |  16 ++
 drivers/usb/misc/xapea00x/Makefile          |   7 +
 drivers/usb/misc/xapea00x/xapea00x-bridge.c | 400 ++++++++++++++++++++++++++
 drivers/usb/misc/xapea00x/xapea00x-core.c   | 426 ++++++++++++++++++++++++++++
 drivers/usb/misc/xapea00x/xapea00x-spi.c    | 209 ++++++++++++++
 drivers/usb/misc/xapea00x/xapea00x.h        |  75 +++++
 9 files changed, 1142 insertions(+)
 create mode 100644 drivers/usb/misc/xapea00x/Kconfig
 create mode 100644 drivers/usb/misc/xapea00x/Makefile
 create mode 100644 drivers/usb/misc/xapea00x/xapea00x-bridge.c
 create mode 100644 drivers/usb/misc/xapea00x/xapea00x-core.c
 create mode 100644 drivers/usb/misc/xapea00x/xapea00x-spi.c
 create mode 100644 drivers/usb/misc/xapea00x/xapea00x.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 79bb02ff812f..c0e34a3ef5aa 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14795,6 +14795,12 @@ L:	linux-wireless@xxxxxxxxxxxxxxx
 S:	Maintained
 F:	drivers/net/wireless/rndis_wlan.c
 
+USB XAPEA00X DRIVER
+M:	David R. Bild <david.bild@xxxxxxxxxx>
+L:	linux-usb@xxxxxxxxxxxxxxx
+S:	Maintained
+F:	drivers/usb/misc/xapea00x/
+
 USB XHCI DRIVER
 M:	Mathias Nyman <mathias.nyman@xxxxxxxxx>
 L:	linux-usb@xxxxxxxxxxxxxxx
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 68d2f2cd17dd..747d7f03fb84 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -275,3 +275,5 @@ config USB_CHAOSKEY
 
 	  To compile this driver as a module, choose M here: the
 	  module will be called chaoskey.
+
+source "drivers/usb/misc/xapea00x/Kconfig"
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 109f54f5b9aa..f3583501547c 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -30,4 +30,5 @@ obj-$(CONFIG_USB_HSIC_USB4604)		+= usb4604.o
 obj-$(CONFIG_USB_CHAOSKEY)		+= chaoskey.o
 
 obj-$(CONFIG_USB_SISUSBVGA)		+= sisusbvga/
+obj-$(CONFIG_USB_XAPEA00X)		+= xapea00x/
 obj-$(CONFIG_USB_LINK_LAYER_TEST)	+= lvstest.o
diff --git a/drivers/usb/misc/xapea00x/Kconfig b/drivers/usb/misc/xapea00x/Kconfig
new file mode 100644
index 000000000000..56c11f6ee7b3
--- /dev/null
+++ b/drivers/usb/misc/xapea00x/Kconfig
@@ -0,0 +1,16 @@
+config  USB_XAPEA00X
+        tristate "Xaptum ENF Access card support (XAP-EA-00x)"
+        depends on USB_SUPPORT
+        select SPI
+        select TCG_TPM
+        select TCG_TIS_SPI
+        help
+          Say Y here if you want to support the Xaptum ENF Access
+          modules (XAP-EA-00x) in the USB or Mini PCI-e form
+          factors. The XAP-EA-00x module exposes a TPM 2.0 as
+          /dev/tpmX to use for authenticating with the Xaptum ENF.
+
+          To compile this driver as a module, choose M here. The
+          module will be called xapea00x.
+
+          If unsure, say M.
diff --git a/drivers/usb/misc/xapea00x/Makefile b/drivers/usb/misc/xapea00x/Makefile
new file mode 100644
index 000000000000..c4bcd7524c31
--- /dev/null
+++ b/drivers/usb/misc/xapea00x/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the xapea00x driver.
+#
+obj-$(CONFIG_USB_XAPEA00X) += xapea00x.o
+
+xapea00x-y += xapea00x-core.o xapea00x-bridge.o
diff --git a/drivers/usb/misc/xapea00x/xapea00x-bridge.c b/drivers/usb/misc/xapea00x/xapea00x-bridge.c
new file mode 100644
index 000000000000..6d78e83d96f6
--- /dev/null
+++ b/drivers/usb/misc/xapea00x/xapea00x-bridge.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Driver for the XAP-EA-00x series of the Xaptum Edge Access Card, a
+ *  TPM 2.0-based hardware module for authenticating IoT devices and
+ *  gateways.
+ *
+ *  Copyright (c) 2017 Xaptum, Inc.
+ */
+
+#include "xapea00x.h"
+
+#define XAPEA00X_BR_CMD_READ		  0x00
+#define XAPEA00X_BR_CMD_WRITE		  0x01
+#define XAPEA00X_BR_CMD_WRITEREAD	  0x02
+
+#define XAPEA00X_BR_BREQTYP_SET		  0x40
+
+#define XAPEA00X_BR_BREQ_SET_GPIO_VALUES  0x21
+#define XAPEA00X_BR_BREQ_SET_GPIO_CS	  0x25
+#define XAPEA00X_BR_BREQ_SET_SPI_WORD	  0x31
+
+#define XAPEA00X_BR_USB_TIMEOUT		  1000 // msecs
+
+#define XAPEA00X_BR_CS_DISABLED		  0x00
+
+/*******************************************************************************
+ * Bridge USB transfers
+ */
+
+struct xapea00x_br_bulk_command {
+	__u16  reserved1;
+	__u8   command;
+	__u8   reserved2;
+	__le32 length;
+} __attribute__((__packed__));
+
+/**
+ * xapea00x_br_prep_bulk_command - Prepares the bulk command header with
+ * the supplied values.
+ * @hdr: pointer to header to prepare
+ * @command: the command id for the command
+ * @length: length in bytes of the command data
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static void xapea00x_br_prep_bulk_command(struct xapea00x_br_bulk_command *hdr,
+					  u8 command, int length)
+{
+	hdr->reserved1 = 0;
+	hdr->command   = command;
+	hdr->reserved2 = 0;
+	hdr->length    = __cpu_to_le32(length);
+}
+
+/**
+ * xapea00x_br_bulk_write - Issues a bulk write to the bridge chip.
+ * @dev: pointer to the device to write to
+ * @command: the command started by this write (WRITE, READ, WRITE_READ)
+ * @data: pointer to the data to write. Must be DMA capable (e.g.,
+ *	  kmalloc-ed, not stack).
+ * @len: length in bytes of the data to write
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_br_bulk_write(struct xapea00x_device *dev,
+				  struct xapea00x_br_bulk_command *header,
+				  const void *data, int len)
+{
+	u8 *buf;
+	unsigned int pipe;
+	int buf_len, actual_len, retval;
+
+	buf_len = sizeof(struct xapea00x_br_bulk_command) + len;
+	buf = kzalloc(buf_len, GFP_KERNEL);
+	if (!buf) {
+		retval = -ENOMEM;
+		goto out;
+	}
+
+	memcpy(buf, header, sizeof(struct xapea00x_br_bulk_command));
+	memcpy(buf + sizeof(struct xapea00x_br_bulk_command), data, len);
+
+	pipe = usb_sndbulkpipe(dev->udev, dev->bulk_out->bEndpointAddress);
+	retval = usb_bulk_msg(dev->udev, pipe, buf, buf_len, &actual_len,
+			      XAPEA00X_BR_USB_TIMEOUT);
+	if (retval) {
+		dev_warn(&dev->interface->dev,
+			 "%s: usb_bulk_msg() failed with %d\n",
+			 __func__, retval);
+		goto free_buf;
+	}
+
+	retval = 0;
+
+free_buf:
+	kzfree(buf);
+
+out:
+	return retval;
+}
+
+/**
+ * xapea00x_br_bulk_read - Issues a bulk read to the bridge chip.
+ * @dev: pointer to the device to read from
+ * @data: pointer to the data read. Must be DMA capable (e.g.,
+ *	  kmalloc-ed, not stack).
+ * @len: length in bytes of the data to read
+ *
+ * Return: If successful, 0. Otherwise a negative error code.
+ */
+static int xapea00x_br_bulk_read(struct xapea00x_device *dev, void *data,
+				 int len)
+{
+	unsigned int pipe;
+	void *buf;
+	int actual_len, retval;
+
+	buf = kzalloc(len, GFP_KERNEL);
+	if (!buf) {
+		retval = -ENOMEM;
+		goto out;
+	}
+
+	pipe = usb_rcvbulkpipe(dev->udev, dev->bulk_in->bEndpointAddress);
+	retval = usb_bulk_msg(dev->udev, pipe, buf, len, &actual_len,
+			      XAPEA00X_BR_USB_TIMEOUT);
+
+	if (retval) {
+		dev_warn(&dev->interface->dev,
+			 "%s: usb_bulk_msg() failed with %d\n",
+			 __func__, retval);
+		goto free_buf;
+	}
+
+	memcpy(data, buf, actual_len);
+	retval = 0;
+
+free_buf:
+	kzfree(buf);
+
+out:
+	return retval;
+}
+
+/**
+ * xapea00x_br_ctrl_write - Issues a send control transfer to the bridge
+ * chip.
+ * @dev: pointer to the device to write to
+ * @bRequest: the command
+ * @wValue: the command value
+ * @wIndex: the command index
+ * @data: pointer to the command data
+ * @len: length in bytes of the command data
+ *
+ * The possible bRequest, wValue, and wIndex values and data format
+ * are specified in the hardware datasheet.
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error code.
+ */
+static int xapea00x_br_ctrl_write(struct xapea00x_device *dev, u8 bRequest,
+				  u16 wValue, u16 wIndex, u8 *data, u16 len)
+{
+	unsigned int pipe;
+	void *buf;
+	int retval;
+
+	/* control_msg buffer must be dma-capable (e.g.k kmalloc-ed,
+	 * not stack).	Copy data into such buffer here, so we can use
+	 * simpler stack allocation in the callers - we have no
+	 * performance concerns given the small buffers and low
+	 * throughputs of this device.
+	 */
+	buf = kzalloc(len, GFP_KERNEL);
+	if (!buf) {
+		retval = -ENOMEM;
+		goto out;
+	}
+	memcpy(buf, data, len);
+
+	pipe = usb_sndctrlpipe(dev->udev, 0);
+	retval = usb_control_msg(dev->udev, pipe, bRequest,
+				 XAPEA00X_BR_BREQTYP_SET, wValue, wIndex,
+				 buf, len, XAPEA00X_BR_USB_TIMEOUT);
+	if (retval < 0) {
+		dev_warn(&dev->interface->dev,
+			 "usb_control_msg() failed with %d\n", retval);
+		goto free_buf;
+	}
+
+	retval = 0;
+
+free_buf:
+	kzfree(buf);
+
+out:
+	return retval;
+}
+
+/*******************************************************************************
+ * Bridge configuration commands
+ */
+
+/**
+ * xapea00x_br_set_gpio_value - Sets the value on the specified pin of
+ * the bridge chip.
+ * @dev: pointer to the device containing the bridge whose pin to set
+ * @pin: the number of the pin to set
+ * @value: the value to set the pin to, 0 or 1
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_br_set_gpio_value(struct xapea00x_device *dev, u8 pin,
+				      u8 value)
+{
+	u8 data[4] = { 0, 0, 0, 0 };
+
+	switch (pin) {
+	case 10:
+	case  9:
+	case  8:
+	case  7:
+	case  6:
+		data[0] = value << (pin - 4);
+		data[2] = 1 << (pin - 4);
+		break;
+	case  5:
+		data[0] = value;
+		data[2] = 1;
+		break;
+	case  4:
+	case  3:
+	case  2:
+	case  1:
+	case  0:
+		data[1] = value << (pin + 3);
+		data[3] = 1 << (pin + 3);
+		break;
+	}
+	return xapea00x_br_ctrl_write(dev, XAPEA00X_BR_BREQ_SET_GPIO_VALUES,
+				      0, 0, data, 4);
+}
+
+/**
+ * xapea00x_br_set_gpio_cs - Sets the chip select control on the specified
+ * pin of the bridge chip.
+ * @dev: pointer to the device containing the bridge whose cs to set
+ * @pin: the number of the pin to set
+ * @control: the chip select control value for the pin, 0, 1, or 2
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_br_set_gpio_cs(struct xapea00x_device *dev, u8 pin,
+				   u8 control)
+{
+	u8 data[2] = { pin, control };
+
+	return xapea00x_br_ctrl_write(dev, XAPEA00X_BR_BREQ_SET_GPIO_CS,
+				      0, 0, data, 2);
+}
+
+/*******************************************************************************
+ * Bridge configuration commands
+ */
+/**
+ * xapea00x_br_disable_cs - disable the built-in chip select
+ * capability of the specified channel.	 It does not support holding
+ * the CS active between SPI transfers, a feature required for the
+ * TPM.	 Instead, we manually control the CS pin as a GPIO.
+ * @dev: pointer to the device containing the bridge whose cs to disable
+ * @channel: the SPI channel whose cs to disable
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful 0. Otherwise a negative error number.
+ */
+int xapea00x_br_disable_cs(struct xapea00x_device *dev, u8 channel)
+{
+	return xapea00x_br_set_gpio_cs(dev, channel,
+				       XAPEA00X_BR_CS_DISABLED);
+}
+
+/**
+ * xapea00x_br_assert_cs - assert the chip select pin for the
+ * specified channel.
+ * @dev: pointer to the device containing the bridge who cs to assert
+ * @channel: the SPI channel whose cs to assert
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful 0. Otherwise a negative error number.
+ */
+int xapea00x_br_assert_cs(struct xapea00x_device *dev, u8 channel)
+{
+	return xapea00x_br_set_gpio_value(dev, channel, 0);
+}
+
+/**
+ * xapea00x_br_deassert_cs - deassert the chip select pin for the
+ * specified channel.
+ * @dev: pointer to the device containing the bridge who cs to deassert
+ * @channel: the SPI channel whose cs to deassert
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful 0. Otherwise a negative error number.
+ */
+int xapea00x_br_deassert_cs(struct xapea00x_device *dev, u8 channel)
+{
+	return xapea00x_br_set_gpio_value(dev, channel, 1);
+}
+
+/*******************************************************************************
+ * Bridge SPI reads and writes
+ */
+/**
+ * xeapea00x_spi_read - Performs a read from the active channel
+ * @dev: pointer to the device to perform the read
+ * @rx_buf: pointer to the buffer to read the data into.  Must be
+ *	    DMA-capable (e.g., kmalloc-ed, not stack).
+ * @len: length in bytes of the data to read
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+int xapea00x_br_spi_read(struct xapea00x_device *dev, void *rx_buf, int len)
+{
+	struct xapea00x_br_bulk_command header;
+	int retval;
+
+	xapea00x_br_prep_bulk_command(&header, XAPEA00X_BR_CMD_READ, len);
+	retval = xapea00x_br_bulk_write(dev, &header, NULL, 0);
+	if (retval)
+		goto out;
+
+	retval = xapea00x_br_bulk_read(dev, rx_buf, len);
+
+out:
+	return retval;
+}
+
+/**
+ *xapea00x_br_spi_write - Performs a write to the active channel
+ * @dev: pointer to the device to perform the write
+ * @tx_buf: pointer to the data to write. Must be DMA-capable (e.g.,
+ *	    kmalloc-ed, not stack).
+ * @len: length in bytes of the data to write
+ */
+int xapea00x_br_spi_write(struct xapea00x_device *dev, const void *tx_buf,
+			  int len)
+{
+	struct xapea00x_br_bulk_command header;
+	int retval;
+
+	xapea00x_br_prep_bulk_command(&header, XAPEA00X_BR_CMD_WRITE, len);
+	retval = xapea00x_br_bulk_write(dev, &header, tx_buf, len);
+
+	return retval;
+}
+
+/**
+ * xapea00x_br_spi_write_read - Performs a simultaneous write and read on
+ * the active channel
+ * @dev: pointer to the device to perform the write/read
+ * @tx_buf: pointer to the data to write. Must be DMA-capable (e.g.,
+ *	    kmalloc-ed, not stack).
+ * @rx_buf: pointer to the buffer to read the data into. Must be
+ *	    DMA-capable (e.g., kmalloc-ed, not stack).
+ * @len: length in bytes of the data to write/read
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+int xapea00x_br_spi_write_read(struct xapea00x_device *dev, const void *tx_buf,
+			       void *rx_buf, int len)
+{
+	struct xapea00x_br_bulk_command header;
+	int retval;
+
+	xapea00x_br_prep_bulk_command(&header, XAPEA00X_BR_CMD_WRITEREAD, len);
+	retval = xapea00x_br_bulk_write(dev, &header, tx_buf, len);
+	if (retval)
+		goto out;
+
+	retval = xapea00x_br_bulk_read(dev, rx_buf, len);
+
+out:
+	return retval;
+}
diff --git a/drivers/usb/misc/xapea00x/xapea00x-core.c b/drivers/usb/misc/xapea00x/xapea00x-core.c
new file mode 100644
index 000000000000..68caf3bf9be6
--- /dev/null
+++ b/drivers/usb/misc/xapea00x/xapea00x-core.c
@@ -0,0 +1,426 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Driver for the XAP-EA-00x series of the Xaptum Edge Access Card, a
+ *  TPM 2.0-based hardware module for authenticating IoT devices and
+ *  gateways.
+ *
+ *  Copyright (c) 2017 Xaptum, Inc.
+ */
+
+#include "xapea00x.h"
+
+#define XAPEA00X_TPM_MODALIAS	       "tpm_tis_spi"
+
+#define kref_to_xapea00x(k) container_of(k, struct xapea00x_device, kref)
+
+static void xapea00x_delete(struct kref *kref)
+{
+	struct xapea00x_device *dev = kref_to_xapea00x(kref);
+
+	usb_put_dev(dev->udev);
+	kfree(dev);
+}
+
+/*******************************************************************************
+ * SPI master functions
+ */
+
+/**
+ * xapea00x_spi_setup - Setup the SPI channel for the TPM.
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_spi_setup(struct spi_device *spi)
+{
+	struct xapea00x_device *dev;
+	int retval;
+
+	dev = spi_master_get_devdata(spi->master);
+
+	mutex_lock(&dev->usb_mutex);
+	if (!dev->interface) {
+		retval = -ENODEV;
+		goto out;
+	}
+
+	/* Verify that this is the TPM device */
+	if (spi->chip_select != 0) {
+		retval = -EINVAL;
+		goto err;
+	}
+
+	/*
+	 * Disable auto chip select for the TPM channel.
+	 * Must be done after setting the SPI parameters.
+	 */
+	retval = xapea00x_br_disable_cs(dev, 0);
+	if (retval)
+		goto err;
+
+	/* De-assert chip select for the TPM channel. */
+	retval = xapea00x_br_deassert_cs(dev, 0);
+	if (retval)
+		goto err;
+
+	dev_dbg(&dev->interface->dev, "configured spi channel for tpm\n");
+	goto out;
+
+err:
+	dev_err(&dev->interface->dev,
+		"configuring SPI channel failed with %d\n", retval);
+
+out:
+	mutex_unlock(&dev->usb_mutex);
+	return retval;
+}
+
+/**
+ * xapea00x_spi_transfer - Execute a single SPI transfer.
+ * @dev: pointer to the device to do the transfer on
+ * @tx_buf: pointer to the data to send, if not NULL
+ * @rx_buf: pointer to the buffer to store the received data, if not NULL
+ * @len: length in bytes of the data to send/receive
+ * @cs_hold: If non-zero, the chip select will remain asserted
+ * @delay_usecs: If nonzero, how long to delay after last bit transfer
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+int xapea00x_spi_transfer(struct xapea00x_device *dev,
+			  const void *tx_buf, void *rx_buf, u32 len,
+			  int cs_hold, u16 delay_usecs)
+{
+	int retval;
+
+	/* Assert chip select */
+	retval = xapea00x_br_assert_cs(dev, 0);
+	if (retval)
+		goto out;
+
+	/* empty transfer */
+	if (!tx_buf && !rx_buf)
+		retval = 0;
+	/* read transfer */
+	else if (!tx_buf)
+		retval = xapea00x_br_spi_read(dev, rx_buf, len);
+	/* write transfer */
+	else if (!rx_buf)
+		retval = xapea00x_br_spi_write(dev, tx_buf, len);
+	/* write_read transfer */
+	else
+		retval = xapea00x_br_spi_write_read(dev, tx_buf, rx_buf, len);
+
+	/* Deassert chip select, if requested */
+	if (!cs_hold)
+		retval = xapea00x_br_deassert_cs(dev, 0);
+
+	/* Delay for the requested time */
+	udelay(delay_usecs);
+
+out:
+	return retval;
+}
+
+/**
+ * xapea00x_spi_transfer_one_message - Execute a full SPI message.
+ * @master: The SPI master on which to execute the message.
+ * @msg: The SPI message to execute.
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative erorr number.
+ */
+static int xapea00x_spi_transfer_one_message(struct spi_master *master,
+					     struct spi_message *msg)
+{
+	struct xapea00x_device *dev;
+	struct spi_transfer *xfer;
+	int is_last, retval;
+
+	dev = spi_master_get_devdata(master);
+
+	mutex_lock(&dev->usb_mutex);
+	if (!dev->interface) {
+		retval = -ENODEV;
+		goto out;
+	}
+
+	/* perform all transfers */
+	list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+		is_last = list_is_last(&xfer->transfer_list, &msg->transfers);
+
+		/* Transfer message */
+		retval = xapea00x_spi_transfer(dev, xfer->tx_buf,
+					       xfer->rx_buf, xfer->len,
+					       is_last == xfer->cs_change,
+					       xfer->delay_usecs);
+		if (retval)
+			goto out;
+
+		msg->actual_length += xfer->len;
+	}
+
+	retval = 0;
+
+out:
+	msg->status = retval;
+	spi_finalize_current_message(master);
+
+	mutex_unlock(&dev->usb_mutex);
+	return retval;
+}
+
+/**
+ * xapea00x_spi_probe - Register and configure the SPI master.
+ * @dev: the device whose SPI master to register
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_spi_probe(struct xapea00x_device *dev)
+{
+	struct spi_master *spi_master;
+	int retval;
+
+	spi_master = spi_alloc_master(&dev->interface->dev, sizeof(void *));
+	if (!spi_master) {
+		retval = -ENOMEM;
+		goto err_out;
+	}
+
+	spi_master_set_devdata(spi_master, dev);
+
+	spi_master->min_speed_hz = 93 * 1000 + 800; /* 93.9kHz */
+	spi_master->max_speed_hz = 12 * 1000 * 1000; /* 12 MHz */
+
+	spi_master->bus_num = -1; /* dynamically assigned */
+	spi_master->num_chipselect = 1;
+	spi_master->mode_bits = SPI_MODE_0;
+
+	spi_master->flags = 0;
+	spi_master->setup = xapea00x_spi_setup;
+	spi_master->cleanup = NULL;
+	spi_master->transfer_one_message = xapea00x_spi_transfer_one_message;
+
+	retval = spi_register_master(spi_master);
+
+	if (retval)
+		goto free_spi;
+
+	dev->spi_master = spi_master;
+	dev_dbg(&dev->interface->dev, "registered SPI master\n");
+
+	return 0;
+
+free_spi:
+	spi_master_put(spi_master);
+	dev->spi_master = NULL;
+
+err_out:
+	return retval;
+}
+
+struct xapea00x_async_probe {
+	struct work_struct work;
+	struct xapea00x_device *dev;
+};
+
+#define work_to_probe(w) container_of(w, struct xapea00x_async_probe, work)
+
+/**
+ * xapea00x_init_async_probe - initialize an async probe with the
+ * specified values.
+ * @probe: pointer to the async_probe to initialize
+ * @dev: pointer to the device to probe
+ * @f: pointer to the probe function
+ */
+static void xapea00x_init_async_probe(struct xapea00x_async_probe *probe,
+				      struct xapea00x_device *dev,
+				      void (*f)(struct work_struct *work))
+{
+	INIT_WORK(&probe->work, f);
+	probe->dev = dev;
+
+	kref_get(&dev->kref);
+	spi_master_get(dev->spi_master);
+}
+
+/**
+ * xapea00x_cleanup_async_probe - clean up the internals of the async
+ * probe. Call this method after the probe has completed.
+ *
+ * The caller is responsible for freeing the probe itself, if
+ * dynamically allocated.
+ *
+ * @probe: pointer to the async_probe to clean up
+ */
+static void xapea00x_cleanup_async_probe(struct xapea00x_async_probe *probe)
+{
+	spi_master_put(probe->dev->spi_master);
+	kref_put(&probe->dev->kref, xapea00x_delete);
+}
+
+static struct spi_board_info tpm_board_info = {
+	.modalias	 = XAPEA00X_TPM_MODALIAS,
+	.max_speed_hz	 = 43 * 1000 * 1000, // Hz
+	.chip_select	 = 0,
+	.mode		 = SPI_MODE_0,
+	.platform_data	 = NULL,
+	.controller_data = NULL,
+};
+
+/**
+ * xapea00x_tpm_probe - Register and initialize the TPM device
+ * @work: the work struct contained by the xapea00x device
+ *
+ * Context: !in_interrupt()
+ */
+static void xapea00x_tpm_probe(struct work_struct *work)
+{
+	struct xapea00x_async_probe *probe = work_to_probe(work);
+	struct xapea00x_device *dev = probe->dev;
+	struct spi_master *spi_master = dev->spi_master;
+	struct spi_device *tpm;
+	int retval;
+
+	tpm = spi_new_device(spi_master, &tpm_board_info);
+	mutex_lock(&dev->usb_mutex);
+	if (!dev->interface) {
+		retval = -ENODEV;
+		goto out;
+	}
+	if (!tpm) {
+		retval = -ENODEV;
+		dev_err(&dev->interface->dev,
+			"unable to add spi device for TPM\n");
+		goto err;
+	}
+
+	dev->tpm = tpm;
+	dev_info(&dev->interface->dev, "TPM initialization complete\n");
+
+	retval = 0;
+	goto out;
+
+err:
+	dev_err(&dev->interface->dev,
+		"TPM initialization failed with %d\n", retval);
+
+out:
+	mutex_unlock(&dev->usb_mutex);
+	xapea00x_cleanup_async_probe(probe);
+	kzfree(probe);
+}
+
+/*******************************************************************************
+ * USB driver structs and functions
+ */
+
+static const struct usb_device_id xapea00x_devices[] = {
+	{ USB_DEVICE(USB_VENDOR_ID_SILABS, USB_PRODUCT_ID_XAPEA001) },
+	{ USB_DEVICE(USB_VENDOR_ID_XAPTUM, USB_PRODUCT_ID_XAPEA002) },
+	{ USB_DEVICE(USB_VENDOR_ID_XAPTUM, USB_PRODUCT_ID_XAPEA003) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, xapea00x_devices);
+
+static int xapea00x_probe(struct usb_interface *interface,
+			  const struct usb_device_id *id)
+{
+	struct xapea00x_device *dev;
+	struct xapea00x_async_probe *probe;
+	int retval;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	kref_init(&dev->kref);
+	mutex_init(&dev->usb_mutex);
+
+	/* ---------------------- USB ------------------------ */
+	dev->interface = interface;
+	dev->udev = usb_get_dev(interface_to_usbdev(interface));
+
+	dev->vid = __le16_to_cpu(dev->udev->descriptor.idVendor);
+	dev->pid = __le16_to_cpu(dev->udev->descriptor.idProduct);
+
+	retval = usb_find_common_endpoints(interface->cur_altsetting,
+					   &dev->bulk_in, &dev->bulk_out,
+					   NULL, NULL);
+	if (retval) {
+		dev_err(&interface->dev,
+			"could not find both bulk-in and bulk-out endpoints\n");
+		goto free_dev;
+	}
+
+	usb_set_intfdata(interface, dev);
+
+	/* ---------------------- SPI Master ------------------------ */
+	retval = xapea00x_spi_probe(dev);
+	if (retval) {
+		dev_err(&interface->dev, "could not initialize SPI master\n");
+		goto free_dev;
+	}
+
+	/* ---------------------- TPM SPI Device ------------------------ */
+	probe = kzalloc(sizeof(*probe), GFP_KERNEL);
+	if (!probe) {
+		retval = -ENOMEM;
+		goto free_spi;
+	}
+	xapea00x_init_async_probe(probe, dev, xapea00x_tpm_probe);
+
+	schedule_work(&probe->work);
+	dev_info(&interface->dev, "scheduled initialization of TPM\n");
+
+	/* ---------------------- Finished ------------------------ */
+	dev_info(&interface->dev, "device connected\n");
+	return 0;
+
+free_spi:
+	spi_unregister_master(dev->spi_master);
+
+free_dev:
+	kref_put(&dev->kref, xapea00x_delete);
+
+	dev_err(&interface->dev, "device failed with %d\n", retval);
+	return retval;
+}
+
+static void xapea00x_disconnect(struct usb_interface *interface)
+{
+	struct xapea00x_device *dev = usb_get_intfdata(interface);
+
+	usb_set_intfdata(interface, NULL);
+	spi_unregister_master(dev->spi_master);
+
+	mutex_lock(&dev->usb_mutex);
+	dev->interface = NULL;
+	mutex_unlock(&dev->usb_mutex);
+
+	kref_put(&dev->kref, xapea00x_delete);
+
+	dev_info(&dev->udev->dev, "device disconnected\n");
+}
+
+static struct usb_driver xapea00x_driver = {
+	.name		      = "xapea00x",
+	.probe		      = xapea00x_probe,
+	.disconnect	      = xapea00x_disconnect,
+	.suspend	      = NULL,
+	.resume		      = NULL,
+	.reset_resume	      = NULL,
+	.id_table	      = xapea00x_devices,
+	.supports_autosuspend = 0
+};
+
+module_usb_driver(xapea00x_driver);
+
+MODULE_AUTHOR("David R. Bild <david.bild@xxxxxxxxxx>");
+MODULE_DESCRIPTION("Xaptum XAP-EA-00x ENF Access card");
+MODULE_LICENSE("GPL");
+
+MODULE_ALIAS("xapea00x");
diff --git a/drivers/usb/misc/xapea00x/xapea00x-spi.c b/drivers/usb/misc/xapea00x/xapea00x-spi.c
new file mode 100644
index 000000000000..8e145b83f73b
--- /dev/null
+++ b/drivers/usb/misc/xapea00x/xapea00x-spi.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Driver for the XAP-EA-00x series of the Xaptum Edge Access Card, a
+ *  TPM 2.0-based hardware module for authenticating IoT devices and
+ *  gateways.
+ *
+ *  Copyright (c) 2017 Xaptum, Inc.
+ */
+
+#include "xapea00x.h"
+
+/*******************************************************************************
+ * SPI master functions
+ */
+
+/**
+ * xapea00x_spi_setup - Setup the SPI channel for the TPM.
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_spi_setup(struct spi_device *spi)
+{
+	struct xapea00x_device *dev;
+	int retval;
+
+	dev = spi_master_get_devdata(spi->master);
+
+	/* Verify that this is the TPM device */
+	if (spi->chip_select != 0) {
+		retval = -EINVAL;
+		goto err;
+	}
+
+	/* Set the SPI parameters for the TPM channel. */
+	retval = xapea00x_br_set_spi_word(dev, 0, XAPEA00X_TPM_SPI_WORD);
+	if (retval)
+		goto err;
+
+	/*
+	 * Disable auto chip select for the TPM channel.
+	 * Must be done after setting the SPI parameters.
+	 */
+	retval = xapea00x_br_set_gpio_cs(dev, 0, XAPEA00X_GPIO_CS_DISABLED);
+	if (retval)
+		goto err;
+
+	/* De-assert chip select for the TPM channel. */
+	retval = xapea00x_br_set_gpio_value(dev, 0, 1);
+	if (retval)
+		goto err;
+
+	dev_dbg(&dev->interface->dev, "configured spi channel for tpm\n");
+	return 0;
+
+err:
+	dev_err(&dev->interface->dev,
+		"configuring SPI channel failed with %d\n", retval);
+	return retval;
+}
+
+/**
+ * xapea00x_spi_cleanup
+ *
+ * Context: !in_interrupt()
+ */
+static void xapea00x_spi_cleanup(struct spi_device *spi)
+{
+	dev_dbg(&spi->dev, "%s\n", __func__);
+}
+
+/**
+ * xapea00x_spi_transfer - Execute a single SPI transfer.
+ * @dev: pointer to the device to do the transfer on
+ * @tx_buf: pointer to the data to send, if not NULL
+ * @rx_buf: pointer to the buffer to store the received data, if not NULL
+ * @len: length in bytes of the data to send/receive
+ * @cs_hold: If non-zero, the chip select will remain asserted
+ * @delay_usecs: If nonzero, how long to delay after last bit transfer
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+int xapea00x_spi_transfer(struct xapea00x_device *dev,
+			  const void *tx_buf, void *rx_buf, u32 len,
+			  int cs_hold, u16 delay_usecs)
+{
+	int retval;
+
+	/* Assert chip select */
+	retval = xapea00x_br_set_gpio_value(dev, 0, 0);
+	if (retval)
+		goto out;
+
+	/* empty transfer */
+	if (!tx_buf && !rx_buf)
+		retval = 0;
+	/* read transfer */
+	else if (!tx_buf)
+		retval = xapea00x_br_spi_read(dev, rx_buf, len);
+	/* write transfer */
+	else if (!rx_buf)
+		retval = xapea00x_br_spi_write(dev, tx_buf, len);
+	/* write_read transfer */
+	else
+		retval = xapea00x_br_spi_write_read(dev, tx_buf, rx_buf, len);
+
+	/* Deassert chip select, if requested */
+	if (!cs_hold)
+		retval = xapea00x_br_set_gpio_value(dev, 0, 1);
+
+	/* Delay for the requested time */
+	udelay(delay_usecs);
+
+out:
+	return retval;
+}
+
+/**
+ * xapea00x_spi_transfer_one_message - Execute a full SPI message.
+ * @master: The SPI master on which to execute the message.
+ * @msg: The SPI message to execute.
+ *
+ * Context: !in_interrupt()
+ *
+ * Return: If successful, 0. Otherwise a negative erorr number.
+ */
+static int xapea00x_spi_transfer_one_message(struct spi_master *master,
+					     struct spi_message *msg)
+{
+	struct xapea00x_device *dev;
+	struct spi_transfer *xfer;
+	int is_last, retval;
+
+	dev = spi_master_get_devdata(master);
+
+	/* perform all transfers */
+	list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+		is_last = list_is_last(&xfer->transfer_list, &msg->transfers);
+
+		/* Transfer message */
+		retval = xapea00x_spi_transfer(dev, xfer->tx_buf,
+					       xfer->rx_buf, xfer->len,
+					       is_last == xfer->cs_change,
+					       xfer->delay_usecs);
+		if (retval)
+			goto out;
+
+		msg->actual_length += xfer->len;
+	}
+
+	retval = 0;
+
+out:
+	msg->status = retval;
+	spi_finalize_current_message(master);
+	return retval;
+}
+
+/**
+ * xapea00x_spi_probe - Register and configure the SPI master.
+ * @dev: the device whose SPI master to register
+ *
+ * Return: If successful, 0. Otherwise a negative error number.
+ */
+static int xapea00x_spi_probe(struct xapea00x_device *dev)
+{
+	struct spi_master *spi_master;
+	int retval;
+
+	spi_master = spi_alloc_master(&dev->udev->dev, sizeof(void *));
+	if (!spi_master) {
+		retval = -ENOMEM;
+		goto err_out;
+	}
+
+	spi_master_set_devdata(spi_master, dev);
+
+	spi_master->min_speed_hz = 93 * 1000 + 800; /* 93.9kHz */
+	spi_master->max_speed_hz = 12 * 1000 * 1000; /* 12 MHz */
+
+	spi_master->bus_num = -1; /* dynamically assigned */
+	spi_master->num_chipselect = XAPEA00X_NUM_CS;
+	spi_master->mode_bits = SPI_MODE_0;
+
+	spi_master->flags = 0;
+	spi_master->setup = xapea00x_spi_setup;
+	spi_master->cleanup = xapea00x_spi_cleanup;
+	spi_master->transfer_one_message = xapea00x_spi_transfer_one_message;
+
+	retval = spi_register_master(spi_master);
+
+	if (retval)
+		goto free_spi;
+
+	dev->spi_master = spi_master;
+	dev_dbg(&dev->interface->dev, "registered SPI master\n");
+
+	return 0;
+
+free_spi:
+	spi_master_put(spi_master);
+	dev->spi_master = NULL;
+
+err_out:
+	return retval;
+}
diff --git a/drivers/usb/misc/xapea00x/xapea00x.h b/drivers/usb/misc/xapea00x/xapea00x.h
new file mode 100644
index 000000000000..07594ec952ef
--- /dev/null
+++ b/drivers/usb/misc/xapea00x/xapea00x.h
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Driver for the XAP-EA-00x series of the Xaptum Edge Access Card, a
+ *  TPM 2.0-based hardware module for authenticating IoT devices and
+ *  gateways.
+ *
+ *  Copyright (c) 2017 Xaptum, Inc.
+ */
+
+#ifndef _XAPEA00X_H
+#define _XAPEA00X_H
+
+#include <linux/kernel.h>
+#include <linux/kref.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+#define USB_VENDOR_ID_SILABS           0x10c4
+#define USB_VENDOR_ID_XAPTUM           0x2FE0
+
+#define USB_PRODUCT_ID_XAPEA001        0x8BDE
+#define USB_PRODUCT_ID_XAPEA002        0x8BDE
+#define USB_PRODUCT_ID_XAPEA003        0x8BEE
+
+struct xapea00x_device {
+	struct kref kref;
+
+	struct usb_device *udev;
+	/*
+	 * The interface pointer will be set NULL when the device
+	 * disconnects.  Accessing it safe only while holding the
+	 * usb_mutex.
+	 */
+	struct usb_interface *interface;
+	/*
+	 * Th usb_mutex must be held while synchronous USB requests are
+	 * in progress. It is acquired during disconnect to be sure
+	 * that there is not an outstanding request.
+	 */
+	struct mutex usb_mutex;
+
+	struct usb_endpoint_descriptor *bulk_in;
+	struct usb_endpoint_descriptor *bulk_out;
+
+	u16 pid;
+	u16 vid;
+
+	struct spi_master *spi_master;
+	struct spi_device *tpm;
+};
+
+/* Public bridge functions */
+int xapea00x_br_disable_cs(struct xapea00x_device *dev, u8 channel);
+int xapea00x_br_assert_cs(struct xapea00x_device *dev, u8 channel);
+int xapea00x_br_deassert_cs(struct xapea00x_device *dev, u8 channel);
+
+int xapea00x_br_spi_read(struct xapea00x_device *dev, void *rx_buf, int len);
+int xapea00x_br_spi_write(struct xapea00x_device *dev, const void *tx_buf,
+			  int len);
+int xapea00x_br_spi_write_read(struct xapea00x_device *dev, const void *tx_buf,
+			       void *rx_buf, int len);
+
+/* Shared SPI function */
+int xapea00x_spi_transfer(struct xapea00x_device *dev,
+			  const void *tx_buf, void *rx_buf, u32 len,
+			  int cs_hold, u16 delay_usecs);
+
+/* Shared TPM functions */
+int xapea00x_tpm_platform_initialize(struct xapea00x_device *dev);
+
+#endif /* _XAPEA00X_H */
-- 
2.16.2

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux