Messages in this thread Patch in this message | | | From | Dave Kim <> | Subject | [PATCH 1/1] drivers: misc: Add support for nCipher HSM devices | Date | Tue, 17 Dec 2019 13:22:44 +0000 |
| |
From: David Kim <david.kim@ncipher.com>
Introduce the driver for nCipher's Solo and Solo XC range of PCIe hardware security modules (HSM), which provide key creation/management and cryptography services.
Co-developed-by: Tim Magee <tim.magee@ncipher.com> Signed-off-by: Tim Magee <tim.magee@ncipher.com> Signed-off-by: David Kim <david.kim@ncipher.com>
--- MAINTAINERS | 8 + drivers/misc/Kconfig | 1 + drivers/misc/Makefile | 1 + drivers/misc/ncipher/Kconfig | 8 + drivers/misc/ncipher/Makefile | 7 + drivers/misc/ncipher/fsl.c | 911 +++++++++++++++++ drivers/misc/ncipher/fsl.h | 117 +++ drivers/misc/ncipher/hostif.c | 1521 +++++++++++++++++++++++++++++ drivers/misc/ncipher/i21555.c | 553 +++++++++++ drivers/misc/ncipher/i21555.h | 68 ++ drivers/misc/ncipher/solo.h | 316 ++++++ include/uapi/linux/nshield_solo.h | 181 ++++ 12 files changed, 3692 insertions(+) create mode 100644 drivers/misc/ncipher/Kconfig create mode 100644 drivers/misc/ncipher/Makefile create mode 100644 drivers/misc/ncipher/fsl.c create mode 100644 drivers/misc/ncipher/fsl.h create mode 100644 drivers/misc/ncipher/hostif.c create mode 100644 drivers/misc/ncipher/i21555.c create mode 100644 drivers/misc/ncipher/i21555.h create mode 100644 drivers/misc/ncipher/solo.h create mode 100644 include/uapi/linux/nshield_solo.h
diff --git a/MAINTAINERS b/MAINTAINERS index 061d59a4a80b..e49b9e44fd3d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11319,6 +11319,14 @@ NATSEMI ETHERNET DRIVER (DP8381x) S: Orphan F: drivers/net/ethernet/natsemi/natsemi.c +NCIPHER NSHIELD HARDWARE SECURITY MODULE DRIVERS +M: Tim Magee <tim.magee@ncipher.com> +M: David Kim <david.kim@ncipher.com> +M: Hamish Cameron <hamish.cameron@ncipher.com> +L: linux-kernel@vger.kernel.org +S: Supported +F: drivers/misc/ncipher/ + NCR 5380 SCSI DRIVERS M: Finn Thain <fthain@telegraphics.com.au> M: Michael Schmitz <schmitzmic@gmail.com> diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 7f0d48f406e3..a6265ad5ca98 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -480,4 +480,5 @@ source "drivers/misc/cxl/Kconfig" source "drivers/misc/ocxl/Kconfig" source "drivers/misc/cardreader/Kconfig" source "drivers/misc/habanalabs/Kconfig" +source "drivers/misc/ncipher/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index c1860d35dc7e..8e4a6ab0a7ba 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -57,3 +57,4 @@ obj-y += cardreader/ obj-$(CONFIG_PVPANIC) += pvpanic.o obj-$(CONFIG_HABANA_AI) += habanalabs/ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o +obj-$(CONFIG_NCIPHER) += ncipher/ diff --git a/drivers/misc/ncipher/Kconfig b/drivers/misc/ncipher/Kconfig new file mode 100644 index 000000000000..5b466cd1896a --- /dev/null +++ b/drivers/misc/ncipher/Kconfig @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# Enable support for nCipher's nShield Solo and Solo XC +config HSM_NCIPHER_NSHIELD_SOLO + tristate "nCipher Solo and Solo XC family of PCIe HSMs" + depends on PCI + help + Select this as built-in or module if you expect to use + a Hardware Security Module from nCipher's Solo or Solo XC range. diff --git a/drivers/misc/ncipher/Makefile b/drivers/misc/ncipher/Makefile new file mode 100644 index 000000000000..b4d5f92addee --- /dev/null +++ b/drivers/misc/ncipher/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for nCipher nShield HSM drivers +# + +obj-$(CONFIG_HSM_NCIPHER_NSHIELD_SOLO) := nshield_solo.o +nshield_solo-y := hostif.o fsl.o i21555.o diff --git a/drivers/misc/ncipher/fsl.c b/drivers/misc/ncipher/fsl.c new file mode 100644 index 000000000000..5c4edeef64c0 --- /dev/null +++ b/drivers/misc/ncipher/fsl.c @@ -0,0 +1,911 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * + * fsl.c: nCipher PCI HSM FSL command driver + * Copyright 2019 nCipher Security Ltd + * + */ + +#include "solo.h" +#include "fsl.h" + +/** + * Resets FSL device. + * + * Extra device info is initialized the first time created. + * + * @param ndev common device. + * @returns 0 if successful, other value if error. + */ +static int fsl_create(struct nfp_dev *ndev) +{ + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + if (ndev->created) { + dev_notice(&ndev->pcidev->dev, + "%s: device already created", __func__); + return 0; + } + + ndev->active_bar = -1; + ndev->detection_type = NFP_HSM_POLLING; + ndev->conn_status = NFP_HSM_STARTING; + + /* try to reset check doorbell registers + * (don't read back in case they hang) + */ + ndev->active_bar = FSL_MEMBAR; + fsl_outl(ndev, FSL_OFFSET_DOORBELL_CS_STATUS, NFAST_INT_DEVICE_CLR); + + if (!ndev->bar[ndev->active_bar]) { + dev_err(&ndev->pcidev->dev, + "%s: error: null FSL memory BAR[%d]", + __func__, ndev->active_bar); + return -ENOMEM; + } + + /* set our context to just be a pointer to ourself */ + ndev->cmdctx = ndev; + + /* try to reset read/write doorbell registers + * (don't read back in case they hang) + */ + dev_notice(&ndev->pcidev->dev, + "%s: clearing read/write doorbell registers", __func__); + fsl_outl(ndev, FSL_OFFSET_DOORBELL_WR_CMD, NFAST_INT_HOST_CLR); + fsl_outl(ndev, FSL_OFFSET_DOORBELL_RD_CMD, NFAST_INT_HOST_CLR); + + dev_notice(&ndev->pcidev->dev, "%s: exiting %s active_bar: %d.", + __func__, __func__, ndev->active_bar); + + ndev->created = 1; + + return 0; +} + +/** + * Destroys an FSL device. + * + * @param ctx device context (always the device itself). + * @returns 0 if successful, other value if error. + */ +static int fsl_destroy(struct nfp_dev *ctx) +{ + struct nfp_dev *ndev; + + /* check for device */ + ndev = ctx; + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* clear doorbell registers */ + if (ndev->bar[ndev->active_bar]) { + dev_notice(&ndev->pcidev->dev, + "%s: clearing doorbell registers", __func__); + fsl_outl(ndev, + FSL_OFFSET_DOORBELL_WR_STATUS, NFAST_INT_DEVICE_CLR); + fsl_outl(ndev, + FSL_OFFSET_DOORBELL_RD_STATUS, NFAST_INT_DEVICE_CLR); + fsl_outl(ndev, + FSL_OFFSET_DOORBELL_CS_STATUS, NFAST_INT_DEVICE_CLR); + } else { + dev_err(&ndev->pcidev->dev, + "%s: warning: no FSL BAR[%d] memory", + __func__, ndev->active_bar); + } + + return 0; +} + +/** + * Returns fsl_created status. + * + * @param ndev common device. + * @returns 0 if created or other value if error. + */ +static int fsl_created(struct nfp_dev *ndev) +{ + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + if (!ndev->created) { + dev_err(&ndev->pcidev->dev, + "%s: error: device not created", __func__); + return -ENODEV; + } + + if (!ndev->bar[ndev->active_bar]) { + dev_err(&ndev->pcidev->dev, + "%s: error: no FSL BAR[%d] memory", __func__, + ndev->active_bar); + return -ENOMEM; + } + + return 0; +} + +/* ----------------------------------------------------- */ +/* This call needs to be in synch with the ISR or the ISR will come in the + * middle of a write/read op and cause problems. + */ +static int fsl_connection_status(struct nfp_dev *ndev, int lock_flag, + int epd_status) +{ + int status = NFP_HSM_STARTING; + + /* check for device */ + if (!ndev) + return -ENODEV; + + /* this code is mainly to support backwards compatibility with the + * interrupt driven approach to detection. + */ + if (epd_status == NFP_HSM_POLLING) { + ndev->detection_type = NFP_HSM_POLLING; + ndev->conn_status = 0; + } else if (epd_status == NFAST_INT_DEVICE_PCI_DOWN) { + ndev->conn_status = NFP_HSM_STARTING; + } + + status = ndev->conn_status; + return status; +} + +/** + * Returns connection check status. + * + * @param ndev common device. + * @returns 0 if started, NFP_HSM_STARTING if not ready, + * or other value if error. + */ +static int fsl_started(struct nfp_dev *ndev, int lock_flag) +{ + int status = NFP_HSM_STARTING; + int epd_status = NFP_HSM_STARTING; + u32 doorbell_cs = 0x0; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + if (!ndev->bar[ndev->active_bar]) { + dev_err(&ndev->pcidev->dev, "%s: error: no FSL BAR[%d] memory", + __func__, ndev->active_bar); + return -ENOMEM; + } + + /* check the status register to see if epd has started */ + doorbell_cs = fsl_inl(ndev, FSL_OFFSET_DOORBELL_POLLING); + dev_notice(&ndev->pcidev->dev, + "%s: doorbell_polling is: %x", __func__, doorbell_cs); + + if (doorbell_cs == NFAST_INT_DEVICE_POLL) { + epd_status = NFP_HSM_POLLING; + dev_notice(&ndev->pcidev->dev, + "%s: EPD in polling mode", __func__); + } else if (doorbell_cs == NFAST_INT_DEVICE_PCI_DOWN) { + epd_status = NFAST_INT_DEVICE_PCI_DOWN; + } + /* check current connection status */ + status = fsl_connection_status(ndev, lock_flag, epd_status); + + if (status == 0) { + dev_notice(&ndev->pcidev->dev, "%s: device started", __func__); + } else if (status == NFP_HSM_STARTING) { + dev_notice(&ndev->pcidev->dev, "%s: device starting", __func__); + /* Closest existing error code */ + status = -EAGAIN; + } else { + dev_err(&ndev->pcidev->dev, + "%s: error: device failure: code 0x%x", + __func__, status); + } + + return status; +} + +/** + * Updates the connection check status. + * + * @param ndev common device. + * @param status new status + * @returns 0 if stopped or other value if error. + */ + +static int fsl_update_connection_status(struct nfp_dev *ndev, int status) +{ + int current_status; + + if (!ndev) + return -ENODEV; + + current_status = ndev->conn_status; + ndev->conn_status = status; + return current_status; +} + +/** + * Completes a connection check status interrupt. + * + * @param ndev common device. + * @param status device status. + */ +static void fsl_check_complete(struct nfp_dev *ndev, int status) +{ + int ne; + int started; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* check for device */ + + ne = fsl_created(ndev); + if (ne != 0) { + dev_err(&ndev->pcidev->dev, + "%s: error: check not completed", __func__); + return; + } + + /* started becomes true after fsl_create and the first cs interrupt is + * successful. + * It switches to false right after since cs_status is set to + * 0 right after this check + * A fsl_close or a fsl_create can + * reset the cs_status to NFP_HSM_STARTING again. + */ + started = + (fsl_update_connection_status(ndev, status) == + NFP_HSM_STARTING) && (status == 0); + + /* reset read/write doorbell registers if just started */ + + if (started) { + dev_notice(&ndev->pcidev->dev, + "fsl_create: clearing read/write doorbell registers"); + fsl_outl(ndev, FSL_OFFSET_DOORBELL_WR_CMD, NFAST_INT_HOST_CLR); + fsl_inl(ndev, FSL_OFFSET_DOORBELL_WR_CMD); + fsl_outl(ndev, FSL_OFFSET_DOORBELL_RD_CMD, NFAST_INT_HOST_CLR); + fsl_inl(ndev, FSL_OFFSET_DOORBELL_RD_CMD); + fsl_outl(ndev, + FSL_OFFSET_DOORBELL_WR_STATUS, NFAST_INT_DEVICE_CLR); + fsl_inl(ndev, FSL_OFFSET_DOORBELL_WR_STATUS); + fsl_outl(ndev, + FSL_OFFSET_DOORBELL_RD_STATUS, NFAST_INT_DEVICE_CLR); + fsl_inl(ndev, FSL_OFFSET_DOORBELL_RD_STATUS); + } + + if (status == 0) { + dev_notice(&ndev->pcidev->dev, "%s: device started", __func__); + } else if (status == NFP_HSM_STARTING) { + dev_notice(&ndev->pcidev->dev, + "%s: device not started yet", __func__); + } else { + dev_err(&ndev->pcidev->dev, + "%s: device check failed with code: 0x%x", + __func__, status); + } +} + +/** + * Handles an interrupt from the FSL device. + * + * @param ctx device context (always the device itself). + * @param handled set non-zero by this routine if interrupt considered handled + * @returns 0 if successful, other value if error. + */ +static int fsl_isr(struct nfp_dev *ctx, int *handled) +{ + struct nfp_dev *ndev = ctx; + int ne; + u32 doorbell_rd, doorbell_wr, doorbell_cs; + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* mark not yet handled */ + *handled = 0; + + ne = fsl_created(ndev); + if (ne != 0) { + dev_err(&ndev->pcidev->dev, + "%s: error: interrupt not handled", __func__); + return ne; + } + + ++ndev->stats.isr; + + doorbell_wr = fsl_inl(ndev, FSL_OFFSET_DOORBELL_WR_STATUS); + doorbell_rd = fsl_inl(ndev, FSL_OFFSET_DOORBELL_RD_STATUS); + doorbell_cs = fsl_inl(ndev, FSL_OFFSET_DOORBELL_CS_STATUS); + dev_notice(&ndev->pcidev->dev, "%s: cs:= %x,rd:=%x,wr:=%x", + __func__, doorbell_cs, doorbell_rd, doorbell_wr); + + while (doorbell_rd || doorbell_wr || doorbell_cs) { + /* prevent any illegal combination of set bits from triggering + * processing. Note that if anyone of these registers have an + * incorrect bit set, it would prevent the other operations from + * being processed since we return from the ISR, even if they + * have legal values. This is an unlikely scenario since these + * registers are written either to 0 or one of the legal values + * by the software on the card. + */ + if ((doorbell_cs) && + ((doorbell_cs & ~NFAST_INT_DEVICE_CHECK_OK) && + (doorbell_cs & ~NFAST_INT_DEVICE_CHECK_FAILED))) { + dev_err(&ndev->pcidev->dev, + "%s: illegal bits in doorbell_cs %x", + __func__, doorbell_cs); + *handled = 1; + /* clear the register*/ + fsl_outl(ndev, FSL_OFFSET_DOORBELL_CS_STATUS, + NFAST_INT_DEVICE_CLR); + return 0; + } + if ((doorbell_rd) && + ((doorbell_rd & ~NFAST_INT_DEVICE_READ_OK) && + (doorbell_rd & ~NFAST_INT_DEVICE_READ_FAILED))) { + dev_err(&ndev->pcidev->dev, + "%s: illegal bits in doorbell_rd %x", + __func__, doorbell_rd); + *handled = 1; + /* clear the register*/ + fsl_outl(ndev, FSL_OFFSET_DOORBELL_RD_STATUS, + NFAST_INT_DEVICE_CLR); + return 0; + } + if ((doorbell_wr) && + ((doorbell_wr & ~NFAST_INT_DEVICE_WRITE_OK) && + (doorbell_wr & ~NFAST_INT_DEVICE_WRITE_FAILED))) { + dev_err(&ndev->pcidev->dev, + "%s: illegal bits in doorbell_wr %x", + __func__, doorbell_wr); + /* clear the register*/ + fsl_outl(ndev, FSL_OFFSET_DOORBELL_WR_STATUS, + NFAST_INT_DEVICE_CLR); + *handled = 1; + return 0; + } + + /* service interrupts. + * if we made it here, the doorbell registers are all valid, + * so no need to check for their validity anymore. + */ + + if (doorbell_wr) { + fsl_outl(ndev, FSL_OFFSET_DOORBELL_WR_STATUS, + NFAST_INT_DEVICE_CLR); + ndev->stats.isr_write++; + nfp_write_complete(ndev, + doorbell_wr & + NFAST_INT_DEVICE_WRITE_OK ? + 1 : 0); + dev_notice(&ndev->pcidev->dev, + "%s: acknowledging write interrupt: ok = %d", + __func__, + doorbell_wr & NFAST_INT_DEVICE_WRITE_OK + ? 1 : 0); + } + + if (doorbell_rd) { + fsl_outl(ndev, FSL_OFFSET_DOORBELL_RD_STATUS, + NFAST_INT_DEVICE_CLR); + ndev->stats.isr_read++; + nfp_read_complete(ndev, + doorbell_rd & + NFAST_INT_DEVICE_READ_OK ? 1 : 0); + dev_notice(&ndev->pcidev->dev, + "%s: acknowledging read interrupt: ok = %d", + __func__, + doorbell_rd & NFAST_INT_DEVICE_READ_OK ? 1 : 0); + } + /* the doorbell_cs is being phased out in favor of polling since + * there were issues caused by this interrupt being issued from + * the card on its own when the driver was not even present. + * To maintain backwards compatibility, this code is being kept, + * but might be removed in the future. + */ + dev_notice(&ndev->pcidev->dev, "%s: doorbell_cs is: %x", + __func__, doorbell_cs); + if (doorbell_cs) { + fsl_outl(ndev, FSL_OFFSET_DOORBELL_CS_STATUS, + NFAST_INT_DEVICE_CLR); + fsl_check_complete(ndev, + doorbell_cs & + NFAST_INT_DEVICE_CHECK_OK ? + 0 : + NFP_HSM_STARTING); + dev_notice(&ndev->pcidev->dev, + "%s: acknowledging check interrupt: status:0x%x", + __func__, + doorbell_cs & NFAST_INT_DEVICE_CHECK_OK ? + 0 : + NFP_HSM_STARTING); + } + + doorbell_wr = fsl_inl(ndev, FSL_OFFSET_DOORBELL_WR_STATUS); + doorbell_rd = fsl_inl(ndev, FSL_OFFSET_DOORBELL_RD_STATUS); + doorbell_cs = fsl_inl(ndev, FSL_OFFSET_DOORBELL_CS_STATUS); + + dev_notice(&ndev->pcidev->dev, "%s: cs status in isr is: %x", + __func__, doorbell_cs); + } + + /* always report the interrupt as handled */ + *handled = 1; + + dev_notice(&ndev->pcidev->dev, "%s: exiting", __func__); + + return 0; +} + +/** + * Performs additional FSL-specific actions when opening a device. + * + * This routine returns an error if the device has not properly started. + * + * @param ctx device context (always the device itself). + * @returns 0 if successful, other value if error. + */ +static int fsl_open(struct nfp_dev *ctx) +{ + struct nfp_dev *ndev = ctx; + int ne; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* check for device */ + + ne = fsl_started(ndev, NFP_WITH_LOCK); + if (ne != 0) { + ndev->stats.ensure_fail++; + dev_err(&ndev->pcidev->dev, + "%s: error: device not started", __func__); + return ne; + } + + return 0; +} + +/** + * Performs additional FSL-specific actions when closing a device. + * + * @param ctx device context (always the device itself). + * @returns 0 if successful, other value if error. + */ +static int fsl_close(struct nfp_dev *ctx) +{ + struct nfp_dev *ndev = ctx; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + return 0; +} + +/** + * Sets control data. + * + * The device control register is writen directly. No doorbell style handshake + * is used. + * + * @param control control string to copy from. + * @param ctx device context (always the device itself). + * @returns 0 if successful, other value if error. + */ +static int fsl_set_control(const struct nfdev_control_str *control, + struct nfp_dev *ctx) +{ + struct nfp_dev *ndev = ctx; + int ne; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* check for device */ + + ndev = (struct nfp_dev *)ctx; + ne = fsl_started(ndev, NFP_WITH_LOCK); + if (ne != 0) { + dev_err(&ndev->pcidev->dev, + "%s: error: unable to set control", __func__); + return ne; + } + + /* set control (written immediately with no explicit + * synchronization with the firmware) + */ + + fsl_outl(ndev, FSL_OFFSET_REGISTER_CONTROL, control->control); + + return 0; +} + +/** + * Returns status data. + * + * The device status registers are read immediately. No doorbell style + * handshake is used. Without explicit synchronization, it is possible + * that an inconsistent state may be returned if the status is being + * updated by the firmware while simultaneously being read by the host. + * For example, the call could return an updated status word with a not + * as yet updated error string. This is likely a degenerate case. + * + * @param status string to copy into. + * @param ctx device context (always the device itself). + * @returns 0 if successful, other value if error. + */ +static int fsl_get_status(struct nfdev_status_str *status, struct nfp_dev *ctx) +{ + struct nfp_dev *ndev = ctx; + int ne; + u32 *error = (uint32_t *)status->error; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* check for device */ + + ndev = (struct nfp_dev *)ctx; + ne = fsl_started(ndev, NFP_WITH_LOCK); + if (ne != 0) { + dev_err(&ndev->pcidev->dev, + "%s: error: unable to get status", __func__); + return ne; + } + + /* get status (read immediately with no explicit synchronization + * with the firmware) + */ + + status->status = fsl_inl(ndev, FSL_OFFSET_REGISTER_STATUS); + error[0] = fsl_inl(ndev, FSL_OFFSET_REGISTER_ERROR_LO); + error[1] = fsl_inl(ndev, FSL_OFFSET_REGISTER_ERROR_HI); + + return 0; +} + +/** + * Initiates a device read request. + * + * @param addr 32-bit bus address used by DMA to push reply from device. + * @param len maximum length data to return. + * @param ctx device context (always the device itself). + * @returns 0 if read initiated, NFP_HSM_STARTING if device not ready, + * or other value if error. + */ +static int fsl_ensure_reading(dma_addr_t addr, + int len, struct nfp_dev *ctx, int lock_flag) +{ + struct nfp_dev *ndev = ctx; + __le32 hdr[3]; + __le32 tmp32; + int ne; + int hdr_len; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* check for device */ + + ndev = (struct nfp_dev *)ctx; + ne = fsl_started(ndev, lock_flag); + if (ne != 0) { + ndev->stats.ensure_fail++; + dev_err(&ndev->pcidev->dev, + "%s: error: unable to initiate read", __func__); + return ne; + } + + dev_notice(&ndev->pcidev->dev, "%s: ndev->bar[ndev->active_bar]= %p", + __func__, (void *)ndev->bar[ndev->active_bar]); + + /* send read request */ + + if (addr) { + dev_notice(&ndev->pcidev->dev, + "%s: requesting DMA reply to bus address %p", + __func__, (void *)addr); + hdr[0] = cpu_to_le32(NFPCI_JOB_CONTROL_PCI_PUSH); + hdr[1] = cpu_to_le32(len); + hdr[2] = cpu_to_le32(addr); + hdr_len = 3 * sizeof(hdr[0]); + } else { + hdr[0] = cpu_to_le32(NFPCI_JOB_CONTROL); + hdr[1] = cpu_to_le32(len); + hdr_len = 2 * sizeof(hdr[0]); + } + memcpy(ndev->bar[ndev->active_bar] + NFPCI_JOBS_RD_CONTROL, + (char const *)hdr, hdr_len); + + /* confirm read request */ + + memcpy((char *)hdr, + ndev->bar[ndev->active_bar] + NFPCI_JOBS_RD_LENGTH, + sizeof(hdr[0])); + tmp32 = cpu_to_le32(len); + if (hdr[0] != tmp32) { + dev_err(&ndev->pcidev->dev, + "%s: error: expected length not written (%08x != %08x)", + __func__, hdr[0], tmp32); + ndev->stats.ensure_fail++; + return -EIO; + } + + /* trigger read request */ + + fsl_outl(ndev, FSL_OFFSET_DOORBELL_RD_CMD, NFAST_INT_HOST_READ_REQUEST); + + ndev->stats.ensure++; + + dev_notice(&ndev->pcidev->dev, + "%s: requesting max %d bytes", __func__, len); + + return 0; +} + +/** + * Reads a device read reply. + * + * @param block data buffer to copy into. + * @param len maximum length of data to copy. + * @param ctx device context (always the device itself). + * @param rcnt returned actual # of bytes copied + * @returns 0 if read initiated, NFP_HSM_STARTING if device not ready, + * or other value if error. + */ +static int fsl_read(char *block, int len, struct nfp_dev *ctx, int *rcnt) +{ + struct nfp_dev *ndev = ctx; + int ne; + int cnt; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + *rcnt = 0; + + /* check for device */ + + ndev = (struct nfp_dev *)ctx; + ne = fsl_started(ndev, NFP_WITH_LOCK); + if (ne != 0) { + ndev->stats.read_fail++; + dev_err(&ndev->pcidev->dev, + "%s: error: unable to complete read", __func__); + return ne; + } + + /* receive reply length */ + + memcpy((char *)&cnt, + ndev->bar[ndev->active_bar] + NFPCI_JOBS_RD_LENGTH, + sizeof(cnt)); + cnt = le32_to_cpu(cnt); + dev_notice(&ndev->pcidev->dev, "%s: cnt=%u.", __func__, cnt); + if (cnt < 0 || cnt > len) { + ndev->stats.read_fail++; + dev_err(&ndev->pcidev->dev, "%s: error: bad byte count (%d) from device", + __func__, cnt); + return -EIO; + } + + /* receive data */ + + ne = copy_to_user(block, ndev->bar[ndev->active_bar] + + NFPCI_JOBS_RD_DATA, cnt) ? -EFAULT : 0; + if (ne != 0) { + ndev->stats.read_fail++; + dev_err(&ndev->pcidev->dev, "%s: error: copy_to_user failed", + __func__); + return ne; + } + + *rcnt = cnt; + ndev->stats.read_block++; + ndev->stats.read_byte += cnt; + dev_warn(&ndev->pcidev->dev, "%s: read %d bytes (std)", __func__, cnt); + + return 0; +} + +/** + * Initiates a device write request. + * + * @param addr 32-bit bus address used by DMA to pull request to device. + * @param block data buffer to copy from. + * @param len length of data to copy. + * @param ctx device context (always the device itself). + * @returns 0 if write successful, NFP_HSM_STARTING if device not + * ready, or other value if error. + */ +static int fsl_write(u32 addr, char const *block, int len, struct nfp_dev *ctx) +{ + struct nfp_dev *ndev = ctx; + __le32 hdr[3]; + int ne; + __le32 tmp32; + int hdr_len; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* check for device */ + + ndev = (struct nfp_dev *)ctx; + ne = fsl_started(ndev, NFP_WITH_LOCK); + if (ne != 0) { + ndev->stats.write_fail++; + dev_err(&ndev->pcidev->dev, + "%s: error: unable to initiate write", __func__); + return ne; + } + + if (addr == 0) { + /* std write */ + + dev_notice(&ndev->pcidev->dev, "%s: ndev->bar[ndev->active_bar]= %p", + __func__, (void *)ndev->bar[ndev->active_bar]); + dev_notice(&ndev->pcidev->dev, + "%s: block len %d", __func__, len); + + /* send write request */ + + ne = copy_from_user(ndev->bar[ndev->active_bar] + + NFPCI_JOBS_WR_DATA, block, len) + ? -EFAULT : 0; + if (ne != 0) { + ndev->stats.write_fail++; + dev_err(&ndev->pcidev->dev, + "%s: error: copy_from_user failed", __func__); + return ne; + } + + hdr[0] = cpu_to_le32(NFPCI_JOB_CONTROL); + hdr[1] = cpu_to_le32(len); + hdr_len = 2 * sizeof(hdr[0]); + memcpy(ndev->bar[ndev->active_bar] + NFPCI_JOBS_WR_CONTROL, + (char const *)hdr, hdr_len); + + /* confirm write request */ + + memcpy((char *)hdr, + ndev->bar[ndev->active_bar] + NFPCI_JOBS_WR_LENGTH, + sizeof(hdr[0])); + tmp32 = cpu_to_le32(len); + if (hdr[0] != tmp32) { + ndev->stats.write_fail++; + dev_err(&ndev->pcidev->dev, "%s: length not written (%08x != %08x)", + __func__, hdr[0], tmp32); + return -EIO; + } + } else { + /* dma write */ + + dev_notice(&ndev->pcidev->dev, "%s: ndev->bar[ndev->active_bar]= %p", + __func__, (void *)ndev->bar[ndev->active_bar]); + dev_notice(&ndev->pcidev->dev, + "%s: block len %d", __func__, len); + dev_notice(&ndev->pcidev->dev, "%s: pull from 0x%016x using DMA", + __func__, addr); + + /* submit write request */ + + hdr[0] = cpu_to_le32(NFPCI_JOB_CONTROL_PCI_PULL); + hdr[1] = cpu_to_le32(len); + hdr[2] = cpu_to_le32(addr); + hdr_len = 3 * sizeof(hdr[0]); + memcpy(ndev->bar[ndev->active_bar] + NFPCI_JOBS_WR_CONTROL, + (char const *)hdr, hdr_len); + + /* confirm write request */ + + memcpy((char *)hdr, + ndev->bar[ndev->active_bar] + NFPCI_JOBS_WR_LENGTH, + sizeof(hdr[0])); + tmp32 = cpu_to_le32(len); + if (hdr[0] != tmp32) { + ndev->stats.write_fail++; + dev_err(&ndev->pcidev->dev, + "%s: length not written (%08x != %08x)", + __func__, tmp32, hdr[0]); + return -EIO; + } + } + + /* trigger write */ + + fsl_outl(ndev, + FSL_OFFSET_DOORBELL_WR_CMD, NFAST_INT_HOST_WRITE_REQUEST); + + ndev->stats.write_block++; + ndev->stats.write_byte += len; + + dev_notice(&ndev->pcidev->dev, "%s: done", __func__); + return 0; +} + +/** FSL Sawshark T1022 device configuration. */ +const struct nfpcmd_dev fsl_t1022_cmddev = { "nCipher nShield Solo XC", + PCI_VENDOR_ID_FREESCALE, + PCI_DEVICE_ID_FREESCALE_T1022, + PCI_VENDOR_ID_NCIPHER, + PCI_SUBSYSTEM_ID_NFAST_REV1, + { 0, FSL_MEMSIZE, 0, 0, 0, 0 }, + NFP_CMD_FLG_NEED_MSI, + NFDEV_IF_PCI_PULL, + fsl_create, + fsl_destroy, + fsl_open, + fsl_close, + fsl_isr, + fsl_write, + fsl_read, + fsl_ensure_reading, + fsl_set_control, + fsl_get_status }; +/* end of file */ diff --git a/drivers/misc/ncipher/fsl.h b/drivers/misc/ncipher/fsl.h new file mode 100644 index 000000000000..d0fa32496215 --- /dev/null +++ b/drivers/misc/ncipher/fsl.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * + * Interface to the Solo XC's T1022 communication processor + * Copyright 2019 nCipher Security Ltd + * + */ + +#ifndef NFP_FSL_H +#define NFP_FSL_H + +#include <linux/io.h> +#include "solo.h" + +/* Always use a lock when 'we' call fsl_started */ +#define NFP_WITH_LOCK 1 + +/* PCI FSL definitions */ + +#ifndef PCI_VENDOR_ID_FREESCALE +#define PCI_VENDOR_ID_FREESCALE 0x1957 +#endif + +#ifndef PCI_DEVICE_ID_FREESCALE_T1022 +#define PCI_DEVICE_ID_FREESCALE_T1022 0x082c +#endif + +#ifndef PCI_VENDOR_ID_NCIPHER +#define PCI_VENDOR_ID_NCIPHER 0x0100 +#endif + +#ifndef PCI_SUBSYSTEM_ID_NFAST_REV1 +#define PCI_SUBSYSTEM_ID_NFAST_REV1 0x0100 +#endif + +#define FSL_CFG_SEC_CMD_STATUS 0x4C +#define FSL_CFG_CMD_MASTER 0x10 + +#define FSL_MEMBAR 1 + +/* NFAST extended PCI register definitions */ + +#define NFPCI_OFFSET_JOBS_DS 0x00020000 + +/* Interrupts from device to host */ + +#define NFAST_INT_DEVICE_CLR 0x00000000 +#define NFAST_INT_DEVICE_CHECK_OK 0x00000100 +#define NFAST_INT_DEVICE_CHECK_FAILED 0x00000200 +#define NFAST_INT_DEVICE_POLL 0x00000300 +#define NFAST_INT_DEVICE_PCI_DOWN 0x00000400 + +/* Interrupts from host to device */ + +#define NFAST_INT_HOST_CLR 0x00000000 + +/* PCI FSL register definitions */ + +#define FSL_LENGTH NFPCI_RAM_MINSIZE_JOBS +#define FSL_MEMSIZE NFPCI_RAM_MINSIZE_KERN + +#define FSL_DOORBELL_LOCATION (FSL_MEMSIZE - 0x100) + +#define FSL_OFFSET_DOORBELL_RD_CMD 0x00u +#define FSL_OFFSET_DOORBELL_WR_CMD 0x04u +#define FSL_OFFSET_DOORBELL_RD_STATUS 0x08u +#define FSL_OFFSET_DOORBELL_WR_STATUS 0x0Cu +#define FSL_OFFSET_DOORBELL_CS_STATUS 0x10u + +#define FSL_OFFSET_REGISTER_CONTROL 0x20u +#define FSL_OFFSET_REGISTER_STATUS 0x24u +#define FSL_OFFSET_REGISTER_ERROR_LO 0x28u +#define FSL_OFFSET_REGISTER_ERROR_HI 0x2Cu +#define FSL_OFFSET_DOORBELL_POLLING 0x30u + +#define FSL_MAGIC 0x12345678u + +/** Monitor firmware supports MOI control and error reporting */ +#define NFDEV_STATUS_MONITOR_MOI 0x0001 + +/** Application firmware supports MOI control and error reporting */ +#define NFDEV_STATUS_APPLICATION_MOI 0x0002 + +/** Application firmware running and supports error reporting */ +#define NFDEV_STATUS_APPLICATION_RUNNING 0x0004 + +/** + * Writes a 32 bit word across PCI to the FSL card. + * + * @param ndev command device. + * @param bar base address region id. + * @param offset offset in bytes from base address. + * @param value 32 bit value being written. + */ +static inline void fsl_outl(struct nfp_dev *ndev, int offset, u32 value) +{ + iowrite32(cpu_to_le32(value), ndev->bar[ndev->active_bar] + + FSL_DOORBELL_LOCATION + offset); +} + +/** + * Reads a 32 bit word across PCI from the FSL card. + * + * @param ndev command device. + * @param bar base address region id. + * @param offset offset in bytes from base address. + * @returns 32 bit value. + */ +static inline uint32_t fsl_inl(struct nfp_dev *ndev, int offset) +{ + return le32_to_cpu(ioread32(ndev->bar[ndev->active_bar] + + FSL_DOORBELL_LOCATION + offset)); +} + +#endif /* NFP_FSL_H */ + +/* end of file */ diff --git a/drivers/misc/ncipher/hostif.c b/drivers/misc/ncipher/hostif.c new file mode 100644 index 000000000000..1562c0e2fe07 --- /dev/null +++ b/drivers/misc/ncipher/hostif.c @@ -0,0 +1,1521 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * + * hostif.c: nCipher PCI HSM linux host interface + * Copyright 2019 nCipher Security Ltd + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/bitops.h> + +#include "solo.h" +#include "i21555.h" +#include "fsl.h" + +/* limits & sizes ------------------------------------------------ */ +#define NFP_READ_MAX (8 * 1024) +#define NFP_WRITE_MAX (8 * 1024) +#define NFP_READBUF_SIZE (NFP_READ_MAX + 8) +#define NFP_WRITEBUF_SIZE (NFP_WRITE_MAX + 8) +#define NFP_MAXDEV 16 + +/* other operating constants ------------------------------------- */ +#define NFP_TIMEOUT_SEC 20 +#define NFP_DMA_NBYTES_OFFSET (4) +#define NFP_DMA_ADDRESS_OFFSET (8) +#define NFP_DRVNAME "nCipher nFast PCI driver" +#define NFP_TIMEOUT ((NFP_TIMEOUT_SEC) * HZ) + +/* Interpretation of the bits of struct nfp_dev.rd_outstanding */ +#define WAIT_BIT 0 /* waiting for data */ +#define CMPLT_BIT 1 /* completing a read (got data or timing out) */ + +/* Used to determine which installed module we're looking at + * from its minor device number + */ +#define INODE_FROM_FILE(file) ((file)->f_path.dentry->d_inode) + +/* Major device type + * "nCipher nFast PCI crypto accelerator" in + * https://www.kernel.org/doc/html/v4.11/admin-guide/devices.html + */ +#define NFP_MAJOR 176 /* */ + +/* device list --------------------------------------------------- */ + +static struct nfp_dev *nfp_dev_list[NFP_MAXDEV]; +static int nfp_num_devices; +static struct class *nfp_class; +/*! @} */ + +/** + * @addtogroup modparams + * Module structures. + * @{ + */ + +/** + * NSHIELD_SOLO module interface version parameter. + * + * This value can be overridden when the module is loaded, for example: + * + * insmod nshield_solo.ko nfp_ifvers=<n> + * + * where n = 0 allows any supported interface, + * n > 0 allows only interface versions <= n. + * See nfdev-common.h for a list of supported interface versions. + * Specific card models may not support all interface versions. + */ +static int nfp_ifvers; + +MODULE_AUTHOR("nCipher"); +MODULE_DESCRIPTION("nCipher PCI HSM driver"); +module_param(nfp_ifvers, int, 0444); +MODULE_PARM_DESC(nfp_ifvers, "maximum interface version (1-2), or any (0)"); +MODULE_LICENSE("GPL"); + +/** @} */ + +/** + * @addtogroup fops + * NSHIELD_SOLO character device file operations. + * @{ + */ + +/** + * Polls an NSHIELD SOLO device. + * + * The kernel calls this function when a user tries to poll an NSHIELD SOLO + * device. The function returns a bit mask which indicates if the device is + * immediately readable or writable. A readable device will set + * (POLLIN | POLLRDNORM). A writable device will set (POLLOUT | POLLWRNORM). + * + * @param filep device file pointer. + * @param wait poll table pointer. + * @returns mask indicating if readable and/or writable. + */ +static u32 nfp_poll(struct file *file, poll_table *wait) +{ + struct nfp_dev *ndev; + u32 mask = 0; + int minor = MINOR(INODE_FROM_FILE(file)->i_rdev); + + if (minor >= NFP_MAXDEV) { + pr_err("%s: minor out of range.", __func__); + return -ENODEV; + } + + ndev = nfp_dev_list[minor]; + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* find ndev from minor */ + + poll_wait(file, &ndev->wr_queue, wait); + poll_wait(file, &ndev->rd_queue, wait); + + if (test_bit(0, &ndev->wr_ready)) + mask |= POLLOUT | POLLWRNORM; /* writeable */ + if (test_bit(0, &ndev->rd_ready)) + mask |= POLLIN | POLLRDNORM; /* readable */ + + dev_notice(&ndev->pcidev->dev, "%s: device is %swritable, %sreadable", + __func__, + mask & POLLOUT ? "" : "not ", mask & POLLIN ? "" : "not "); + + return mask; +} + +void nfp_write_complete(struct nfp_dev *ndev, int ok) +{ + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* could be executed simultaneously by more than one thread - + * e.g. from the write isr and from the nfp_write/timeout + * we don't want that to happen. + */ + if (test_and_set_bit(CMPLT_BIT, &ndev->wr_outstanding)) + return; + + if (!test_bit(WAIT_BIT, &ndev->wr_outstanding)) { + /* we can only get here if the write has already + * been completed + */ + dev_err(&ndev->pcidev->dev, + "%s: no write outstanding to complete; ignoring completion", + __func__); + clear_bit(CMPLT_BIT, &ndev->wr_outstanding); + return; + } + + /* complete write by waking waiting processes */ + ndev->wr_ok = ok; + + dev_notice(&ndev->pcidev->dev, "%s: write completed %sokay", + __func__, ok ? "" : "not "); + + /* make sure that write is complete before we clear wr_outstanding */ + smp_mb__before_atomic(); + clear_bit(CMPLT_BIT, &ndev->wr_outstanding); + clear_bit(WAIT_BIT, &ndev->wr_outstanding); + + /* wake up anyone waiting */ + + wake_up_all(&ndev->wr_queue); +} + +#define CREATE_TRACE_POINTS + +/** + * Writes to an NSHIELD SOLO device. + * + * Data in the user space buffer is written to the NSHIELD SOLO device. Any + * previous data is overwritten. An error is returned if not all + * bytes are written from the user space buffer. + * + * @param file device file pointer. + * @param buf pointer to a user space buffer. + * @param count size of user space buffer. + * @param off offset position (ignored). + * @returns actual number of bytes written or a negative value if an erroR + * occurred. + */ +static ssize_t nfp_write(struct file *file, char const __user *buf, + size_t count, loff_t *off) +{ + struct nfp_dev *ndev; + u32 addr; + int nbytes; + int minor; + int ne; + + /* find ndev from minor */ + minor = MINOR(INODE_FROM_FILE(file)->i_rdev); + if (minor >= NFP_MAXDEV) { + pr_err("%s: minor out of range.", __func__); + return -ENODEV; + } + + ndev = nfp_dev_list[minor]; + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* check max length requested */ + if (count <= 0 || NFP_WRITE_MAX < count) { + dev_err(&ndev->pcidev->dev, + "%s: invalid requested write length %lu", + __func__, count); + return -EINVAL; + } + + /* check if called before ready */ + if (!test_and_clear_bit(0, &ndev->wr_ready)) { + dev_err(&ndev->pcidev->dev, "%s: write called when not ready.", + __func__); + return -ENXIO; + } + set_bit(WAIT_BIT, &ndev->wr_outstanding); + + dev_notice(&ndev->pcidev->dev, "%s: writing %ld bytes", + __func__, count); + + addr = 0; + if (ndev->ifvers >= NFDEV_IF_PCI_PULL) { + dev_notice(&ndev->pcidev->dev, + "%s: copying %lu bytes to dma buffer", + __func__, count); + addr = ndev->write_dma; + nbytes = cpu_to_le32(count); + *(u32 *)(ndev->write_buf + NFP_DMA_NBYTES_OFFSET) = nbytes; + if (0 != + copy_from_user(ndev->write_buf + NFP_DMA_ADDRESS_OFFSET, + buf, count)) { + clear_bit(WAIT_BIT, &ndev->wr_outstanding); + set_bit(0, &ndev->wr_ready); + dev_err(&ndev->pcidev->dev, + "%s: copy from user space failed", __func__); + return -EIO; + } + } + + ne = ndev->cmddev->write_block(addr, buf, count, ndev->cmdctx); + if (ne != 0) { + nfp_write_complete(ndev, 0); + if (ne != -EAGAIN) + dev_err(&ndev->pcidev->dev, + "%s: write_block failed", __func__); + } + + while (test_bit(WAIT_BIT, &ndev->wr_outstanding)) { + if (!wait_event_timeout(ndev->wr_queue, + test_bit(WAIT_BIT, + &ndev->wr_outstanding) == 0, + NFP_TIMEOUT)) { + nfp_write_complete(ndev, 0); + set_bit(0, &ndev->wr_ready); + dev_err(&ndev->pcidev->dev, + "%s: module timed out", __func__); + return -ENXIO; + } + if (test_bit(WAIT_BIT, &ndev->wr_outstanding)) { + dev_err(&ndev->pcidev->dev, + "%s: handling spurious wake-up", __func__); + } + } + set_bit(0, &ndev->wr_ready); + + dev_warn(&ndev->pcidev->dev, "%s: returning %ld.", __func__, + ndev->wr_ok ? count : -EIO); + + if (!ndev->wr_ok) { + dev_err(&ndev->pcidev->dev, + "%s: device write failed", __func__); + return -EIO; + } + + dev_warn(&ndev->pcidev->dev, + "%s: wrote %ld bytes (%s)", __func__, count, + ndev->ifvers >= NFDEV_IF_PCI_PULL ? "dma" : "std"); + return count; +} + +void nfp_read_complete(struct nfp_dev *ndev, int ok) +{ + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* could be executed simultaneously by more than one thread - + * e.g. from the read isr and from the timeout + * we don't want that to happen. + */ + if (test_and_set_bit(CMPLT_BIT, &ndev->rd_outstanding)) + return; + + if (!test_bit(WAIT_BIT, &ndev->rd_outstanding)) { + /* we can only get here if the read has already been completed + * and no new ENSUREREADING request has been received since + */ + dev_err(&ndev->pcidev->dev, + "%s: no read outstanding to complete; ignoring completion", + __func__); + clear_bit(CMPLT_BIT, &ndev->rd_outstanding); + return; + } + + /* in case the timer has not expired */ + del_timer(&ndev->rd_timer); + + ndev->rd_ok = ok; + set_bit(0, &ndev->rd_ready); + + dev_notice(&ndev->pcidev->dev, "%s: read completed %sokay", + __func__, ok ? "" : "not "); + + /* make sure that rd_ready is set before we clear rd_outstanding */ + smp_mb__before_atomic(); + clear_bit(CMPLT_BIT, &ndev->rd_outstanding); + clear_bit(WAIT_BIT, &ndev->rd_outstanding); + + /* wake up anyone waiting */ + + wake_up_all(&ndev->rd_queue); +} + +static void nfp_read_timeout(struct timer_list *t) +{ + struct nfp_dev *ndev = from_timer(ndev, t, rd_timer); + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + nfp_read_complete(ndev, 0); +} + +/** + * Reads from an NSHIELD SOLO device. + * + * Data is read from the NSHIELD SOLO device into the user space buffer. + * The read removes the data from the device. An error is returned is not + * all the bytes are read from the user space buffer. + * + * @param file device file pointer. + * @param buf pointer to a user space buffer. + * @param count maximum size of user space buffer. + * @param off offset position (ignored). + * @returns actual number of bytes read or a negative + * value if an error occurred. + */ +static ssize_t nfp_read(struct file *file, char __user *buf, size_t count, + loff_t *off) +{ + struct nfp_dev *ndev; + int nbytes; + int minor; + int ne; + + minor = MINOR(INODE_FROM_FILE(file)->i_rdev); + if (minor >= NFP_MAXDEV) { + pr_err("%s: minor out of range.", __func__); + return -ENODEV; + } + + ndev = nfp_dev_list[minor]; + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + /* check user space buffer */ + if (!access_ok(buf, count)) { + dev_err(&ndev->pcidev->dev, + "%s: user space verify failed.", __func__); + return -EFAULT; + } + + /* check max length requested */ + + if (count <= 0 || NFP_READ_MAX < count) { + dev_err(&ndev->pcidev->dev, + "%s: invalid requested max read length %lu", + __func__, count); + return -EINVAL; + } + + if (!test_and_clear_bit(0, &ndev->rd_ready)) { + dev_err(&ndev->pcidev->dev, + "%s: read called when not ready.", __func__); + return -EIO; + } + nbytes = 0; + + /* check if read was ok */ + + if (!ndev->rd_ok) { + dev_err(&ndev->pcidev->dev, "%s: read failed", __func__); + return -EIO; + } + + /* finish read */ + + if (ndev->ifvers >= NFDEV_IF_PCI_PUSH) { + nbytes = *(u32 *)(ndev->read_buf + NFP_DMA_NBYTES_OFFSET); + nbytes = le32_to_cpu(nbytes); + dev_notice(&ndev->pcidev->dev, + "%s: nbytes %d", __func__, nbytes); + if (nbytes < 0 || nbytes > count) { + dev_err(&ndev->pcidev->dev, + "%s: bad byte count (%d) from device", + __func__, nbytes); + return -EIO; + } + if (copy_to_user(buf, ndev->read_buf + NFP_DMA_ADDRESS_OFFSET, + nbytes) != 0) { + dev_err(&ndev->pcidev->dev, + "%s: copy to user space failed", __func__); + return -EIO; + } + } else { + nbytes = 0; + ne = ndev->cmddev->read_block(buf, count, ndev->cmdctx, + (void *)&nbytes); + if (ne != 0) { + dev_err(&ndev->pcidev->dev, + "%s: device read failed", __func__); + return ne; + } + } + + if (nbytes > NFP_READ_MAX) { + dev_err(&ndev->pcidev->dev, + "%s: read reply overflow: %d > %d max", + __func__, nbytes, NFP_READ_MAX); + return -EIO; + } + + dev_warn(&ndev->pcidev->dev, "%s: read %d bytes (%s)", __func__, nbytes, + ndev->ifvers >= NFDEV_IF_PCI_PUSH ? "dma" : "std"); + return nbytes; +} + +static int nfp_alloc_pci_push(struct nfp_dev *ndev) +{ + /* allocate resources needed for PCI Push, + * if not already allocated. + * return True if successful + */ + if (!ndev->read_buf) { + ndev->read_buf = + kzalloc(NFP_READBUF_SIZE, GFP_KERNEL | GFP_DMA); + if (!ndev->read_buf) + return -ENOMEM; + + ndev->read_dma = + dma_map_single(&ndev->pcidev->dev, ndev->read_buf, + NFP_READBUF_SIZE, DMA_BIDIRECTIONAL); + if (dma_mapping_error(&ndev->pcidev->dev, ndev->read_dma)) { + dev_err(&ndev->pcidev->dev, + "dma_mapping_error found after attempting dma_map_single"); + kfree(ndev->read_buf); + ndev->read_buf = NULL; + ndev->read_dma = 0; + } + } + return ndev->read_buf ? 1 : 0; +} + +static int nfp_alloc_pci_pull(struct nfp_dev *ndev) +{ + /* allocate resources needed for PCI Pull, + * if not already allocated. + * return True if successful + */ + if (!ndev->write_buf) { + ndev->write_buf = + kzalloc(NFP_WRITEBUF_SIZE, GFP_KERNEL | GFP_DMA); + if (!ndev->write_buf) + return -ENOMEM; + + ndev->write_dma = dma_map_single(&ndev->pcidev->dev, + ndev->write_buf, + NFP_WRITEBUF_SIZE, + DMA_BIDIRECTIONAL); + if (dma_mapping_error(&ndev->pcidev->dev, ndev->write_dma)) { + dev_err(&ndev->pcidev->dev, + "dma_mapping_error found after attempting dma_map_single"); + kfree(ndev->write_buf); + ndev->write_buf = NULL; + ndev->write_dma = 0; + } + } + return ndev->write_buf ? 1 : 0; +} + +static void nfp_free_pci_push(struct nfp_dev *ndev) +{ + /* free resources allocated to PCI Push */ + if (ndev->read_buf) { + dma_unmap_single(&ndev->pcidev->dev, ndev->read_dma, + NFP_READBUF_SIZE, DMA_BIDIRECTIONAL); + kfree(ndev->read_buf); + ndev->read_buf = NULL; + ndev->read_dma = 0; + } +} + +static void nfp_free_pci_pull(struct nfp_dev *ndev) +{ + /* free resources allocated to PCI Pull */ + if (ndev->write_buf) { + dma_unmap_single(&ndev->pcidev->dev, ndev->write_dma, + NFP_WRITEBUF_SIZE, DMA_BIDIRECTIONAL); + kfree(ndev->write_buf); + ndev->write_buf = NULL; + ndev->write_dma = 0; + } +} + +/* + * Sets device interface version. + * + * @param ndev an NSHIELD SOLO device. + * @param ifvers interface version. + * @returns interface version actually set. + */ +static int nfp_set_ifvers(struct nfp_dev *ndev, int ifvers) +{ + int max_ifvers; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return 0; /* no interface version */ + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + max_ifvers = ndev->cmddev->max_ifvers; + if (nfp_ifvers != 0 && max_ifvers > nfp_ifvers) + max_ifvers = nfp_ifvers; + + /* on any error, ifvers remains unchanged */ + if (ifvers < 0 || ifvers > max_ifvers) { + /* invalid nfp_ifvers: set to max as fallback */ + dev_err(&ndev->pcidev->dev, "%s: %d out of allowable range [0:%d]", + __func__, ifvers, max_ifvers); + return ndev->ifvers; + } + + if (ifvers == 0 && + ndev->cmddev->deviceid == PCI_DEVICE_ID_FREESCALE_T1022) { + /* default ifvers: set to max for ngsolo not for legacy! + * The legacy card needs it set to 0 when in Maintenance + * mode and then the hardserver steps it up to ifvers 2 + * when switching back to Operational mode. Solo XC starts + * at the max (3) whenever possible. + */ + ifvers = max_ifvers; + } + + if (ifvers >= NFDEV_IF_PCI_PUSH) { + if (!nfp_alloc_pci_push(ndev)) { + dev_err(&ndev->pcidev->dev, + "%s: can't set ifvers %d as resources not available", + __func__, ifvers); + return ndev->ifvers; + } + } else { + nfp_free_pci_push(ndev); + } + + if (ifvers >= NFDEV_IF_PCI_PULL) { + if (!nfp_alloc_pci_pull(ndev)) { + dev_err(&ndev->pcidev->dev, + "%s: can't set ifvers %d as resources not available", + __func__, ifvers); + return ndev->ifvers; + } + } else { + nfp_free_pci_pull(ndev); + } + + ndev->ifvers = ifvers; + dev_warn(&ndev->pcidev->dev, + "%s: setting ifvers = %d", __func__, ifvers); + + return ifvers; +} + +/** + * Performs an NSHIELD SOLO device IOCTL call. + * + * @param inode device inode pointer. + * @param file device file pointer. + * @param cmd command id. + * @param arg command argument. + * @returns 0 if successful. + */ +static int nfp_ioctl(struct inode *inode, + struct file *file, + u32 cmd, + u64 arg) +{ + struct nfp_dev *ndev; + int minor; + + /* find ndev from minor */ + minor = MINOR(INODE_FROM_FILE(file)->i_rdev); + + if (minor >= NFP_MAXDEV) { + pr_err("%s: minor out of range.", __func__); + return -ENODEV; + } + + ndev = nfp_dev_list[minor]; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + switch (cmd) { + case NFDEV_IOCTL_ENQUIRY: { + struct nfdev_enquiry_str enq_data; + int err = -EIO; + + dev_dbg(&ndev->pcidev->dev, "%s: enquiry", __func__); + enq_data.busno = ndev->busno; + enq_data.slotno = ndev->slotno; + if ((void *)arg) { + err = copy_to_user((void __user *)arg, &enq_data, + sizeof(enq_data)) ? -EFAULT : 0; + if (err) { + dev_err(&ndev->pcidev->dev, + "%s: copy to user space failed.", + __func__); + return err; + } + } else { + dev_err(&ndev->pcidev->dev, + "%s enquiry: arg pointer is NULL!", __func__); + return err; + } + } break; + case NFDEV_IOCTL_ENSUREREADING: + case NFDEV_IOCTL_ENSUREREADING_BUG3349: { + dma_addr_t addr; + u32 len; + int err = -EIO; + int ne; + + dev_dbg(&ndev->pcidev->dev, "%s: ensure reading", __func__); + + /* get and check max length */ + if ((void *)arg) { + err = copy_from_user((void *)&len, (void __user *)arg, + sizeof(u32)) ? -EFAULT : 0; + if (err) { + dev_err(&ndev->pcidev->dev, + "%s: ensure reading: copy from user space failed.", + __func__); + return err; + } + /* signal a read to the module */ + dev_warn(&ndev->pcidev->dev, + "%s: signalling read request to module, len = %x.", + __func__, len); + if (len > NFP_READ_MAX) { + dev_err(&ndev->pcidev->dev, "%s: len > %x = %x.", + __func__, NFP_READ_MAX, len); + return -EINVAL; + } + } else { + dev_err(&ndev->pcidev->dev, + "%s ensure reading: arg pointer is NULL!", + __func__); + return err; + } + + if (len <= 0 || NFP_READ_MAX < len) { + dev_err(&ndev->pcidev->dev, + "%s: ensure reading: invalid max length %d/%d", + __func__, len, NFP_READ_MAX); + err = -EINVAL; + return err; + } + + /* check if okay to start read */ + + if (test_and_set_bit(WAIT_BIT, &ndev->rd_outstanding)) { + dev_err(&ndev->pcidev->dev, + "%s: ensure reading: another read is outstanding", + __func__); + return -EBUSY; + } + + dev_warn(&ndev->pcidev->dev, + "%s: ndev->rd_outstanding=1", __func__); + + /* start read ready timeout */ + + mod_timer(&ndev->rd_timer, jiffies + (NFP_TIMEOUT_SEC * HZ)); + + dev_warn(&ndev->pcidev->dev, "%s: read request", __func__); + /* start read */ + + addr = (ndev->ifvers < NFDEV_IF_PCI_PUSH) ? 0 : ndev->read_dma; + dev_notice(&ndev->pcidev->dev, + "%s: ensure reading: read request with ifvers=%d addr=%p", + __func__, ndev->ifvers, (void *)addr); + + ne = ndev->cmddev->ensure_reading(addr, len, ndev->cmdctx, 1); + + if (ne != 0) { + dev_err(&ndev->pcidev->dev, + "%s: ensure reading: read request failed", + __func__); + del_timer_sync(&ndev->rd_timer); + /* make sure that del_timer_sync is done before + * we clear rd_outstanding + */ + smp_mb__before_atomic(); + clear_bit(WAIT_BIT, &ndev->rd_outstanding); + return -EIO; + } + } break; + + case NFDEV_IOCTL_PCI_IFVERS: { + int vers, err = -EIO; + + dev_dbg(&ndev->pcidev->dev, "%s: set ifvers", __func__); + if ((void *)arg) { + err = copy_from_user(&vers, (void __user *)arg, + sizeof(vers)) ? -EFAULT : 0; + if (err) { + dev_err(&ndev->pcidev->dev, + "%s: set ifvers: copy from user space failed.", + __func__); + return err; + } + } else { + dev_err(&ndev->pcidev->dev, + "%s ifvers: arg pointer is NULL!", + __func__); + return err; + } + + if (test_bit(WAIT_BIT, &ndev->rd_outstanding)) { + dev_err(&ndev->pcidev->dev, + "%s: set ifvers: unable to set interface version while read outstanding", + __func__); + return -EIO; + } + + nfp_set_ifvers(ndev, vers); + } break; + + case NFDEV_IOCTL_CHUPDATE: + dev_err(&ndev->pcidev->dev, + "%s: channel update ignored", __func__); + break; + + case NFDEV_IOCTL_STATS: { + int err = -EIO; + + dev_dbg(&ndev->pcidev->dev, "%s: stats", __func__); + if ((void *)arg) { + err = copy_to_user((void __user *)arg, &ndev->stats, + sizeof(struct nfdev_stats_str)) + ? -EFAULT : 0; + if (err) { + dev_err(&ndev->pcidev->dev, + "%s: stats: copy to user space failed.", + __func__); + return err; + } + } else { + dev_err(&ndev->pcidev->dev, + "%s stats: arg pointer is NULL!", __func__); + return err; + } + } break; + + case NFDEV_IOCTL_CONTROL: { + int err = -EIO; + struct nfdev_control_str control; + + dev_dbg(&ndev->pcidev->dev, "%s: control", __func__); + if ((void *)arg) { + err = copy_from_user(&control, (void __user *)arg, + sizeof(control)) ? -EFAULT : 0; + if (err) { + dev_err(&ndev->pcidev->dev, + "%s: control: copy from user space failed.", + __func__); + return err; + } + } else { + dev_err(&ndev->pcidev->dev, + "%s control: arg pointer is NULL!", __func__); + return err; + } + if (!ndev->cmddev->setcontrol) { + dev_warn(&ndev->pcidev->dev, + "%s: control: set control not supported for this device: ignored", + __func__); + return -EINVAL; + } + dev_notice(&ndev->pcidev->dev, + "%s: control: updating HSM control register to 0x%x.", + __func__, + control.control); + + return ndev->cmddev->setcontrol(&control, ndev->cmdctx); + } break; + + case NFDEV_IOCTL_STATUS: { + int err = -EIO; + struct nfdev_status_str status; + + dev_dbg(&ndev->pcidev->dev, "%s: status", __func__); + + if (!ndev->cmddev->getstatus) { + dev_warn(&ndev->pcidev->dev, + "%s: status not supported for this device: ignored", + __func__); + return -EINVAL; + } + err = ndev->cmddev->getstatus(&status, + ndev->cmdctx); + + if (err) + return err; + + if ((void *)arg) { + err = copy_to_user((void __user *)arg, &status, + sizeof(status)) ? -EFAULT : 0; + if (err) { + dev_err(&ndev->pcidev->dev, + "%s: status: copy from user space failed.", + __func__); + return err; + } + } else { + dev_err(&ndev->pcidev->dev, + "%s status: arg pointer is NULL!", + __func__); + return err; + } + dev_notice(&ndev->pcidev->dev, + "%s: read status: 0x%x, error: 0x%02x%02x%02x%02x%02x%02x%02x%02x", + __func__, + status.status, status.error[0], status.error[1], + status.error[2], status.error[3], status.error[4], + status.error[5], status.error[6], status.error[7]); + } break; + + default: { + dev_err(&ndev->pcidev->dev, "%s: unknown ioctl.", __func__); + return -EINVAL; + } break; + } + + return 0; +} + +/** + * Performs an NSHIELD SOLO device unlocked IOCTL call. + * + * @param file device file pointer. + * @param cmd command id. + * @param arg command argument. + * @returns 0 if successful. + */ +static long nfp_unlocked_ioctl(struct file *file, + u32 cmd, + unsigned long arg) +{ + long ret; + int minor; + struct nfp_dev *ndev; + + minor = MINOR(INODE_FROM_FILE(file)->i_rdev); + if (minor >= NFP_MAXDEV) { + pr_err("%s: minor out of range.", __func__); + return -ENODEV; + } + + ndev = nfp_dev_list[minor]; + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + mutex_lock(&ndev->ioctl_mutex); + ret = nfp_ioctl(NULL, file, cmd, arg); + mutex_unlock(&ndev->ioctl_mutex); + + dev_dbg(&ndev->pcidev->dev, "%s: left", __func__); + return ret; +} + +static irqreturn_t nfp_isr(int irq, void *context) +{ + struct nfp_dev *ndev = (struct nfp_dev *)context; + int handled; + int ne; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return IRQ_NONE; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + ne = ndev->cmddev->isr(ndev->cmdctx, &handled); + + if (ne != 0) + dev_err(&ndev->pcidev->dev, "%s: cmddev isr failed (%d)", + __func__, ne); + + return IRQ_RETVAL(handled); +} + +/** + * Opens an NSHIELD SOLO device. + * + * The kernel calls this function when a user tries to open an + * NSHIELD SOLO device. It is an error to attempt to open an + * already opened device. + * + * @param inode device inode pointer. + * @param file device file pointer. + * @return 0 if successful. + */ +static int nfp_open(struct inode *inode, struct file *file) +{ + struct nfp_dev *ndev; + int ne; + int minor = MINOR(INODE_FROM_FILE(file)->i_rdev); + + /* find ndev */ + + if (minor >= NFP_MAXDEV) { + pr_err("%s: minor out of range.", __func__); + return -ENODEV; + } + + ndev = nfp_dev_list[minor]; + if (!ndev) { + pr_err("%s: cannot find dev %d.", __func__, minor); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + dev_notice(&ndev->pcidev->dev, + "%s: opening file at %p.", __func__, file); + + /* check if already open */ + + if (atomic_read(&ndev->busy)) { + dev_err(&ndev->pcidev->dev, "%s: device %s busy", __func__, + pci_name(ndev->pcidev)); + return -EBUSY; + } + atomic_set(&ndev->busy, 1); + + /* drop any old data */ + + clear_bit(0, &ndev->rd_ready); + + /* set interface to module default */ + + if (ndev->cmddev->deviceid == PCI_DEVICE_ID_FREESCALE_T1022) + nfp_set_ifvers(ndev, NFDEV_IF_PCI_PULL); + else + nfp_set_ifvers(ndev, NFDEV_IF_STANDARD); + + dev_notice(&ndev->pcidev->dev, + "%s: ifvers set to %d", __func__, ndev->ifvers); + + /* open device */ + + ne = ndev->cmddev->open(ndev->cmdctx); + if (ne != 0) { + dev_err(&ndev->pcidev->dev, "%s: device open failed: error %d", + __func__, ne); + atomic_set(&ndev->busy, 0); + return ne; + } + + dev_warn(&ndev->pcidev->dev, "%s: device %s open", + __func__, pci_name(ndev->pcidev)); + + return 0; +} + +/** + * Releases an NSHIELD SOLO device. + * + * The kernel calls this function when a user tries to close an + * NSHIELD SOLO device. It is an error to attempt to close an + * already closed device. + * + * @param node device inode pointer. + * @param file device file pointer. + * @returns 0 if successful. + */ +static int nfp_release(struct inode *node, struct file *file) +{ + struct nfp_dev *ndev; + long timeout; + int ne; + int minor = MINOR(INODE_FROM_FILE(file)->i_rdev); + + /* find ndev from minor */ + if (minor >= NFP_MAXDEV) { + pr_err("%s: minor out of range.", __func__); + return(-ENODEV); + } + + ndev = nfp_dev_list[minor]; + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + + dev_warn(&ndev->pcidev->dev, "%s: closing file at %p.", __func__, file); + + { + wait_queue_entry_t wait; + + timeout = 1; + init_waitqueue_entry(&wait, current); + current->state = TASK_UNINTERRUPTIBLE; + add_wait_queue(&ndev->rd_queue, &wait); + if (test_bit(WAIT_BIT, &ndev->rd_outstanding)) { + dev_dbg(&ndev->pcidev->dev, + "%s: read outstanding", __func__); + timeout = schedule_timeout(NFP_TIMEOUT); + dev_dbg(&ndev->pcidev->dev, + "%s: finished waiting", __func__); + } + current->state = TASK_RUNNING; + remove_wait_queue(&ndev->rd_queue, &wait); + if (!timeout) { + dev_err(&ndev->pcidev->dev, + "%s: outstanding read timed out", __func__); + } + } + + atomic_set(&ndev->busy, 1); + if (test_bit(WAIT_BIT, &ndev->rd_outstanding)) { + del_timer_sync(&ndev->rd_timer); + /* make sure that del_timer_sync is done before + * we clear rd_outstanding + */ + smp_mb__before_atomic(); + clear_bit(WAIT_BIT, &ndev->rd_outstanding); + } + atomic_set(&ndev->busy, 0); + + /* close device */ + + ne = ndev->cmddev->close(ndev->cmdctx); + if (ne != 0) { + dev_err(&ndev->pcidev->dev, + "%s: device close failed", __func__); + return ne; + } + + return(0); +} + +/** + * NSHIELD SOLO character device file operations table. + */ +static const struct file_operations nfp_fops = { .owner = THIS_MODULE, + .poll = nfp_poll, + .write = nfp_write, + .read = nfp_read, + .unlocked_ioctl = nfp_unlocked_ioctl, + .open = nfp_open, + .release = nfp_release, +}; + +/** + * @addtogroup devmgr + * NSHIELD SOLO device management. + * @{ + */ + +/* device setup -------------------------------------------------- */ + +static void nfp_dev_destroy(struct nfp_dev *ndev, struct pci_dev *pci_dev) +{ + int i; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return; + } + + dev_dbg(&ndev->pcidev->dev, "%s: entered", __func__); + if (ndev) { + nfp_free_pci_push(ndev); + nfp_free_pci_pull(ndev); + + if (ndev->irq) { + dev_notice(&ndev->pcidev->dev, "%s: freeing irq, %x", + __func__, ndev->irq); + free_irq(ndev->irq, ndev); + } + for (i = 0; i < 6; i++) + if (ndev->bar[i]) { + dev_notice(&ndev->pcidev->dev, + "%s: freeing MEM BAR, %d", + __func__, i); + release_mem_region(pci_resource_start(pci_dev, + i), + pci_resource_len(pci_dev, + i)); + iounmap(ndev->bar[i]); + } + dev_notice(&ndev->pcidev->dev, "%s: freeing ndev", __func__); + kfree(ndev); + } +} + +static int nfp_setup(const struct nfpcmd_dev *cmddev, u8 bus, u8 slot, + u32 bar[6], u32 irq_line, struct pci_dev *pcidev) +{ + struct nfp_dev *ndev = 0; + int ne; + int i; + + dev_warn(&pcidev->dev, + "%s: Found '%s' at bus %x, slot %x, irq %d.", + __func__, cmddev->name, bus, slot, irq_line); + + if (nfp_num_devices >= NFP_MAXDEV) { + dev_err(&pcidev->dev, + "%s: minor out of range.", __func__); + goto fail_continue; + } + + ndev = kzalloc(sizeof(*ndev), GFP_KERNEL); + if (!ndev) { + /* logged by the allocator */ + goto fail_continue; + } + dev_warn(&pcidev->dev, + "%s: allocated device structure.", __func__); + + ndev->busno = bus; + ndev->pcidev = pcidev; + ndev->slotno = slot; + ndev->cmddev = cmddev; + + for (i = 0; i < NFP_BARSIZES_COUNT; i++) { + int map_bar_size = cmddev->bar_sizes[i] & NFP_BARSIZES_MASK; + int bar_flags = cmddev->bar_sizes[i] & ~NFP_BARSIZES_MASK; + + if (map_bar_size) { + if (!request_mem_region(pci_resource_start(pcidev, i), + pci_resource_len(pcidev, i), + cmddev->name)) { + dev_err(&ndev->pcidev->dev, + "%s: request_mem_region failed, %llx %llx %d (%s)", + __func__, + pci_resource_start(pcidev, i), + pci_resource_len(pcidev, i), i, + cmddev->name); + goto fail_continue; + } + + if (bar_flags & PCI_BASE_ADDRESS_SPACE_PREFETCHABLE) { + ndev->bar[i] = + ioremap(bar[i], map_bar_size); + } else { + ndev->bar[i] = + ioremap_nocache(bar[i], map_bar_size); + } + + if (!ndev->bar[i]) { + dev_err(&ndev->pcidev->dev, + "%s: unable to map memory BAR %d, (0x%x).", + __func__, i, bar[i]); + goto fail_continue; + } + } + } + + mutex_init(&ndev->ioctl_mutex); + + init_waitqueue_head(&ndev->wr_queue); + init_waitqueue_head(&ndev->rd_queue); + + set_bit(0, &ndev->wr_ready); + + ne = ndev->cmddev->create(ndev); + if (ne != 0) { + dev_err(&ndev->pcidev->dev, + "%s: failed to create command device (%d)", + __func__, ne); + goto fail_continue; + } + + if (request_irq(irq_line, nfp_isr, IRQF_SHARED, cmddev->name, ndev)) { + dev_err(&ndev->pcidev->dev, + "%s: unable to claim interrupt.", __func__); + goto fail_continue; + } + ndev->irq = irq_line; + + memset(&ndev->stats, 0, sizeof(ndev->stats)); + + pci_set_drvdata(pcidev, ndev); + + /* setup timeout timer */ + timer_setup(&ndev->rd_timer, nfp_read_timeout, 0); + mod_timer(&ndev->rd_timer, jiffies + (NFP_TIMEOUT_SEC * HZ)); + + nfp_dev_list[nfp_num_devices] = ndev; + device_create(nfp_class, NULL, /* parent */ + MKDEV(NFP_MAJOR, nfp_num_devices), NULL, /* drvdata */ + "nshield_solo%d", nfp_num_devices); + dev_warn(&ndev->pcidev->dev, "%s: nfp_num_devices= %d, ndev = %p.", + __func__, nfp_num_devices, ndev); + nfp_num_devices++; + return 1; + +fail_continue: + nfp_dev_destroy(ndev, pcidev); + + return 0; +} + +/* device probing ---------------------------------------------------------- */ + +/** + * Adds a PCI device to the module. The PCI subsystem calls this function + * when a PCI device is found. + * + * @param pcidev PCI device. + * @param id PCI device ids. + * @returns 0 if successful. + */ +static int nfp_pci_probe(struct pci_dev *pcidev, + struct pci_device_id const *id) +{ + int i; + u32 bar[6]; + const struct nfpcmd_dev *nfp_drvlist[] = { &i21555_cmddev, + &fsl_t1022_cmddev, NULL }; + const struct nfpcmd_dev *cmddev = nfp_drvlist[id->driver_data]; + u64 iosize; + u32 irq_line; + int pos = 0u; + int err = 0; + + if (!pcidev || !id) { + pr_err("%s: pcidev or id was NULL!", __func__); + return -ENODEV; + } + + dev_notice(&pcidev->dev, "%s: probing PCI device %s", + __func__, pci_name(pcidev)); + + /* enable the device */ + err = pci_enable_device(pcidev); + if (err) { + dev_err(&pcidev->dev, "%s: pci_enable_device failed", __func__); + err = -ENODEV; + goto probe_err; + } + + pci_set_master(pcidev); + + /* save PCI device info */ + irq_line = pcidev->irq; + for (i = 0; i < NFP_BARSIZES_COUNT; ++i) { + iosize = cmddev->bar_sizes[i] & NFP_BARSIZES_MASK; + if (pci_resource_len(pcidev, i) < iosize) { + dev_err(&pcidev->dev, + "%s: %s region request overflow: bar %d, requested %llx, maximum %llx", + __func__, pci_name(pcidev), i, iosize, + pci_resource_len(pcidev, i)); + err = -ENODEV; + goto probe_err; + } + bar[i] = pci_resource_start(pcidev, i); + } + + if (cmddev->flags & NFP_CMD_FLG_NEED_MSI) { + pos = pci_find_capability(pcidev, PCI_CAP_ID_MSI); + if (!pos) { + dev_err(&pcidev->dev, "%s: %s MSI not supported", + __func__, pci_name(pcidev)); + err = -ENODEV; + goto probe_err; + } + dev_err(&pcidev->dev, "%s: %s MSI support at %d", + __func__, pci_name(pcidev), pos); + + err = pci_enable_msi(pcidev); + if (err) { + dev_err(&pcidev->dev, + "%s: %s unable to enable MSI", + __func__, pci_name(pcidev)); + goto probe_err; + } + + /* IRQ vector changes if MSI is enabled. */ + irq_line = pcidev->irq; + dev_notice(&pcidev->dev, "%s: %s MSI IRQ at %d", + __func__, pci_name(pcidev), irq_line); + } + + dev_warn(&pcidev->dev, "%s: devname %s, slotname %s, busname %s", + __func__, "", pci_name(pcidev), pcidev->bus->name); + + err = nfp_setup(cmddev, pcidev->bus->number, PCI_SLOT(pcidev->devfn), + bar, irq_line, pcidev); + if (!err) { + err = -ENODEV; + goto probe_err; + } + + return 0; + +probe_err: + pci_disable_msi(pcidev); + pci_clear_master(pcidev); + pci_disable_device(pcidev); + return err; +} + +/** + * Removes a PCI device from the module. The PCI subsystem calls this function + * when a PCI device is removed. + * + * @param pcidev PCI device. + * @returns 0 if successful. + */ +static void nfp_pci_remove(struct pci_dev *pcidev) +{ + int index; + struct nfp_dev *ndev; + + dev_err(&pcidev->dev, "%s: removing PCI device %s", + __func__, pci_name(pcidev)); + + /* find existing device */ + + ndev = pci_get_drvdata(pcidev); + if (!ndev) { + dev_err(&pcidev->dev, + "%s: no NSHIELD SOLO device associated with this PCI device", + __func__); + return; + } + + /* destroy common device */ + + if (ndev->cmddev) + ndev->cmddev->destroy(ndev->cmdctx); + + nfp_dev_destroy(ndev, pcidev); + + pci_disable_msi(pcidev); + pci_clear_master(pcidev); + pci_disable_device(pcidev); + + index = 0; + while (index < NFP_MAXDEV) { + if (nfp_dev_list[index] == ndev) { + nfp_dev_list[index] = NULL; + device_destroy(nfp_class, MKDEV(NFP_MAJOR, index)); + } + index++; + } +} + +/** + * PCI device ID table. We use the driver_data field to hold an index into + * nfp_drvlist, so bear than in mind when editing either. + */ +static struct pci_device_id nfp_pci_tbl[] = { + { + PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_21555, + PCI_VENDOR_ID_NCIPHER, PCI_SUBSYSTEM_ID_NFAST_REV1, 0, + 0, /* Ignore class */ + 0 /* Index into nfp_drvlist */ + }, + { + PCI_VENDOR_ID_FREESCALE, PCI_DEVICE_ID_FREESCALE_T1022, + PCI_VENDOR_ID_NCIPHER, PCI_SUBSYSTEM_ID_NFAST_REV1, 0, + 0, /* Ignore class */ + 1 /* Index into nfp_drvlist */ + }, + { + 0, + } /* terminate list */ +}; +MODULE_DEVICE_TABLE(pci, nfp_pci_tbl); + +/** + * PCI driver operations. + */ +static struct pci_driver nfp_pci_driver = { .name = "nshield_solo", + .probe = nfp_pci_probe, + .remove = nfp_pci_remove, + .id_table = nfp_pci_tbl }; + +/*--------------------*/ +/* init */ +/*--------------------*/ + +static int nfp_init(void) +{ + int index; + + pr_info("%s: entered", __func__); + + if (register_chrdev(NFP_MAJOR, NFP_DRVNAME, &nfp_fops)) { + pr_err("unable to get major for nshield_solo device."); + return -EIO; + } + + for (index = 0; index < NFP_MAXDEV; index++) + nfp_dev_list[index] = NULL; + + nfp_class = class_create(THIS_MODULE, "nshield_solo"); + if (IS_ERR(nfp_class)) { + pr_err("%s: failed to create a class for this device, err = %ld", + __func__, PTR_ERR(nfp_class)); + return -EIO; + } + + index = 0; + return pci_register_driver(&nfp_pci_driver); +} + +/** @} */ + +/** + * Initializes this NSHIELD SOLO kernel module. + */ +static int __init nfp_module_init(void) +{ + int err; + + pr_err("%s: inserting nshield_solo module", __func__); + + err = nfp_init(); + + return err; +} + +/** + * Exits this NSHIELD SOLO kernel module. + */ +static void __exit nfp_module_exit(void) +{ + pr_notice("%s: removing nshield_solo module", __func__); + + /* unregister pci driver */ + + pci_unregister_driver(&nfp_pci_driver); + /* ... which triggers device removals */ + + class_destroy(nfp_class); + + unregister_chrdev(NFP_MAJOR, NFP_DRVNAME); + pr_notice("%s: removed nshield_solo module", __func__); +} + +module_init(nfp_module_init); +module_exit(nfp_module_exit); + +/* end of file */ diff --git a/drivers/misc/ncipher/i21555.c b/drivers/misc/ncipher/i21555.c new file mode 100644 index 000000000000..29ff6d10b2ea --- /dev/null +++ b/drivers/misc/ncipher/i21555.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * + * i21555.c: nCipher PCI HSM intel 21555 command driver + * Copyright 2019 nCipher Security Ltd + * + */ + +#include "solo.h" +#include "i21555.h" + +/* pci fixed length accessors. ---------------------------------- + * The below functions are used predominantly + * to access CSR registers in pci memory space. + */ +static u32 nfp_inl(struct nfp_dev *ndev, int bar, int offset) +{ + dev_dbg(&ndev->pcidev->dev, + "%s: addr %p", __func__, ndev->bar[bar] + offset); + return le32_to_cpu(ioread32(ndev->bar[bar] + offset)); +} + +static u16 nfp_inw(struct nfp_dev *ndev, int bar, int offset) +{ + dev_dbg(&ndev->pcidev->dev, + "%s: addr %p", __func__, ndev->bar[bar] + offset); + return le16_to_cpu(ioread16(ndev->bar[bar] + offset)); +} + +static void nfp_outl(struct nfp_dev *ndev, int bar, int offset, u32 data) +{ + dev_dbg(&ndev->pcidev->dev, "%s: addr %p, data %x", + __func__, ndev->bar[bar] + offset, data); + iowrite32(cpu_to_le32(data), ndev->bar[bar] + offset); +} + +static void nfp_outw(struct nfp_dev *ndev, int bar, int offset, u16 data) +{ + dev_dbg(&ndev->pcidev->dev, "%s: addr %p, data %x", + __func__, ndev->bar[bar] + offset, data); + iowrite16(cpu_to_le16(data), ndev->bar[bar] + offset); +} + +static int nfp_config_inl(struct nfp_dev *ndev, int offset, u32 *res) +{ + if (!ndev || !ndev->pcidev) + return -ENODEV; + pci_read_config_dword(ndev->pcidev, offset, res); + return 0; +} + +/* started ------------------------------------------------------ + * + * Check that device is ready to talk, by checking that + * the i21555 has master enabled on its secondary interface + */ + +static int i21555_started(struct nfp_dev *ndev) +{ + u32 tmp32; + int ne; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_info(&ndev->pcidev->dev, "%s: entered", __func__); + + ne = nfp_config_inl(ndev, I21555_CFG_SEC_CMD_STATUS, &tmp32); + if (ne != 0) { + dev_err(&ndev->pcidev->dev, + "%s: nfp_config_inl failed", __func__); + return ne; + } + + tmp32 = le32_to_cpu(tmp32) & 0xffff; + + if (tmp32 & CFG_CMD_MASTER) { + dev_notice(&ndev->pcidev->dev, + "%s: Yes %x", __func__, tmp32); + } else { + dev_err(&ndev->pcidev->dev, "%s: device not started yet %x", + __func__, tmp32); + ne = -EAGAIN; + } + return ne; +} + +/* create ------------------------------------------------------- */ + +static int i21555_create(struct nfp_dev *ndev) +{ + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_info(&ndev->pcidev->dev, "%s: entered", __func__); + + /* set our context to just be a pointer to our struct nfp_dev */ + ndev->cmdctx = ndev; + + if (!ndev->bar[CSR_BAR]) { + dev_err(&ndev->pcidev->dev, + "%s: null BAR[%d]", __func__, CSR_BAR); + return -ENOMEM; + } + dev_warn(&ndev->pcidev->dev, "%s: enable doorbell", __func__); + nfp_outl(ndev, CSR_BAR, I21555_OFFSET_DOORBELL_PRI_SET_MASK, + I21555_DOORBELL_PRI_ENABLE); + nfp_outl(ndev, CSR_BAR, I21555_OFFSET_DOORBELL_PRI_CLEAR_MASK, + I21555_DOORBELL_PRI_ENABLE); + return 0; +} + +/* stop ------------------------------------------------------- */ + +static int i21555_destroy(struct nfp_dev *ctx) +{ + struct nfp_dev *ndev = ctx; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_info(&ndev->pcidev->dev, "%s: entered", __func__); + + if (!ndev) { + dev_err(&ndev->pcidev->dev, "%s: NULL ndev", __func__); + return -ENODEV; + } + if (!ndev->bar[CSR_BAR]) { + dev_err(&ndev->pcidev->dev, + "%s: null BAR[%d]", __func__, CSR_BAR); + return -ENOMEM; + } + nfp_outl(ndev, CSR_BAR, I21555_OFFSET_DOORBELL_PRI_SET_MASK, + I21555_DOORBELL_PRI_DISABLE); + nfp_outl(ndev, CSR_BAR, I21555_OFFSET_DOORBELL_PRI_CLEAR_MASK, + I21555_DOORBELL_PRI_DISABLE); + + return 0; +} + +/* open ------------------------------------------------------- */ + +static int i21555_open(struct nfp_dev *ctx) +{ + struct nfp_dev *ndev = ctx; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_info(&ndev->pcidev->dev, "%s: entered", __func__); + + return 0; +} + +/* close ------------------------------------------------------- */ + +static int i21555_close(struct nfp_dev *ctx) +{ + struct nfp_dev *ndev = ctx; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_info(&ndev->pcidev->dev, "%s: entered", __func__); + + return 0; +} + +/* isr ------------------------------------------------------- */ + +static int i21555_isr(struct nfp_dev *ctx, int *handled) +{ + struct nfp_dev *ndev = ctx; + u16 doorbell; + u16 tmp16; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_info(&ndev->pcidev->dev, "%s: entered", __func__); + + *handled = 0; + + ndev->stats.isr++; + + if (!ndev->bar[CSR_BAR]) { + dev_err(&ndev->pcidev->dev, + "%s: null BAR[%d]", __func__, CSR_BAR); + return -ENOMEM; + } + + /* This interrupt may not be from our module, so check that it + * actually is us before handling it. + */ + doorbell = nfp_inw(ndev, CSR_BAR, I21555_OFFSET_DOORBELL_PRI_SET); + while (doorbell && doorbell != 0xffff) { + *handled = 1; + /* service interrupts */ + if (doorbell & (NFAST_INT_DEVICE_WRITE_OK | + NFAST_INT_DEVICE_WRITE_FAILED)) { + ndev->stats.isr_write++; + tmp16 = (NFAST_INT_DEVICE_WRITE_OK | + NFAST_INT_DEVICE_WRITE_FAILED); + nfp_outw(ndev, CSR_BAR, + I21555_OFFSET_DOORBELL_PRI_CLEAR, tmp16); + + dev_warn(&ndev->pcidev->dev, + "%s: write done interrupt, ok = %d.", __func__, + doorbell & NFAST_INT_DEVICE_WRITE_OK ? 1 : 0); + + nfp_write_complete(ndev, doorbell & + NFAST_INT_DEVICE_WRITE_OK ? 1 : 0); + } + + if (doorbell & + (NFAST_INT_DEVICE_READ_OK | + NFAST_INT_DEVICE_READ_FAILED)) { + ndev->stats.isr_read++; + tmp16 = (NFAST_INT_DEVICE_READ_OK | + NFAST_INT_DEVICE_READ_FAILED); + nfp_outw(ndev, CSR_BAR, + I21555_OFFSET_DOORBELL_PRI_CLEAR, tmp16); + + dev_warn(&ndev->pcidev->dev, + "%s: read ack interrupt, ok = %d.", __func__, + doorbell & NFAST_INT_DEVICE_READ_OK ? 1 : 0); + nfp_read_complete(ndev, doorbell & + NFAST_INT_DEVICE_READ_OK ? 1 : 0); + } + + if (doorbell & + ~(NFAST_INT_DEVICE_READ_OK | NFAST_INT_DEVICE_READ_FAILED | + NFAST_INT_DEVICE_WRITE_OK | + NFAST_INT_DEVICE_WRITE_FAILED)) { + nfp_outw(ndev, CSR_BAR, + I21555_OFFSET_DOORBELL_PRI_CLEAR, doorbell); + dev_err(&ndev->pcidev->dev, "%s: unexpected interrupt %x", + __func__, doorbell); + } + doorbell = nfp_inw(ndev, CSR_BAR, + I21555_OFFSET_DOORBELL_PRI_SET); + } + dev_notice(&ndev->pcidev->dev, "%s: exiting", __func__); + return 0; +} + +/* write ------------------------------------------------------- */ + +static int i21555_write(u32 addr, const char *block, int len, + struct nfp_dev *ctx) +{ + struct nfp_dev *ndev = ctx; + __le32 hdr[2]; + int ne; + u16 tmp16; + __le32 tmp32; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_info(&ndev->pcidev->dev, "%s: entered", __func__); + + ndev->stats.write_fail++; + + if (!ndev->bar[CSR_BAR]) { + dev_err(&ndev->pcidev->dev, + "%s: null BAR[%d]", __func__, CSR_BAR); + return -ENOMEM; + } + + ne = i21555_started(ndev); + if (ne != 0) { + if (ne != -EAGAIN) { + dev_err(&ndev->pcidev->dev, + "%s: i21555_started failed", __func__); + } + return ne; + } + + dev_notice(&ndev->pcidev->dev, "%s: ndev->bar[ MEMBAR2 ]= %p", + __func__, ndev->bar[MEMBAR2]); + dev_notice(&ndev->pcidev->dev, "%s: ndev->bar[ CSR_BAR ]= %p", + __func__, ndev->bar[CSR_BAR]); + dev_notice(&ndev->pcidev->dev, "%s: block len %d", __func__, len); + + /* send write request */ + + ne = copy_from_user(ndev->bar[MEMBAR2] + NFPCI_JOBS_WR_DATA, + block, len) ? -EFAULT : 0; + if (ne != 0) { + dev_err(&ndev->pcidev->dev, + "%s: copy_from_user failed", __func__); + return ne; + } + hdr[0] = cpu_to_le32(NFPCI_JOB_CONTROL); + hdr[1] = cpu_to_le32(len); + memcpy(ndev->bar[MEMBAR2] + NFPCI_JOBS_WR_CONTROL, + (const char *)hdr, 2 * sizeof(hdr[0])); + + /* confirm write request */ + + memcpy((char *)hdr, ndev->bar[MEMBAR2] + NFPCI_JOBS_WR_LENGTH, + sizeof(hdr[0])); + tmp32 = cpu_to_le32(len); + if (hdr[0] != tmp32) { + dev_err(&ndev->pcidev->dev, + "%s: length not written", __func__); + return -EIO; + } + + tmp16 = NFAST_INT_HOST_WRITE_REQUEST >> 16; + nfp_outw(ndev, CSR_BAR, I21555_OFFSET_DOORBELL_SEC_SET, tmp16); + + ndev->stats.write_fail--; + ndev->stats.write_block++; + ndev->stats.write_byte += len; + + dev_warn(&ndev->pcidev->dev, "%s: done", __func__); + return 0; +} + +/* read ------------------------------------------------------- */ + +static int i21555_read(char *block, int len, struct nfp_dev *ctx, int *rcount) +{ + struct nfp_dev *ndev = ctx; + int ne; + int count; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_info(&ndev->pcidev->dev, "%s: entered", __func__); + *rcount = 0; + + ndev->stats.read_fail++; + + if (!ndev->bar[CSR_BAR]) { + dev_err(&ndev->pcidev->dev, "%s: null BAR[%d]", + __func__, CSR_BAR); + return -ENOMEM; + } + + ne = i21555_started(ndev); + if (ne != 0) { + if (ne != -EAGAIN) + dev_err(&ndev->pcidev->dev, + "%s: i21555_started failed", __func__); + + return ne; + } + + memcpy((char *)&count, ndev->bar[MEMBAR2] + NFPCI_JOBS_RD_LENGTH, + sizeof(count)); + count = le32_to_cpu(count); + if (count < 0 || count > len) { + dev_err(&ndev->pcidev->dev, "%s: bad byte count (%d) from device", + __func__, count); + return -EIO; + } + ne = copy_to_user(block, ndev->bar[MEMBAR2] + NFPCI_JOBS_RD_DATA, + count) ? -EFAULT : 0; + if (ne != 0) { + dev_err(&ndev->pcidev->dev, + "%s: copy_to_user failed", __func__); + return ne; + } + dev_warn(&ndev->pcidev->dev, "%s: done", __func__); + *rcount = count; + ndev->stats.read_fail--; + ndev->stats.read_block++; + ndev->stats.read_byte += len; + return 0; +} + +/* ensure reading -------------------------------------------------- */ + +static int i21555_ensure_reading(dma_addr_t addr, + int len, struct nfp_dev *ctx, int lock_flag) +{ + struct nfp_dev *ndev = ctx; + __le32 hdr[3]; + u16 tmp16; + __le32 tmp32; + int ne; + int hdr_len; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_info(&ndev->pcidev->dev, "%s: entered", __func__); + + ndev->stats.ensure_fail++; + + if (!ndev->bar[CSR_BAR]) { + dev_err(&ndev->pcidev->dev, "%s: null BAR[%d]", + __func__, CSR_BAR); + return -ENOMEM; + } + + ne = i21555_started(ndev); + if (ne != 0) { + if (ne != -EAGAIN) { + dev_err(&ndev->pcidev->dev, + "%s: i21555_started failed", __func__); + } + return ne; + } + + dev_notice(&ndev->pcidev->dev, "%s: ndev->bar[ MEMBAR2 ]= %p", __func__, + ndev->bar[MEMBAR2]); + dev_notice(&ndev->pcidev->dev, "%s: ndev->bar[ CSR_BAR ]= %p", __func__, + ndev->bar[CSR_BAR]); + + /* send read request */ + + if (addr) { + dev_notice(&ndev->pcidev->dev, "%s: new format, addr %p", + __func__, (void *)addr); + hdr[0] = cpu_to_le32(NFPCI_JOB_CONTROL_PCI_PUSH); + hdr[1] = cpu_to_le32(len); + hdr[2] = cpu_to_le32(addr); + hdr_len = 3 * sizeof(hdr[0]); + } else { + hdr[0] = cpu_to_le32(NFPCI_JOB_CONTROL); + hdr[1] = cpu_to_le32(len); + hdr_len = 2 * sizeof(hdr[0]); + } + + memcpy(ndev->bar[MEMBAR2] + NFPCI_JOBS_RD_CONTROL, + (const char *)hdr, hdr_len); + + /* confirm read request */ + + memcpy((char *)hdr, ndev->bar[MEMBAR2] + NFPCI_JOBS_RD_LENGTH, + sizeof(hdr[0])); + tmp32 = cpu_to_le32(len); + if (hdr[0] != tmp32) { + dev_err(&ndev->pcidev->dev, "%s: len not written", __func__); + return -EIO; + } + + tmp16 = NFAST_INT_HOST_READ_REQUEST >> 16; + nfp_outw(ndev, CSR_BAR, I21555_OFFSET_DOORBELL_SEC_SET, tmp16); + + ndev->stats.ensure_fail--; + ndev->stats.ensure++; + + return 0; +} + +/* set control register ----------------------------------------- */ + +static int i21555_set_control(const struct nfdev_control_str *control, + struct nfp_dev *ctx) +{ + struct nfp_dev *ndev = ctx; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_info(&ndev->pcidev->dev, "%s: entered", __func__); + if (!ndev->bar[CSR_BAR]) { + dev_err(&ndev->pcidev->dev, "%s: null BAR[%d]", + __func__, CSR_BAR); + return -ENOMEM; + } + nfp_outl(ndev, CSR_BAR, I21555_SCRATCHPAD_REGISTER_CONTROL, + control->control); + return 0; +} + +/* get status/error registers ----------------------------------- */ + +static int i21555_get_status(struct nfdev_status_str *status, + struct nfp_dev *ctx) +{ + struct nfp_dev *ndev = ctx; + u32 *error = (u32 *)status->error; + + /* check for device */ + if (!ndev) { + pr_err("%s: error: no device", __func__); + return -ENODEV; + } + + dev_info(&ndev->pcidev->dev, "%s: entered", __func__); + if (!ndev->bar[CSR_BAR]) { + dev_err(&ndev->pcidev->dev, + "%s: null BAR[%d]", __func__, CSR_BAR); + return -ENOMEM; + } + status->status = nfp_inl(ndev, + CSR_BAR, I21555_SCRATCHPAD_REGISTER_STATUS); + error[0] = nfp_inl(ndev, CSR_BAR, I21555_SCRATCHPAD_REGISTER_ERROR_LO); + error[1] = nfp_inl(ndev, CSR_BAR, I21555_SCRATCHPAD_REGISTER_ERROR_HI); + return 0; +} + +/* command device structure ------------------------------------- */ + +const struct nfpcmd_dev i21555_cmddev = { + .name = "nCipher nShield Solo", + .vendorid = PCI_VENDOR_ID_INTEL, + .deviceid = PCI_DEVICE_ID_INTEL_21555, + .sub_vendorid = PCI_VENDOR_ID_NCIPHER, + .sub_deviceid = PCI_SUBSYSTEM_ID_NFAST_REV1, + .bar_sizes = BAR_SIZES, + .flags = 0, + .max_ifvers = NFDEV_IF_PCI_PUSH, + .create = i21555_create, + .destroy = i21555_destroy, + .open = i21555_open, + .close = i21555_close, + .isr = i21555_isr, + .write_block = i21555_write, + .read_block = i21555_read, + .ensure_reading = i21555_ensure_reading, + .setcontrol = i21555_set_control, + .getstatus = i21555_get_status, +}; + +/* end of file */ diff --git a/drivers/misc/ncipher/i21555.h b/drivers/misc/ncipher/i21555.h new file mode 100644 index 000000000000..e4794d21a428 --- /dev/null +++ b/drivers/misc/ncipher/i21555.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * + * Interfaces to the Solo's Intel 21555 bridge + * Copyright 2019 nCipher Security Ltd + * + */ + +#ifndef NFP_I21555_H +#define NFP_I21555_H + +#include "solo.h" + +#ifndef PCI_VENDOR_ID_INTEL +#define PCI_VENDOR_ID_INTEL 0x8086 +#endif + +#ifndef PCI_DEVICE_ID_INTEL_21555 +#define PCI_DEVICE_ID_INTEL_21555 0xb555 +#endif + +#ifndef PCI_VENDOR_ID_NCIPHER +#define PCI_VENDOR_ID_NCIPHER 0x0100 +#endif + +#ifndef PCI_SUBSYSTEM_ID_NFAST_REV1 +#define PCI_SUBSYSTEM_ID_NFAST_REV1 0x0100 +#endif + +#define I21555_OFFSET_DOORBELL_PRI_SET 0x9C +#define I21555_OFFSET_DOORBELL_SEC_SET 0x9E +#define I21555_OFFSET_DOORBELL_PRI_CLEAR 0x98 + +#define I21555_OFFSET_DOORBELL_PRI_SET_MASK 0xA4 +#define I21555_OFFSET_DOORBELL_PRI_CLEAR_MASK 0xA0 + +#define I21555_DOORBELL_PRI_ENABLE 0x0000 +#define I21555_DOORBELL_PRI_DISABLE 0xFFFF + +/* 8 32-bit scratchpad registers start here; bridge manual section 11.4 */ +#define I21555_SCRATCHPAD_REGISTER(n) (0xA8 + 4 * (n)) + +/* Scratchpad register assignments */ +#define I21555_SCRATCHPAD_REGISTER_CONTROL I21555_SCRATCHPAD_REGISTER(0) +#define I21555_SCRATCHPAD_REGISTER_STATUS I21555_SCRATCHPAD_REGISTER(1) +#define I21555_SCRATCHPAD_REGISTER_ERROR_LO I21555_SCRATCHPAD_REGISTER(2) +#define I21555_SCRATCHPAD_REGISTER_ERROR_HI I21555_SCRATCHPAD_REGISTER(3) + +#define I21555_CFG_SEC_CMD_STATUS 0x44 + +#define CFG_CMD_MASTER 0x0004 + +#define MEMBAR1 0 +#define MEMBAR2 2 + +/* lower 4k of BAR0 map the 21555 CSRs (doorbell IRQs etc) */ +#define MEMBAR1_SIZE 4096 + +#define CSR_BAR MEMBAR1 +#define BAR_SIZES {MEMBAR1_SIZE, \ + 0, \ + NFPCI_RAM_MINSIZE_JOBS | \ + PCI_BASE_ADDRESS_SPACE_PREFETCHABLE, \ + 0, \ + 0, \ + 0} + +#endif /* NFP_I21555_H */ diff --git a/drivers/misc/ncipher/solo.h b/drivers/misc/ncipher/solo.h new file mode 100644 index 000000000000..5240805e6610 --- /dev/null +++ b/drivers/misc/ncipher/solo.h @@ -0,0 +1,316 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * + * solo.h: nCipher PCI HSM interface + * Copyright 2019 nCipher Security Ltd + * + * Declares the nFast PCI register interface, driver runtime + * structures, and supporting items. + * + * The interface presented by nFast PCI devices consists of: + * + * - a region of shared RAM used for data transfer & control information + * - a doorbell interrupt register, so both sides can give each other + * interrupts + * - a number of DMA channels for transferring data + * + */ + +#ifndef SOLO_H +#define SOLO_H + +#include <linux/version.h> +#include <linux/types.h> +#include <linux/time.h> +#include <linux/io.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/ptrace.h> +#include <linux/errno.h> +#include <linux/ioport.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/poll.h> +#include <linux/mutex.h> +#include <linux/nshield_solo.h> + +/* Sizes of some regions */ + +/* + * This is the minimum size of shared RAM. In future it may be possible to + * negotiate larger sizes of shared RAM or auto-detect how big it is + */ +#define NFPCI_RAM_MINSIZE 0x00100000 +#define NFPCI_RAM_MINSIZE_JOBS 0x00020000 /* standard jobs only */ +#define NFPCI_RAM_MINSIZE_KERN 0x00040000 /* standard and + * kernel jobs + */ + +/* Offsets within shared memory space. + * The following main regions are: + * jobs input area + * jobs output area + * kernel jobs input area + * kernel output area + */ + +#define NFPCI_OFFSET_JOBS 0x00000000 +#define NFPCI_OFFSET_JOBS_WR 0x00000000 +#define NFPCI_OFFSET_JOBS_RD 0x00010000 +#define NFPCI_OFFSET_KERN 0x00020000 +#define NFPCI_OFFSET_KERN_WR 0x00020000 +#define NFPCI_OFFSET_KERN_RD 0x00030000 + +/* Interrupts, defined by bit position in doorbell register */ + +/* Interrupts from device to host */ +#define NFAST_INT_DEVICE_WRITE_OK 0x00000001 +#define NFAST_INT_DEVICE_WRITE_FAILED 0x00000002 +#define NFAST_INT_DEVICE_READ_OK 0x00000004 +#define NFAST_INT_DEVICE_READ_FAILED 0x00000008 +#define NFAST_INT_DEVICE_KERN_WRITE_OK 0x00000010 +#define NFAST_INT_DEVICE_KERN_WRITE_FAILED 0x00000020 +#define NFAST_INT_DEVICE_KERN_READ_OK 0x00000040 +#define NFAST_INT_DEVICE_KERN_READ_FAILED 0x00000080 + +/* Interrupts from host to device */ +#define NFAST_INT_HOST_WRITE_REQUEST 0x00010000 +#define NFAST_INT_HOST_READ_REQUEST 0x00020000 +#define NFAST_INT_HOST_DEBUG 0x00040000 +#define NFAST_INT_HOST_KERN_WRITE_REQUEST 0x00080000 +#define NFAST_INT_HOST_KERN_READ_REQUEST 0x00100000 + +/* Ordinary job submission */ + +/* + * The NFPCI_OFFSET_JOBS_WR and NFPCI_OFFSET_JOBS_RD regions + * are defined by the following (byte) address offsets. + */ + +#define NFPCI_OFFSET_CONTROL 0x0 +#define NFPCI_OFFSET_LENGTH 0x4 +#define NFPCI_OFFSET_DATA 0x8 +#define NFPCI_OFFSET_PUSH_ADDR 0x8 +#define NFPCI_OFFSET_PULL_ADDR 0x8 + +#define NFPCI_JOBS_WR_CONTROL (NFPCI_OFFSET_JOBS_WR + \ + NFPCI_OFFSET_CONTROL) +#define NFPCI_JOBS_WR_LENGTH (NFPCI_OFFSET_JOBS_WR + \ + NFPCI_OFFSET_LENGTH) +#define NFPCI_JOBS_WR_DATA (NFPCI_OFFSET_JOBS_WR + \ + NFPCI_OFFSET_DATA) +/* address in PCI space of host buffer for NFPCI_JOB_CONTROL_PCI_PUSH */ +#define NFPCI_JOBS_WR_PULL_ADDR (NFPCI_OFFSET_JOBS_WR + \ + NFPCI_OFFSET_PULL_ADDR) +#define NFPCI_MAX_JOBS_WR_LEN (0x0000FFF8) + +#define NFPCI_JOBS_RD_CONTROL (NFPCI_OFFSET_JOBS_RD + \ + NFPCI_OFFSET_CONTROL) +#define NFPCI_JOBS_RD_LENGTH (NFPCI_OFFSET_JOBS_RD + \ + NFPCI_OFFSET_LENGTH) +#define NFPCI_JOBS_RD_DATA (NFPCI_OFFSET_JOBS_RD + \ + NFPCI_OFFSET_DATA) +/* address in PCI space of host buffer for NFPCI_JOB_CONTROL_PCI_PUSH */ +#define NFPCI_JOBS_RD_PUSH_ADDR (NFPCI_OFFSET_JOBS_RD + \ + NFPCI_OFFSET_PUSH_ADDR) +#define NFPCI_MAX_JOBS_RD_LEN (0x000FFF8) + +/* Kernel interface job submission */ + +#define NFPCI_KERN_WR_CONTROL (NFPCI_OFFSET_KERN_WR + \ + NFPCI_OFFSET_CONTROL) +#define NFPCI_KERN_WR_LENGTH (NFPCI_OFFSET_KERN_WR + \ + NFPCI_OFFSET_LENGTH) +#define NFPCI_KERN_WR_DATA (NFPCI_OFFSET_KERN_WR + \ + NFPCI_OFFSET_DATA) +/* address in PCI space of host buffer for NFPCI_JOB_CONTROL_PCI_PUSH */ +#define NFPCI_KERN_WR_PULL_ADDR (NFPCI_OFFSET_KERN_WR + \ + NFPCI_OFFSET_PULL_ADDR) +#define NFPCI_MAX_KERN_WR_LEN (0x0000FFF8) + +#define NFPCI_KERN_RD_CONTROL (NFPCI_OFFSET_KERN_RD + \ + NFPCI_OFFSET_CONTROL) +#define NFPCI_KERN_RD_LENGTH (NFPCI_OFFSET_KERN_RD + \ + NFPCI_OFFSET_LENGTH) +#define NFPCI_KERN_RD_DATA (NFPCI_OFFSET_KERN_RD + \ + NFPCI_OFFSET_DATA) +/* address in PCI space of host buffer for NFPCI_JOB_CONTROL_PCI_PUSH */ +#define NFPCI_KERN_RD_PUSH_ADDR (NFPCI_OFFSET_KERN_RD + \ + NFPCI_OFFSET_PUSH_ADDR) +#define NFPCI_MAX_KERN_RD_LEN (0x000FFF8) + +#define NFPCI_JOB_CONTROL 0x00000001 +#define NFPCI_JOB_CONTROL_PCI_PUSH 0x00000002 +#define NFPCI_JOB_CONTROL_PCI_PULL 0x00000003 + +/* + * The 'Control' word is analogous to the SCSI read/write address; + * 1 = standard push/pull I/O + * 2 = push/push I/O + * 3 = pull/push I/O + * + * To submit a block of job data, the host: + * - sets the (32-bit, little-endian) word at NFPCI_JOBS_WR_CONTROL to + * NFPCI_JOB_CONTROL + * - sets the word at NFPCI_JOBS_WR_LENGTH to the length of the data + * - copies the data to NFPCI_JOBS_WR_DATA + * - sets interrupt NFAST_INT_HOST_WRITE_REQUEST in the doorbell register + * - awaits the NFAST_INT_DEVICE_WRITE_OK (or _FAILED) interrupts back + * + * To read a block of jobs back, the host: + * - sets the word at NFPCI_JOBS_RD_CONTROL to NFPCI_JOB_CONTROL + * - sets the word at NFPCI_JOBS_RD_LENGTH to the max length for returned data + * - sets interrupt NFAST_INT_HOST_READ_REQUEST + * - awaits the NFAST_INT_DEVICE_READ_OK (or _FAILED) interrupt + * - reads the data from NFPCI_JOBS_RD_DATA; the module will set the word at + * NFPCI_JOBS_RD_LENGTH to its actual length. + * + * Optionally the host can request the PCI read data to be pushed to host PCI + * mapped ram: + * - allocates a contiguous PCI addressable buffer for a NFPCI_JOBS_BLOCK of + * max size NFPCI_MAX_JOBS_RD_LEN (or NFPCI_MAX_KERN_RD_LEN) + 8 + * - sets the word at NFPCI_JOBS_RD_CONTROL to NFPCI_JOB_CONTROL_PCI_PUSH + * - sets the word at NFPCI_JOBS_RD_LENGTH to the max length for returned data + * - sets the word at NFPCI_JOBS_RD_PUSH_ADDR to be the host PCI address of + * the buffer + * - sets interrupt NFAST_INT_HOST_READ_REQUEST + * - awaits the NFAST_INT_DEVICE_READ_OK (or _FAILED) interrupt + * - reads the data from the buffer at NFPCI_OFFSET_DATA in the buffer. The + * module will set NFPCI_OFFSET_LENGTH to the actual length. + * + * Optionally the host can request the PCI write data to be pulled from host + * PCI mapped ram: + * - allocates a contiguous PCI addressable buffer for a NFPCI_JOBS_BLOCK of + * max size NFPCI_MAX_JOBS_WR_LEN (or NFPCI_MAX_KERN_WR_LEN) + 8 + * - copies the data to the PCI addressable buffer + * - sets the word at NFPCI_JOBS_WR_CONTROL to NFPCI_JOB_CONTROL_PCI_PULL + * - sets the word at NFPCI_JOBS_WR_LENGTH to the length of the data + * - sets the word at NFPCI_JOBS_RD_PULL_ADDR to be the host PCI address of + * the buffer + * - sets interrupt NFAST_INT_HOST_WRITE_REQUEST in the doorbell register + * - awaits the NFAST_INT_DEVICE_WRITE_OK (or _FAILED) interrupts back + */ + +#define NFPCI_SCRATCH_CONTROL 0 + +#define NFPCI_SCRATCH_CONTROL_HOST_MOI (0x1) +#define NFPCI_SCRATCH_CONTROL_MODE_SHIFT 1 +#define NFPCI_SCRATCH_CONTROL_MODE_MASK (3 << \ + NFPCI_SCRATCH_CONTROL_MODE_SHIFT) + +#define NFPCI_SCRATCH_STATUS 1 + +#define NFPCI_SCRATCH_STATUS_MONITOR_MOI (0x1) +#define NFPCI_SCRATCH_STATUS_APPLICATION_MOI (0x2) +#define NFPCI_SCRATCH_STATUS_APPLICATION_RUNNING (0x4) +#define NFPCI_SCRATCH_STATUS_ERROR (0x8) + +#define NFPCI_SCRATCH_ERROR_LO 2 +#define NFPCI_SCRATCH_ERROR_HI 3 + +#define NFP_BARSIZES_COUNT 6 +/* + * This masks off the bottom bits of the PCI_CSR_BAR which signify that the + * BAR is an IO BAR rather than a MEM BAR + */ +#define NFP_BARSIZES_MASK ~0xF + +/* per-instance device structure */ +struct nfp_dev { + /* downward facing part of the device interface */ + void __iomem *bar[NFP_BARSIZES_COUNT]; + void *extra[NFP_BARSIZES_COUNT]; + int busno; + int slotno; + struct nfp_dev *cmdctx; + char *iobuf; + int active_bar; + int created; + int conn_status; + int detection_type; + int iosize[6]; + u32 irq; + struct nfpcmd_dev const *cmddev; + struct nfdev_stats_str stats; + + /* upward facing part of the device interface */ + u8 *read_buf; + dma_addr_t read_dma; + + u8 *write_buf; + dma_addr_t write_dma; + + struct pci_dev *pcidev; + + atomic_t busy; + int ifvers; + struct timer_list rd_timer; + + wait_queue_head_t rd_queue; + unsigned long rd_ready; + unsigned long rd_outstanding; + int rd_ok; + + wait_queue_head_t wr_queue; + unsigned long wr_ready; + unsigned long wr_outstanding; + int wr_ok; + + struct mutex ioctl_mutex; /* lock across ioctl */ +}; + +/* Per-device-type command handlers */ +struct nfpcmd_dev { + const char *name; + u16 vendorid, deviceid, sub_vendorid, sub_deviceid; + u32 bar_sizes[NFP_BARSIZES_COUNT]; /* includes IO bit */ + u32 flags, max_ifvers; + + int (*create)(struct nfp_dev *ndev); + int (*destroy)(struct nfp_dev *ctx); + int (*open)(struct nfp_dev *ctx); + int (*close)(struct nfp_dev *ctx); + int (*isr)(struct nfp_dev *ctx, int *handled); + int (*write_block)(u32 addr, + const char __user *ublock, int len, + struct nfp_dev *ctx); + int (*read_block)(char __user *ublock, + int len, struct nfp_dev *ctx, int *rcount); + int (*ensure_reading)(dma_addr_t addr, + int len, struct nfp_dev *ctx, int lock_flag); + int (*setcontrol)(const struct nfdev_control_str *control, + struct nfp_dev *ctx); /* may be NULL */ + int (*getstatus)(struct nfdev_status_str *status, + struct nfp_dev *ctx); /* may be NULL */ +}; + +/* These instances are defined in the per-board driver modules */ +extern const struct nfpcmd_dev i21555_cmddev; +extern const struct nfpcmd_dev fsl_t1022_cmddev; + +#define NFP_CMD_FLG_NEED_MSI 0x2 + +#ifndef PCI_BASE_ADDRESS_SPACE_PREFETCHABLE +#define PCI_BASE_ADDRESS_SPACE_PREFETCHABLE 0x8 +#endif + +/* callbacks from command drivers */ +void nfp_read_complete(struct nfp_dev *ndev, int ok); +void nfp_write_complete(struct nfp_dev *ndev, int ok); + +/* status codes */ +#define NFP_HSM_STARTING 1 /* The HSM hasn't readied its PCI interface yet */ +#define NFP_HSM_POLLING 2 /* The HSM's EPD will use polling */ + +/* error conversions table */ +struct errstr { + int oserr; + int nferr; +}; + +extern struct errstr errtab[]; + +#endif /* SOLO_H */ diff --git a/include/uapi/linux/nshield_solo.h b/include/uapi/linux/nshield_solo.h new file mode 100644 index 000000000000..9a45a792aebf --- /dev/null +++ b/include/uapi/linux/nshield_solo.h @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * + * nshield_solo.h: UAPI header for driving the nCipher PCI HSMs using the + * nshield_solo module + * + */ +#ifndef _UAPI_NSHIELD_SOLO_H_ +#define _UAPI_NSHIELD_SOLO_H_ + +#include <linux/types.h> +#include <linux/ioctl.h> + +/* Device ioctl struct definitions */ + +/* Result of the ENQUIRY ioctl. */ +struct nfdev_enquiry_str { + __u32 busno; /**< Which bus is the PCI device on. */ + __u8 slotno; /**< Which slot is the PCI device in. */ + __u8 reserved[3]; /**< for consistent struct alignment */ +}; + +/* Result of the STATS ioctl. */ +struct nfdev_stats_str { + __u32 isr; /**< Count interrupts. */ + __u32 isr_read; /**< Count read interrupts. */ + __u32 isr_write; /**< Count write interrupts. */ + __u32 write_fail; /**< Count write failures. */ + __u32 write_block; /**< Count blocks written. */ + __u32 write_byte; /**< Count bytes written. */ + __u32 read_fail; /**< Count read failures. */ + __u32 read_block; /**< Count blocks read. */ + __u32 read_byte; /**< Count bytes read. */ + __u32 ensure_fail; /**< Count read request failures. */ + __u32 ensure; /**< Count read requests. */ +}; + +/* Result of the STATUS ioctl. */ +struct nfdev_status_str { + __u32 status; /**< Status flags. */ + char error[8]; /**< Error string. */ +}; + +/* Input to the CONTROL ioctl. */ +struct nfdev_control_str { + __u32 control; /**< Control flags. */ +}; + +/** Index of control bits indicating desired mode + * + * Desired mode follows the M_ModuleMode enumeration. + */ +#define NFDEV_CONTROL_MODE_SHIFT 1 + +/** Detect a backwards-compatible control value + * + * Returns true if the request control value "makes no difference", i.e. + * and the failure of an attempt to set it is therefore uninteresting. + */ +#define NFDEV_CONTROL_HARMLESS(c) ((c) <= 1) + +/** Monitor firmware supports MOI control and error reporting + */ +#define NFDEV_STATUS_MONITOR_MOI 0x0001 + +/** Application firmware supports MOI control and error reporting + */ +#define NFDEV_STATUS_APPLICATION_MOI 0x0002 + +/** Application firmware running and supports error reporting + */ +#define NFDEV_STATUS_APPLICATION_RUNNING 0x0004 + +/** HSM failed + * + * Consult error[] for additional information. + */ +#define NFDEV_STATUS_FAILED 0x0008 + +/** Standard PCI interface. */ +#define NFDEV_IF_STANDARD 0x01 + +/** PCI interface with read replies pushed from device + * via DMA. + */ +#define NFDEV_IF_PCI_PUSH 0x02 + +/** PCI interface with read replies pushed from device + * and write requests pulled from host via DMA. + */ +#define NFDEV_IF_PCI_PULL 0x03 + +/** Maximum PCI interface. */ +#define NFDEV_IF_MAX_VERS NFDEV_IF_PCI_PUSH_PULL + +/* platform independent base ioctl numbers */ + +enum { + /** Enquiry ioctl. + * \return nfdev_enquiry_str describing the attached device. + */ + NFDEV_IOCTL_NUM_ENQUIRY = 1, + + /** Channel Update ioctl. + * \deprecated + */ + NFDEV_IOCTL_NUM_CHUPDATE, + + /** Ensure Reading ioctl. + * Signal a read request to the device. + * \param (unsigned int) Length of data to be read. + */ + NFDEV_IOCTL_NUM_ENSUREREADING, + + /** Device Count ioctl. + * Not implemented for on all platforms. + * \return (int) the number of attached devices. + */ + NFDEV_IOCTL_NUM_DEVCOUNT, + + /** Internal Debug ioctl. + * Not implemented in release drivers. + */ + NFDEV_IOCTL_NUM_DEBUG, + + /** PCI Interface Version ioctl. + * \param (int) Maximum PCI interface version + * supported by the user of the device. + */ + NFDEV_IOCTL_NUM_PCI_IFVERS, + + /** Statistics ioctl. + * \return nfdev_enquiry_str describing the attached device. + */ + NFDEV_IOCTL_NUM_STATS, + + /** Module control ioctl + * \param (nfdev_control_str) Value to write to HSM + * control register + */ + NFDEV_IOCTL_NUM_CONTROL, + + /** Module state ioctl + * \return (nfdev_status_str) Values read from HSM + * status/error registers + */ + NFDEV_IOCTL_NUM_STATUS, +}; + +#define NFDEV_IOCTL_TYPE 0x10 + +#define NFDEV_IOCTL_CHUPDATE \ + _IO(NFDEV_IOCTL_TYPE, NFDEV_IOCTL_NUM_CHUPDATE) + +#define NFDEV_IOCTL_ENQUIRY \ + _IOR(NFDEV_IOCTL_TYPE, NFDEV_IOCTL_NUM_ENQUIRY, \ + struct nfdev_enquiry_str) + +#define NFDEV_IOCTL_ENSUREREADING \ + _IOW(NFDEV_IOCTL_TYPE, NFDEV_IOCTL_NUM_ENSUREREADING, int) + +#define NFDEV_IOCTL_ENSUREREADING_BUG3349 \ + _IO(NFDEV_IOCTL_TYPE, NFDEV_IOCTL_NUM_ENSUREREADING) + +#define NFDEV_IOCTL_DEBUG \ + _IOW(NFDEV_IOCTL_TYPE, NFDEV_IOCTL_NUM_DEBUG, int) + +#define NFDEV_IOCTL_PCI_IFVERS \ + _IOW(NFDEV_IOCTL_TYPE, NFDEV_IOCTL_NUM_PCI_IFVERS, int) + +#define NFDEV_IOCTL_STATS \ + _IOR(NFDEV_IOCTL_TYPE, NFDEV_IOCTL_NUM_STATS, struct nfdev_stats_str) + +#define NFDEV_IOCTL_CONTROL \ + _IOW(NFDEV_IOCTL_TYPE, NFDEV_IOCTL_NUM_CONTROL, \ + const struct nfdev_control_str) + +#define NFDEV_IOCTL_STATUS \ + _IOR(NFDEV_IOCTL_TYPE, NFDEV_IOCTL_NUM_STATUS, struct nfdev_status_str) + +#endif /* _UAPI_NSHIELD_SOLO_H_ */ -- 2.24.1
David Kim Senior Software Engineer Tel: +44 1223 703449
nCipher Security One Station Square Cambridge CB1 2GA United Kingdom
| |