[PATCH v2 49/50] wilc1000: implement zero-copy transmit support for SPI

From: David Mosberger-Tang
Date: Wed Dec 22 2021 - 20:16:28 EST


This enables zero-copy transmits for the SPI driver. Maybe something
similar could be implemented for the SDIO driver, but I'm not really
familiar with it.

Signed-off-by: David Mosberger-Tang <davidm@xxxxxxxxxx>
---
drivers/net/wireless/microchip/wilc1000/spi.c | 162 ++++++++++++++++++
1 file changed, 162 insertions(+)

diff --git a/drivers/net/wireless/microchip/wilc1000/spi.c b/drivers/net/wireless/microchip/wilc1000/spi.c
index 8951202ed76e2..8d94f111ffc49 100644
--- a/drivers/net/wireless/microchip/wilc1000/spi.c
+++ b/drivers/net/wireless/microchip/wilc1000/spi.c
@@ -44,6 +44,8 @@ MODULE_PARM_DESC(enable_crc16,
static const struct wilc_hif_func wilc_hif_spi;

static int wilc_spi_reset(struct wilc *wilc);
+static int wilc_spi_write_sk_buffs(struct wilc *wilc, u32 addr,
+ size_t num_skbs, struct sk_buff_head *skbs);

/********************************************
*
@@ -107,9 +109,33 @@ static int wilc_spi_reset(struct wilc *wilc);
#define DATA_PKT_LOG_SZ DATA_PKT_LOG_SZ_MAX
#define DATA_PKT_SZ (1 << DATA_PKT_LOG_SZ)

+#define DATA_START_TAG 0xf0
+#define DATA_ORDER_FIRST 0x01
+#define DATA_ORDER_INNER 0x02
+#define DATA_ORDER_LAST 0x03
+
#define WILC_SPI_COMMAND_STAT_SUCCESS 0
#define WILC_GET_RESP_HDR_START(h) (((h) >> 4) & 0xf)

+/* wilc_spi_write_sk_buffs() needs the following max. number of SPI
+ * transfers:
+ *
+ * - 1 transfer to send the CMD_DMA_EXT_WRITE command
+ * - 1 transfer per sk_buff (at most WILC_VMM_TBL_SIZE of them)
+ * - for each data packet:
+ * + 1 transfer for the data start tag
+ * + 1 transfer for the current sk_buff (if it spans
+ * the boundary of a data packet)
+ * + 1 transfer for the optional CRC16
+ * - 1 transfer to read the DMA response bytes
+ */
+#define MAX_DATA_PKTS DIV_ROUND_UP(WILC_TX_BUFF_SIZE, DATA_PKT_SZ)
+#define MAX_SPI_XFERS \
+ (1 \
+ + WILC_VMM_TBL_SIZE \
+ + 3 * MAX_DATA_PKTS \
+ + 1)
+
struct wilc_spi {
bool isinit; /* true if SPI protocol has been configured */
bool probing_crc; /* true if we're probing chip's CRC config */
@@ -119,6 +145,11 @@ struct wilc_spi {
struct gpio_desc *enable; /* ENABLE GPIO or NULL */
struct gpio_desc *reset; /* RESET GPIO or NULL */
} gpios;
+ /* Scratch space used by wilc_spi_write_sk_buffs() for SPI
+ * transfers and the data packets' CRCs.
+ */
+ struct spi_transfer xfer[MAX_SPI_XFERS];
+ u8 crc[2 * MAX_DATA_PKTS];
};

struct wilc_spi_cmd {
@@ -1037,6 +1068,136 @@ static int wilc_spi_write(struct wilc *wilc, u32 addr, u8 *buf, u32 size)
return spi_data_rsp(wilc, CMD_DMA_EXT_WRITE);
}

+static void wilc_spi_add_xfer(struct spi_message *msg,
+ struct spi_transfer **xferp,
+ size_t len, const void *tx_buf, void *rx_buf)
+{
+ struct spi_transfer *xfer = *xferp;
+
+ xfer->tx_buf = tx_buf;
+ xfer->rx_buf = rx_buf;
+ xfer->len = len;
+ spi_message_add_tail(xfer, msg);
+ *xferp = xfer + 1;
+}
+
+/**
+ * wilc_spi_write_sk_buffs() - Zero-copy write sk_buffs to the chip.
+ * @wilc: Pointer to the wilc structure.
+ * @addr: The WILC address to transfer the data to.
+ * @num_skbs: The length of the skbs array.
+ * @skbs: The queue containing the sk_buffs to transmit.
+ *
+ * Zero-copy transfer one or more sk_buffs to the WILC chip. At most
+ * WILC_VMM_TBL_SIZE sk_buffs may be transmitted and the total size of
+ * the data in the sk_buffs must not exceed WILC_VMM_TBL_SIZE.
+ *
+ * Context: The caller must hold ownership of the SPI bus through a
+ * call to acquire_bus().
+ *
+ * Return: Zero on success, negative number on error.
+ */
+static int wilc_spi_write_sk_buffs(struct wilc *wilc, u32 addr, size_t num_skbs,
+ struct sk_buff_head *skbs)
+{
+ static const u8 data_hdr_first = DATA_START_TAG | DATA_ORDER_FIRST;
+ static const u8 data_hdr_inner = DATA_START_TAG | DATA_ORDER_INNER;
+ static const u8 data_hdr_last = DATA_START_TAG | DATA_ORDER_LAST;
+ size_t num_data_packets = 0, total_bytes = 0, num_sent, n, space;
+ struct spi_device *spi = to_spi_device(wilc->dev);
+ struct wilc_spi *spi_priv = wilc->bus_data;
+ u8 rsp[WILC_SPI_DATA_RSP_BYTES], *crc;
+ int i, ret, cmd_len;
+ struct spi_transfer *xfer;
+ struct wilc_spi_cmd cmd;
+ struct spi_message msg;
+ struct sk_buff *skb;
+ const u8 *data_hdr;
+ u16 crc_calc;
+
+ /* setup the SPI message and transfers: */
+
+ spi_message_init(&msg);
+ msg.spi = spi;
+
+ skb = skb_peek(skbs);
+ for (i = 0; i < num_skbs; ++i) {
+ n = skb->len;
+ total_bytes += n;
+ skb = skb_peek_next(skb, skbs);
+ }
+
+ num_data_packets = DIV_ROUND_UP(total_bytes, DATA_PKT_SZ);
+ skb = skb_peek(skbs);
+ num_sent = 0;
+ xfer = spi_priv->xfer;
+ crc = spi_priv->crc;
+
+ cmd_len = wilc_spi_dma_init_cmd(wilc, &cmd, CMD_DMA_EXT_WRITE,
+ addr, total_bytes);
+ if (cmd_len < 0) {
+ dev_err(&spi->dev, "Failed to init DMA command.");
+ return -EINVAL;
+ }
+ wilc_spi_add_xfer(&msg, &xfer, cmd_len, &cmd, NULL);
+
+ for (i = 0; i < num_data_packets; ++i) {
+ space = DATA_PKT_SZ;
+ crc_calc = 0xffff;
+
+ /* write data packet's start header: */
+ if (i == num_data_packets - 1)
+ data_hdr = &data_hdr_last;
+ else if (i == 0)
+ data_hdr = &data_hdr_first;
+ else
+ data_hdr = &data_hdr_inner;
+ wilc_spi_add_xfer(&msg, &xfer, 1, data_hdr, NULL);
+
+ /* write packet data: */
+ do {
+ n = skb->len - num_sent;
+ if (n > space)
+ n = space;
+ wilc_spi_add_xfer(&msg, &xfer, n,
+ skb->data + num_sent, NULL);
+ if (spi_priv->crc16_enabled)
+ crc_calc = crc_itu_t(crc_calc,
+ skb->data + num_sent, n);
+ num_sent += n;
+ space -= n;
+
+ if (num_sent >= skb->len) {
+ skb = skb_peek_next(skb, skbs);
+ --num_skbs;
+ num_sent = 0;
+ }
+ } while (space > 0 && num_skbs > 0);
+
+ /* write optional CRC16 checksum: */
+ if (spi_priv->crc16_enabled) {
+ crc[0] = crc_calc >> 8;
+ crc[1] = crc_calc;
+ wilc_spi_add_xfer(&msg, &xfer, 2, crc, NULL);
+ crc += 2;
+ }
+ }
+ /* last transfer reads the response bytes: */
+ wilc_spi_add_xfer(&msg, &xfer, sizeof(rsp), NULL, rsp);
+
+ WARN_ON((u8 *)xfer - (u8 *)spi_priv->xfer > sizeof(spi_priv->xfer));
+ WARN_ON(crc - spi_priv->crc > sizeof(spi_priv->crc));
+
+ ret = spi_sync(spi, &msg);
+ if (ret < 0) {
+ dev_err(&spi->dev, "spi_sync() failed: ret=%d\n", ret);
+ return -EINVAL;
+ }
+
+ /* Check if the chip received the data correctly: */
+ return spi_data_check_rsp(wilc, rsp);
+}
+
/********************************************
*
* Bus interfaces
@@ -1275,6 +1436,7 @@ static const struct wilc_hif_func wilc_hif_spi = {
.hif_read_size = wilc_spi_read_size,
.hif_block_tx_ext = wilc_spi_write,
.hif_block_rx_ext = wilc_spi_read,
+ .hif_sk_buffs_tx = wilc_spi_write_sk_buffs,
.hif_sync_ext = wilc_spi_sync_ext,
.hif_reset = wilc_spi_reset,
};
--
2.25.1