Am Mittwoch, den 10.01.2018, 10:58 -0600 schrieb David R. Bild :
> 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 <[email protected]>
> ---
> 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 | 399 ++++++++++++++++++++++++++
> drivers/usb/misc/xapea00x/xapea00x-core.c | 429
> ++++++++++++++++++++++++++++
> drivers/usb/misc/xapea00x/xapea00x-spi.c | 209 ++++++++++++++
> drivers/usb/misc/xapea00x/xapea00x.h | 75 +++++
> 9 files changed, 1144 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 a6e86e20761e..8d16f34b8603 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -14300,6 +14300,12 @@ L: [email protected]
> S: Maintained
> F: drivers/net/wireless/rndis_wlan.c
>
> +USB XAPEA00X DRIVER
> +M: David R. Bild <[email protected]>
> +L: [email protected]
> +S: Maintained
> +F: drivers/usb/misc/xapea00x/
> +
> USB XHCI DRIVER
> M: Mathias Nyman <[email protected]>
> L: [email protected]
> 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..27e87bc1b4b2
> --- /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..3efa697f216a
> --- /dev/null
> +++ b/drivers/usb/misc/xapea00x/xapea00x-bridge.c
> @@ -0,0 +1,399 @@
> +// 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 {
> + __be16 reserved1;
Here BE ...
> + __u8 command;
> + __u8 reserved2;
> + __le32 length;
... there LE?
> +} __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 builk write to the bridge chip.
typo
> + * @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);
Why zeroed? You copy all over it.
> + 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;
Does this make any sense?
> + }
> +
> + 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)
Combine with xapea00x_br_bulk_write()?
> +{
> + 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);
Why kzalloc something you copy over as the very next thing?
> + 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);
Add a break please
> + }
> + 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..ad62c40d724b
> --- /dev/null
> +++ b/drivers/usb/misc/xapea00x/xapea00x-core.c
> @@ -0,0 +1,429 @@
> +// 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");
> + retval = 0;
> + goto out;
The control flow is innovative.
> +
> +err:
> + dev_err(&dev->interface->dev,
> + "configuring SPI channel failed with %d\n", retval);
> +
> +out:
> + mutex_unlock(&dev->usb_mutex);
> + 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_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);
Ouch. Do we really need to unconditionally delay?
How about saving the time and using udelay only when required?
> +
> +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 = 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;
Looks like a race condition
> + 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_free_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) {
Why test this under a mutex?
> + 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 err_out;
> + }
> +
> + 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 err_out;
> + }
> +
> + /* ---------------------- TPM SPI Device ------------------------ */
> + probe = kzalloc(sizeof(*probe), GFP_KERNEL);
Oops in the failure case
> + 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;
> +
> +err_out:
> + dev_err(&interface->dev, "device failed with %d\n", retval);
> + kref_put(&dev->kref, xapea00x_delete);
> + 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 <[email protected]>");
> +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 */
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html