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 <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 | 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: 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..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 <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 */ -- 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