* [PATCH v9 0/3] Virtio SPI Linux driver
@ 2025-08-28 9:34 Haixu Cui
2025-08-28 9:34 ` [PATCH v9 1/3] virtio: Add ID for virtio SPI Haixu Cui
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Haixu Cui @ 2025-08-28 9:34 UTC (permalink / raw)
To: andriy.shevchenko, harald.mommer, quic_msavaliy, quic_haixcui,
broonie, virtio-dev, viresh.kumar, linux-spi, linux-kernel,
hdanton, qiang4.zhang, alex.bennee
Cc: quic_ztu, virtualization
This is the 9th version of the virtio SPI Linux driver patch series which is
intended to be compliant with the upcoming virtio specification
version 1.4. The specification can be found in repository:
https://github.com/oasis-tcs/virtio-spec.git branch virtio-1.4.
The original contributor Harald submitted:
RFC v1 to RFC v3
non-RFC v1 to non-RFC v3
I continued the work and submitted:
RFC v4 to RFC v8
non-RFC v4
This submission is labeled as v9 to maintain continuity and clarity.
It builds upon non-RFC v4, and incorporates feedback from the community
and testing improvements.
Changes between non-RFC v4 and v9:
- Updated macro definition to use SPI_MODE_X_MASK instead of manually
listing individual flags.
- Restructured struct virtio_spi_req to improve cache alignment and
layout efficiency.
- Replaced manual comparison logic with max() for word delay calculation.
- Removed unnecessary u suffix from unsigned int initializations.
- Simplified memory cleanup by using __free(kfree) for spi_req.
- Standardized error variable naming from err to ret across the driver.
- Removed redundant ACPI_COMPANION_SET() call to avoid inconsistency
with device_set_node().
- Corrected logic for reading spi,bus-num property.
- eplaced return err with return dev_err_probe for cleaner error reporting.
- Used dev_to_virtio() in power management callbacks.
- Added virtualization@lists.linux-foundation.org as the mailing list
for patch discussions.
Changes between RFC V8 and non-RFC V4:
- Replaced BUILD_BUG_ON with static_assert for SPI mode constants to
improve compile-time validation.
- Introduced VIRTIO_SPI_MODE_MASK macro to simplify SPI mode bit masking.
- Removed unnecessary __cacheline_aligned attributes from buffer
pointers in virtio_spi_req.
- Simplified type casting in delay conversions by removing redundant
(u32) casts.
- Replaced manual virtqueue cleanup with devm_add_action_or_reset()
for automatic resource management.
- Migrated controller registration to devm_spi_register_controller()
for better lifecycle handling.
- Updated power management callbacks to use struct device * and
added virtio_spi_pm_ops
- Standardized bit macros in virtio_spi.h from BIT(x) to _BITUL(x) for
type safety.
- Improved documentation comments in virtio_spi_config for clarity
and consistency.
Changes between RFC V7 and RFC V8:
- Restored original Copyright.
Changes between RFC V6 and RFC V7:
- Restored original MODULE_AUTHOR information in the driver source file.
- Reinstated the original Signed-off-by tags.
Changes between RFC V5 and RFC V6:
- Dynamically allocate the virtio_spi_req structure instead of keeping
it as a member of virtio_spi_priv.
- Remove redundant comments.
Changes between RFC V4 and RFC V5:
- Use dev_err_probe instead of dev_err in virtio_spi_probe function
to improve error handling.
- Add comments to virtio_spi_set_delays function and revise several
field descriptions in mode_func_supported for improved clarity.
- Update bitmask definitions from (1 << n) to BIT(n) to enhance code
readability.
Changes between non-RFC V3 and RFC V4
- Remove the logic code for statically creating SPI devices through
the spi_new_device function.
- Add ACPI support.
- According to Hillf Danton's comment, use init_completion instead of
reinit_completion in virtio_spi_transfer_one function.
Changes between non-RFC V2 and non-RFC V3
- Child spi device tree nodes are supported now.
If a child spi device tree node exists the setup of the user mode SPI
device is done by spi_register_controller() and the driver itself does
not call spi_new_device() any more to setup the chip selects.
If there is no device tree child node the SPI device sets up the user
mode SPI devices autonomously as it was before.
Changes between non-RFC V1 and non-RFC V2
- Remove some comments stating the obvious
- Remove error trace when devm_spi_alloc_host() failed as this is habit
- Add some blank lines to improve readabilty
- Last TODO comment removed which was used to trigger some discussion.
Discussion did not take place, most probably the code below is correct
as it is
- Abstained from replacing "Cannot " by "Failed to " in error messages
as the wording "Cannot " is frequently used even when "Failed to " has
the majority. Announced this, heard nothing about this, so added the
"Reviewed-by" from Viresh Kumar <viresh.kumar@linaro.org> as
everything else was done.
Changes between RFC V3 and non-RFC V1:
- Address kernel test robot comment which revealed an actual bug
- Rework some comments in the code addressing review comments
- Remove a TODO comment which has served it's purpose
- Allocate struct virtio_spi_req spi_req only once at startup
- Use callback transfer_one instead of transfer_one_message to simplify
and shorten code. Due to this rework in the affected function(s) some
additional changes:
- Do init_completion() only once at startup, for re-initialization
now reinit_completion() is used
- Translate result codes VIRTIO_SPI_PARAM_ERR and VIRTIO_SPI_TRANS_ERR
to appropriate Linux error codes -EINVAL and -EIO
Changes between RFC V2 and RFC V3:
- Order header inclusion alphabetically.
- Add Viresh Kumar's "signed-off" to the header files.
- Rework virtio_spi_one_transfer
- Rework the delays according to Haixu Cui's advise. Delays are now
handled in a new sub-function virtio_spi_set_delays.
- Minor change: Re-formulate arguments of sg_init_one.
- Rework virtio_spi_probe
- Replace some goto in error paths by return.
- Add spi_unregister_controller to an error path. Abstained from
using devm_spi_register_controller to keep order of
de-initialization in virtio_spi_remove.
- Add deletion of vqueue to all error paths taken after the virtqueues
have been initialized.
Changes between RFC V1 and RFC V2:
- Update from virtio SPI draft specification V4 to V10.
- Incorporate review comments gotten from the community.
- A proposal for a performance enhancement having more than only one SPI
message in flight had to be kept out. The more complicated code would
have caused an unacceptable project risk now.
The driver was smoke tested on QEMU using:
- Qualcomm's target hardware with a physical SPI backend device via
vhost-user protocol (Linux v6.12).
- OpenSynergy's proprietary virtio SPI device simulating a SPI backend
on top of linux-next.git (v6.8).
- Regression tested on physical hardware using kernel v6.5.7.
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v9 1/3] virtio: Add ID for virtio SPI
2025-08-28 9:34 [PATCH v9 0/3] Virtio SPI Linux driver Haixu Cui
@ 2025-08-28 9:34 ` Haixu Cui
2025-08-28 9:34 ` [PATCH v9 2/3] virtio-spi: Add virtio-spi.h Haixu Cui
2025-08-28 9:34 ` [PATCH v9 3/3] SPI: Add virtio SPI driver Haixu Cui
2 siblings, 0 replies; 9+ messages in thread
From: Haixu Cui @ 2025-08-28 9:34 UTC (permalink / raw)
To: andriy.shevchenko, harald.mommer, quic_msavaliy, quic_haixcui,
broonie, virtio-dev, viresh.kumar, linux-spi, linux-kernel,
hdanton, qiang4.zhang, alex.bennee
Cc: quic_ztu, virtualization
Add VIRTIO_ID_SPI definition for virtio SPI.
Signed-off-by: Haixu Cui <quic_haixcui@quicinc.com>
Reviewed-by: Viresh Kumar <viresh.kumar@linaro.org>
---
include/uapi/linux/virtio_ids.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
index 7aa2eb766205..6c12db16faa3 100644
--- a/include/uapi/linux/virtio_ids.h
+++ b/include/uapi/linux/virtio_ids.h
@@ -68,6 +68,7 @@
#define VIRTIO_ID_AUDIO_POLICY 39 /* virtio audio policy */
#define VIRTIO_ID_BT 40 /* virtio bluetooth */
#define VIRTIO_ID_GPIO 41 /* virtio gpio */
+#define VIRTIO_ID_SPI 45 /* virtio spi */
/*
* Virtio Transitional IDs
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v9 2/3] virtio-spi: Add virtio-spi.h
2025-08-28 9:34 [PATCH v9 0/3] Virtio SPI Linux driver Haixu Cui
2025-08-28 9:34 ` [PATCH v9 1/3] virtio: Add ID for virtio SPI Haixu Cui
@ 2025-08-28 9:34 ` Haixu Cui
2025-08-28 9:34 ` [PATCH v9 3/3] SPI: Add virtio SPI driver Haixu Cui
2 siblings, 0 replies; 9+ messages in thread
From: Haixu Cui @ 2025-08-28 9:34 UTC (permalink / raw)
To: andriy.shevchenko, harald.mommer, quic_msavaliy, quic_haixcui,
broonie, virtio-dev, viresh.kumar, linux-spi, linux-kernel,
hdanton, qiang4.zhang, alex.bennee
Cc: quic_ztu, virtualization
Add virtio-spi.h header for virtio SPI.
Signed-off-by: Haixu Cui <quic_haixcui@quicinc.com>
---
MAINTAINERS | 6 ++
include/uapi/linux/virtio_spi.h | 181 ++++++++++++++++++++++++++++++++
2 files changed, 187 insertions(+)
create mode 100644 include/uapi/linux/virtio_spi.h
diff --git a/MAINTAINERS b/MAINTAINERS
index fed6cd812d79..af98e411138d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26790,6 +26790,12 @@ S: Maintained
F: include/uapi/linux/virtio_snd.h
F: sound/virtio/*
+VIRTIO SPI DRIVER
+M: Haixu Cui <quic_haixcui@quicinc.com>
+L: virtualization@lists.linux-foundation.org
+S: Maintained
+F: include/uapi/linux/virtio_spi.h
+
VIRTUAL BOX GUEST DEVICE DRIVER
M: Hans de Goede <hansg@kernel.org>
M: Arnd Bergmann <arnd@arndb.de>
diff --git a/include/uapi/linux/virtio_spi.h b/include/uapi/linux/virtio_spi.h
new file mode 100644
index 000000000000..8ab3c970cdd3
--- /dev/null
+++ b/include/uapi/linux/virtio_spi.h
@@ -0,0 +1,181 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ * Copyright (C) 2023 OpenSynergy GmbH
+ * Copyright (C) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#ifndef _LINUX_VIRTIO_VIRTIO_SPI_H
+#define _LINUX_VIRTIO_VIRTIO_SPI_H
+
+#include <linux/types.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_ids.h>
+#include <linux/virtio_types.h>
+
+/* Sample data on trailing clock edge */
+#define VIRTIO_SPI_CPHA _BITUL(0)
+/* Clock is high when IDLE */
+#define VIRTIO_SPI_CPOL _BITUL(1)
+/* Chip Select is active high */
+#define VIRTIO_SPI_CS_HIGH _BITUL(2)
+/* Transmit LSB first */
+#define VIRTIO_SPI_MODE_LSB_FIRST _BITUL(3)
+/* Loopback mode */
+#define VIRTIO_SPI_MODE_LOOP _BITUL(4)
+
+/**
+ * struct virtio_spi_config - All config fields are read-only for the
+ * Virtio SPI driver
+ * @cs_max_number: maximum number of chipselect the host SPI controller
+ * supports.
+ * @cs_change_supported: indicates if the host SPI controller supports to toggle
+ * chipselect after each transfer in one message:
+ * 0: unsupported, chipselect will be kept in active state throughout the
+ * message transaction;
+ * 1: supported.
+ * Note: Message here contains a sequence of SPI transfers.
+ * @tx_nbits_supported: indicates the supported number of bit for writing:
+ * bit 0: DUAL (2-bit transfer), 1 for supported
+ * bit 1: QUAD (4-bit transfer), 1 for supported
+ * bit 2: OCTAL (8-bit transfer), 1 for supported
+ * other bits are reserved as 0, 1-bit transfer is always supported.
+ * @rx_nbits_supported: indicates the supported number of bit for reading:
+ * bit 0: DUAL (2-bit transfer), 1 for supported
+ * bit 1: QUAD (4-bit transfer), 1 for supported
+ * bit 2: OCTAL (8-bit transfer), 1 for supported
+ * other bits are reserved as 0, 1-bit transfer is always supported.
+ * @bits_per_word_mask: mask indicating which values of bits_per_word are
+ * supported. If not set, no limitation for bits_per_word.
+ * @mode_func_supported: indicates the following features are supported or not:
+ * bit 0-1: CPHA feature
+ * 0b00: invalid, should support as least one CPHA setting
+ * 0b01: supports CPHA=0 only
+ * 0b10: supports CPHA=1 only
+ * 0b11: supports CPHA=0 and CPHA=1.
+ * bit 2-3: CPOL feature
+ * 0b00: invalid, should support as least one CPOL setting
+ * 0b01: supports CPOL=0 only
+ * 0b10: supports CPOL=1 only
+ * 0b11: supports CPOL=0 and CPOL=1.
+ * bit 4: chipselect active high feature, 0 for unsupported and 1 for
+ * supported, chipselect active low is supported by default.
+ * bit 5: LSB first feature, 0 for unsupported and 1 for supported,
+ * MSB first is supported by default.
+ * bit 6: loopback mode feature, 0 for unsupported and 1 for supported,
+ * normal mode is supported by default.
+ * @max_freq_hz: the maximum clock rate supported in Hz unit, 0 means no
+ * limitation for transfer speed.
+ * @max_word_delay_ns: the maximum word delay supported, in nanoseconds.
+ * A value of 0 indicates that word delay is unsupported.
+ * Each transfer may consist of a sequence of words.
+ * @max_cs_setup_ns: the maximum delay supported after chipselect is asserted,
+ * in ns unit, 0 means delay is not supported to introduce after chipselect is
+ * asserted.
+ * @max_cs_hold_ns: the maximum delay supported before chipselect is deasserted,
+ * in ns unit, 0 means delay is not supported to introduce before chipselect
+ * is deasserted.
+ * @max_cs_incative_ns: maximum delay supported after chipselect is deasserted,
+ * in ns unit, 0 means delay is not supported to introduce after chipselect is
+ * deasserted.
+ */
+struct virtio_spi_config {
+ __u8 cs_max_number;
+ __u8 cs_change_supported;
+#define VIRTIO_SPI_RX_TX_SUPPORT_DUAL _BITUL(0)
+#define VIRTIO_SPI_RX_TX_SUPPORT_QUAD _BITUL(1)
+#define VIRTIO_SPI_RX_TX_SUPPORT_OCTAL _BITUL(2)
+ __u8 tx_nbits_supported;
+ __u8 rx_nbits_supported;
+ __le32 bits_per_word_mask;
+#define VIRTIO_SPI_MF_SUPPORT_CPHA_0 _BITUL(0)
+#define VIRTIO_SPI_MF_SUPPORT_CPHA_1 _BITUL(1)
+#define VIRTIO_SPI_MF_SUPPORT_CPOL_0 _BITUL(2)
+#define VIRTIO_SPI_MF_SUPPORT_CPOL_1 _BITUL(3)
+#define VIRTIO_SPI_MF_SUPPORT_CS_HIGH _BITUL(4)
+#define VIRTIO_SPI_MF_SUPPORT_LSB_FIRST _BITUL(5)
+#define VIRTIO_SPI_MF_SUPPORT_LOOPBACK _BITUL(6)
+ __le32 mode_func_supported;
+ __le32 max_freq_hz;
+ __le32 max_word_delay_ns;
+ __le32 max_cs_setup_ns;
+ __le32 max_cs_hold_ns;
+ __le32 max_cs_inactive_ns;
+};
+
+/**
+ * struct spi_transfer_head - virtio SPI transfer descriptor
+ * @chip_select_id: chipselect index the SPI transfer used.
+ * @bits_per_word: the number of bits in each SPI transfer word.
+ * @cs_change: whether to deselect device after finishing this transfer
+ * before starting the next transfer, 0 means cs keep asserted and
+ * 1 means cs deasserted then asserted again.
+ * @tx_nbits: bus width for write transfer.
+ * 0,1: bus width is 1, also known as SINGLE
+ * 2 : bus width is 2, also known as DUAL
+ * 4 : bus width is 4, also known as QUAD
+ * 8 : bus width is 8, also known as OCTAL
+ * other values are invalid.
+ * @rx_nbits: bus width for read transfer.
+ * 0,1: bus width is 1, also known as SINGLE
+ * 2 : bus width is 2, also known as DUAL
+ * 4 : bus width is 4, also known as QUAD
+ * 8 : bus width is 8, also known as OCTAL
+ * other values are invalid.
+ * @reserved: for future use.
+ * @mode: SPI transfer mode.
+ * bit 0: CPHA, determines the timing (i.e. phase) of the data
+ * bits relative to the clock pulses.For CPHA=0, the
+ * "out" side changes the data on the trailing edge of the
+ * preceding clock cycle, while the "in" side captures the data
+ * on (or shortly after) the leading edge of the clock cycle.
+ * For CPHA=1, the "out" side changes the data on the leading
+ * edge of the current clock cycle, while the "in" side
+ * captures the data on (or shortly after) the trailing edge of
+ * the clock cycle.
+ * bit 1: CPOL, determines the polarity of the clock. CPOL=0 is a
+ * clock which idles at 0, and each cycle consists of a pulse
+ * of 1. CPOL=1 is a clock which idles at 1, and each cycle
+ * consists of a pulse of 0.
+ * bit 2: CS_HIGH, if 1, chip select active high, else active low.
+ * bit 3: LSB_FIRST, determines per-word bits-on-wire, if 0, MSB
+ * first, else LSB first.
+ * bit 4: LOOP, loopback mode.
+ * @freq: the transfer speed in Hz.
+ * @word_delay_ns: delay to be inserted between consecutive words of a
+ * transfer, in ns unit.
+ * @cs_setup_ns: delay to be introduced after CS is asserted, in ns
+ * unit.
+ * @cs_delay_hold_ns: delay to be introduced before CS is deasserted
+ * for each transfer, in ns unit.
+ * @cs_change_delay_inactive_ns: delay to be introduced after CS is
+ * deasserted and before next asserted, in ns unit.
+ */
+struct spi_transfer_head {
+ __u8 chip_select_id;
+ __u8 bits_per_word;
+ __u8 cs_change;
+ __u8 tx_nbits;
+ __u8 rx_nbits;
+ __u8 reserved[3];
+ __le32 mode;
+ __le32 freq;
+ __le32 word_delay_ns;
+ __le32 cs_setup_ns;
+ __le32 cs_delay_hold_ns;
+ __le32 cs_change_delay_inactive_ns;
+};
+
+/**
+ * struct spi_transfer_result - virtio SPI transfer result
+ * @result: Transfer result code.
+ * VIRTIO_SPI_TRANS_OK: Transfer successful.
+ * VIRTIO_SPI_PARAM_ERR: Parameter error.
+ * VIRTIO_SPI_TRANS_ERR: Transfer error.
+ */
+struct spi_transfer_result {
+#define VIRTIO_SPI_TRANS_OK 0
+#define VIRTIO_SPI_PARAM_ERR 1
+#define VIRTIO_SPI_TRANS_ERR 2
+ __u8 result;
+};
+
+#endif /* #ifndef _LINUX_VIRTIO_VIRTIO_SPI_H */
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v9 3/3] SPI: Add virtio SPI driver
2025-08-28 9:34 [PATCH v9 0/3] Virtio SPI Linux driver Haixu Cui
2025-08-28 9:34 ` [PATCH v9 1/3] virtio: Add ID for virtio SPI Haixu Cui
2025-08-28 9:34 ` [PATCH v9 2/3] virtio-spi: Add virtio-spi.h Haixu Cui
@ 2025-08-28 9:34 ` Haixu Cui
2025-09-01 12:07 ` Andy Shevchenko
2 siblings, 1 reply; 9+ messages in thread
From: Haixu Cui @ 2025-08-28 9:34 UTC (permalink / raw)
To: andriy.shevchenko, harald.mommer, quic_msavaliy, quic_haixcui,
broonie, virtio-dev, viresh.kumar, linux-spi, linux-kernel,
hdanton, qiang4.zhang, alex.bennee
Cc: quic_ztu, virtualization
This is the virtio SPI Linux kernel driver.
Signed-off-by: Haixu Cui <quic_haixcui@quicinc.com>
---
MAINTAINERS | 1 +
drivers/spi/Kconfig | 11 +
drivers/spi/Makefile | 1 +
drivers/spi/spi-virtio.c | 438 +++++++++++++++++++++++++++++++++++++++
4 files changed, 451 insertions(+)
create mode 100644 drivers/spi/spi-virtio.c
diff --git a/MAINTAINERS b/MAINTAINERS
index af98e411138d..50f77c0c9d3f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26794,6 +26794,7 @@ VIRTIO SPI DRIVER
M: Haixu Cui <quic_haixcui@quicinc.com>
L: virtualization@lists.linux-foundation.org
S: Maintained
+F: drivers/spi/spi-virtio.c
F: include/uapi/linux/virtio_spi.h
VIRTUAL BOX GUEST DEVICE DRIVER
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 891729c9c564..7b609013fb05 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -1224,6 +1224,17 @@ config SPI_UNIPHIER
If your SoC supports SCSSI, say Y here.
+config SPI_VIRTIO
+ tristate "Virtio SPI Controller"
+ depends on SPI_MASTER && VIRTIO
+ help
+ If you say yes to this option, support will be included for the virtio
+ SPI controller driver. The hardware can be emulated by any device model
+ software according to the virtio protocol.
+
+ This driver can also be built as a module. If so, the module
+ will be called spi-virtio.
+
config SPI_XCOMM
tristate "Analog Devices AD-FMCOMMS1-EBZ SPI-I2C-bridge driver"
depends on I2C
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 062c85989c8c..27a7cf68d55d 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -158,6 +158,7 @@ spi-thunderx-objs := spi-cavium.o spi-cavium-thunderx.o
obj-$(CONFIG_SPI_THUNDERX) += spi-thunderx.o
obj-$(CONFIG_SPI_TOPCLIFF_PCH) += spi-topcliff-pch.o
obj-$(CONFIG_SPI_UNIPHIER) += spi-uniphier.o
+obj-$(CONFIG_SPI_VIRTIO) += spi-virtio.o
obj-$(CONFIG_SPI_XCOMM) += spi-xcomm.o
obj-$(CONFIG_SPI_XILINX) += spi-xilinx.o
obj-$(CONFIG_SPI_XLP) += spi-xlp.o
diff --git a/drivers/spi/spi-virtio.c b/drivers/spi/spi-virtio.c
new file mode 100644
index 000000000000..ed0a071e760c
--- /dev/null
+++ b/drivers/spi/spi-virtio.c
@@ -0,0 +1,438 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * SPI bus driver for the Virtio SPI controller
+ * Copyright (C) 2023 OpenSynergy GmbH
+ * Copyright (C) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/stddef.h>
+#include <linux/virtio.h>
+#include <linux/virtio_ring.h>
+#include <linux/virtio_spi.h>
+
+#define VIRTIO_SPI_MODE_MASK \
+ (SPI_MODE_X_MASK | SPI_CS_HIGH | SPI_LSB_FIRST)
+
+struct virtio_spi_req {
+ struct completion completion;
+ const u8 *tx_buf;
+ u8 *rx_buf;
+ struct spi_transfer_head transfer_head ____cacheline_aligned;
+ struct spi_transfer_result result;
+};
+
+struct virtio_spi_priv {
+ /* The virtio device we're associated with */
+ struct virtio_device *vdev;
+ /* Pointer to the virtqueue */
+ struct virtqueue *vq;
+ /* Copy of config space mode_func_supported */
+ u32 mode_func_supported;
+ /* Copy of config space max_freq_hz */
+ u32 max_freq_hz;
+};
+
+static void virtio_spi_msg_done(struct virtqueue *vq)
+{
+ struct virtio_spi_req *req;
+ unsigned int len;
+
+ while ((req = virtqueue_get_buf(vq, &len)))
+ complete(&req->completion);
+}
+
+/*
+ * virtio_spi_set_delays - Set delay parameters for SPI transfer
+ *
+ * This function sets various delay parameters for SPI transfer,
+ * including delay after CS asserted, timing intervals between
+ * adjacent words within a transfer, delay before and after CS
+ * deasserted. It converts these delay parameters to nanoseconds
+ * using spi_delay_to_ns and stores the results in spi_transfer_head
+ * structure.
+ * If the conversion fails, the function logs a warning message and
+ * returns an error code.
+ * . . . . . . . . . .
+ * Delay + A + + B + + C + D + E + F + A +
+ * . . . . . . . . . .
+ * ___. . . . . . .___.___. .
+ * CS# |___.______.____.____.___.___| . |___._____________
+ * . . . . . . . . . .
+ * . . . . . . . . . .
+ * SCLK__.___.___NNN_____NNN__.___.___.___.___.___.___NNN_______
+ *
+ * NOTE: 1st transfer has two words, the delay between these two words are
+ * 'B' in the diagram.
+ *
+ * A => struct spi_device -> cs_setup
+ * B => max{struct spi_transfer -> word_delay, struct spi_device -> word_delay}
+ * Note: spi_device and spi_transfer both have word_delay, Linux
+ * choose the bigger one, refer to _spi_xfer_word_delay_update function
+ * C => struct spi_transfer -> delay
+ * D => struct spi_device -> cs_hold
+ * E => struct spi_device -> cs_inactive
+ * F => struct spi_transfer -> cs_change_delay
+ *
+ * So the corresponding relationship:
+ * A <===> cs_setup_ns (after CS asserted)
+ * B <===> word_delay_ns (delay between adjacent words within a transfer)
+ * C+D <===> cs_delay_hold_ns (before CS deasserted)
+ * E+F <===> cs_change_delay_inactive_ns (after CS deasserted, these two
+ * values are also recommended in the Linux driver to be added up)
+ */
+static int virtio_spi_set_delays(struct spi_transfer_head *th,
+ struct spi_device *spi,
+ struct spi_transfer *xfer)
+{
+ int cs_setup;
+ int cs_word_delay_xfer;
+ int cs_word_delay_spi;
+ int delay;
+ int cs_hold;
+ int cs_inactive;
+ int cs_change_delay;
+
+ cs_setup = spi_delay_to_ns(&spi->cs_setup, xfer);
+ if (cs_setup < 0) {
+ dev_warn(&spi->dev, "Cannot convert cs_setup\n");
+ return cs_setup;
+ }
+ th->cs_setup_ns = cpu_to_le32(cs_setup);
+
+ cs_word_delay_xfer = spi_delay_to_ns(&xfer->word_delay, xfer);
+ if (cs_word_delay_xfer < 0) {
+ dev_warn(&spi->dev, "Cannot convert cs_word_delay_xfer\n");
+ return cs_word_delay_xfer;
+ }
+ cs_word_delay_spi = spi_delay_to_ns(&spi->word_delay, xfer);
+ if (cs_word_delay_spi < 0) {
+ dev_warn(&spi->dev, "Cannot convert cs_word_delay_spi\n");
+ return cs_word_delay_spi;
+ }
+
+ th->word_delay_ns = cpu_to_le32(max(cs_word_delay_spi, cs_word_delay_xfer));
+
+ delay = spi_delay_to_ns(&xfer->delay, xfer);
+ if (delay < 0) {
+ dev_warn(&spi->dev, "Cannot convert delay\n");
+ return delay;
+ }
+ cs_hold = spi_delay_to_ns(&spi->cs_hold, xfer);
+ if (cs_hold < 0) {
+ dev_warn(&spi->dev, "Cannot convert cs_hold\n");
+ return cs_hold;
+ }
+ th->cs_delay_hold_ns = cpu_to_le32(delay + cs_hold);
+
+ cs_inactive = spi_delay_to_ns(&spi->cs_inactive, xfer);
+ if (cs_inactive < 0) {
+ dev_warn(&spi->dev, "Cannot convert cs_inactive\n");
+ return cs_inactive;
+ }
+ cs_change_delay = spi_delay_to_ns(&xfer->cs_change_delay, xfer);
+ if (cs_change_delay < 0) {
+ dev_warn(&spi->dev, "Cannot convert cs_change_delay\n");
+ return cs_change_delay;
+ }
+ th->cs_change_delay_inactive_ns =
+ cpu_to_le32(cs_inactive + cs_change_delay);
+
+ return 0;
+}
+
+static int virtio_spi_transfer_one(struct spi_controller *ctrl,
+ struct spi_device *spi,
+ struct spi_transfer *xfer)
+{
+ struct virtio_spi_priv *priv = spi_controller_get_devdata(ctrl);
+ struct virtio_spi_req *spi_req __free(kfree);
+ struct spi_transfer_head *th;
+ struct scatterlist sg_out_head, sg_out_payload;
+ struct scatterlist sg_in_result, sg_in_payload;
+ struct scatterlist *sgs[4];
+ unsigned int outcnt = 0;
+ unsigned int incnt = 0;
+ int ret;
+
+ spi_req = kzalloc(sizeof(*spi_req), GFP_KERNEL);
+ if (!spi_req)
+ return -ENOMEM;
+
+ init_completion(&spi_req->completion);
+
+ th = &spi_req->transfer_head;
+
+ /* Fill struct spi_transfer_head */
+ th->chip_select_id = spi_get_chipselect(spi, 0);
+ th->bits_per_word = spi->bits_per_word;
+ th->cs_change = xfer->cs_change;
+ th->tx_nbits = xfer->tx_nbits;
+ th->rx_nbits = xfer->rx_nbits;
+ th->reserved[0] = 0;
+ th->reserved[1] = 0;
+ th->reserved[2] = 0;
+
+ static_assert(VIRTIO_SPI_CPHA == SPI_CPHA,
+ "VIRTIO_SPI_CPHA must match SPI_CPHA");
+ static_assert(VIRTIO_SPI_CPOL == SPI_CPOL,
+ "VIRTIO_SPI_CPOL must match SPI_CPOL");
+ static_assert(VIRTIO_SPI_CS_HIGH == SPI_CS_HIGH,
+ "VIRTIO_SPI_CS_HIGH must match SPI_CS_HIGH");
+ static_assert(VIRTIO_SPI_MODE_LSB_FIRST == SPI_LSB_FIRST,
+ "VIRTIO_SPI_MODE_LSB_FIRST must match SPI_LSB_FIRST");
+
+ th->mode = cpu_to_le32(spi->mode & VIRTIO_SPI_MODE_MASK);
+ if (spi->mode & SPI_LOOP)
+ th->mode |= cpu_to_le32(VIRTIO_SPI_MODE_LOOP);
+
+ th->freq = cpu_to_le32(xfer->speed_hz);
+
+ ret = virtio_spi_set_delays(th, spi, xfer);
+ if (ret)
+ goto msg_done;
+
+ /* Set buffers */
+ spi_req->tx_buf = xfer->tx_buf;
+ spi_req->rx_buf = xfer->rx_buf;
+
+ /* Prepare sending of virtio message */
+ init_completion(&spi_req->completion);
+
+ sg_init_one(&sg_out_head, th, sizeof(*th));
+ sgs[outcnt] = &sg_out_head;
+ outcnt++;
+
+ if (spi_req->tx_buf) {
+ sg_init_one(&sg_out_payload, spi_req->tx_buf, xfer->len);
+ sgs[outcnt] = &sg_out_payload;
+ outcnt++;
+ }
+
+ if (spi_req->rx_buf) {
+ sg_init_one(&sg_in_payload, spi_req->rx_buf, xfer->len);
+ sgs[outcnt] = &sg_in_payload;
+ incnt++;
+ }
+
+ sg_init_one(&sg_in_result, &spi_req->result,
+ sizeof(struct spi_transfer_result));
+ sgs[outcnt + incnt] = &sg_in_result;
+ incnt++;
+
+ ret = virtqueue_add_sgs(priv->vq, sgs, outcnt, incnt, spi_req,
+ GFP_KERNEL);
+ if (ret)
+ goto msg_done;
+
+ /* Simple implementation: There can be only one transfer in flight */
+ virtqueue_kick(priv->vq);
+
+ wait_for_completion(&spi_req->completion);
+
+ /* Read result from message and translate return code */
+ switch (spi_req->result.result) {
+ case VIRTIO_SPI_TRANS_OK:
+ break;
+ case VIRTIO_SPI_PARAM_ERR:
+ ret = -EINVAL;
+ break;
+ case VIRTIO_SPI_TRANS_ERR:
+ ret = -EIO;
+ break;
+ default:
+ ret = -EIO;
+ break;
+ }
+
+msg_done:
+ if (ret)
+ ctrl->cur_msg->status = ret;
+
+ return ret;
+}
+
+static void virtio_spi_read_config(struct virtio_device *vdev)
+{
+ struct spi_controller *ctrl = dev_get_drvdata(&vdev->dev);
+ struct virtio_spi_priv *priv = vdev->priv;
+ u8 cs_max_number;
+ u8 tx_nbits_supported;
+ u8 rx_nbits_supported;
+
+ cs_max_number = virtio_cread8(vdev, offsetof(struct virtio_spi_config,
+ cs_max_number));
+ ctrl->num_chipselect = cs_max_number;
+
+ /* Set the mode bits which are understood by this driver */
+ priv->mode_func_supported =
+ virtio_cread32(vdev, offsetof(struct virtio_spi_config,
+ mode_func_supported));
+ ctrl->mode_bits = priv->mode_func_supported &
+ (VIRTIO_SPI_CS_HIGH | VIRTIO_SPI_MODE_LSB_FIRST);
+ if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPHA_1)
+ ctrl->mode_bits |= VIRTIO_SPI_CPHA;
+ if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPOL_1)
+ ctrl->mode_bits |= VIRTIO_SPI_CPOL;
+ if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LSB_FIRST)
+ ctrl->mode_bits |= SPI_LSB_FIRST;
+ if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LOOPBACK)
+ ctrl->mode_bits |= SPI_LOOP;
+ tx_nbits_supported =
+ virtio_cread8(vdev, offsetof(struct virtio_spi_config,
+ tx_nbits_supported));
+ if (tx_nbits_supported & VIRTIO_SPI_RX_TX_SUPPORT_DUAL)
+ ctrl->mode_bits |= SPI_TX_DUAL;
+ if (tx_nbits_supported & VIRTIO_SPI_RX_TX_SUPPORT_QUAD)
+ ctrl->mode_bits |= SPI_TX_QUAD;
+ if (tx_nbits_supported & VIRTIO_SPI_RX_TX_SUPPORT_OCTAL)
+ ctrl->mode_bits |= SPI_TX_OCTAL;
+ rx_nbits_supported =
+ virtio_cread8(vdev, offsetof(struct virtio_spi_config,
+ rx_nbits_supported));
+ if (rx_nbits_supported & VIRTIO_SPI_RX_TX_SUPPORT_DUAL)
+ ctrl->mode_bits |= SPI_RX_DUAL;
+ if (rx_nbits_supported & VIRTIO_SPI_RX_TX_SUPPORT_QUAD)
+ ctrl->mode_bits |= SPI_RX_QUAD;
+ if (rx_nbits_supported & VIRTIO_SPI_RX_TX_SUPPORT_OCTAL)
+ ctrl->mode_bits |= SPI_RX_OCTAL;
+
+ ctrl->bits_per_word_mask =
+ virtio_cread32(vdev, offsetof(struct virtio_spi_config,
+ bits_per_word_mask));
+
+ priv->max_freq_hz =
+ virtio_cread32(vdev, offsetof(struct virtio_spi_config,
+ max_freq_hz));
+}
+
+static int virtio_spi_find_vqs(struct virtio_spi_priv *priv)
+{
+ struct virtqueue *vq;
+
+ vq = virtio_find_single_vq(priv->vdev, virtio_spi_msg_done, "spi-rq");
+ if (IS_ERR(vq))
+ return PTR_ERR(vq);
+ priv->vq = vq;
+ return 0;
+}
+
+/* Function must not be called before virtio_spi_find_vqs() has been run */
+static void virtio_spi_del_vq(void *data)
+{
+ struct virtio_device *vdev = data;
+
+ virtio_reset_device(vdev);
+ vdev->config->del_vqs(vdev);
+}
+
+static int virtio_spi_probe(struct virtio_device *vdev)
+{
+ struct virtio_spi_priv *priv;
+ struct spi_controller *ctrl;
+ int ret;
+ u32 bus_num;
+
+ ctrl = devm_spi_alloc_host(&vdev->dev, sizeof(*priv));
+ if (!ctrl)
+ return -ENOMEM;
+
+ priv = spi_controller_get_devdata(ctrl);
+ priv->vdev = vdev;
+ vdev->priv = priv;
+
+ device_set_node(&ctrl->dev, dev_fwnode(&vdev->dev));
+
+ dev_set_drvdata(&vdev->dev, ctrl);
+
+ ret = device_property_read_u32(&vdev->dev, "spi,bus-num", &bus_num);
+ if (ret || bus_num > S16_MAX)
+ ctrl->bus_num = -1;
+ else
+ ctrl->bus_num = bus_num;
+
+ virtio_spi_read_config(vdev);
+
+ ctrl->transfer_one = virtio_spi_transfer_one;
+
+ ret = virtio_spi_find_vqs(priv);
+ if (ret)
+ return dev_err_probe(&vdev->dev, ret, "Cannot setup virtqueues\n");
+
+ /* Register cleanup for virtqueues using devm */
+ ret = devm_add_action_or_reset(&vdev->dev, virtio_spi_del_vq, vdev);
+ if (ret)
+ return dev_err_probe(&vdev->dev, ret, "Cannot register virtqueue cleanup\n");
+
+ /* Use devm version to register controller */
+ ret = devm_spi_register_controller(&vdev->dev, ctrl);
+ if (ret)
+ return dev_err_probe(&vdev->dev, ret, "Cannot register controller\n");
+
+ return 0;
+}
+
+static int virtio_spi_freeze(struct device *dev)
+{
+ struct spi_controller *ctrl = dev_get_drvdata(dev);
+ struct virtio_device *vdev = dev_to_virtio(dev);
+ int ret;
+
+ ret = spi_controller_suspend(ctrl);
+ if (ret) {
+ dev_warn(dev, "cannot suspend controller (%d)\n", ret);
+ return ret;
+ }
+
+ virtio_spi_del_vq(vdev);
+ return 0;
+}
+
+static int virtio_spi_restore(struct device *dev)
+{
+ struct spi_controller *ctrl = dev_get_drvdata(dev);
+ struct virtio_device *vdev = dev_to_virtio(dev);
+ int ret;
+
+ ret = virtio_spi_find_vqs(vdev->priv);
+ if (ret) {
+ dev_err(dev, "problem starting vqueue (%d)\n", ret);
+ return ret;
+ }
+
+ ret = spi_controller_resume(ctrl);
+ if (ret)
+ dev_err(dev, "problem resuming controller (%d)\n", ret);
+
+ return ret;
+}
+
+static struct virtio_device_id virtio_spi_id_table[] = {
+ { VIRTIO_ID_SPI, VIRTIO_DEV_ANY_ID },
+ {}
+};
+MODULE_DEVICE_TABLE(virtio, virtio_spi_id_table);
+
+static const struct dev_pm_ops virtio_spi_pm_ops = {
+ .freeze = pm_sleep_ptr(virtio_spi_freeze),
+ .restore = pm_sleep_ptr(virtio_spi_restore),
+};
+
+static struct virtio_driver virtio_spi_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .pm = &virtio_spi_pm_ops,
+ },
+ .id_table = virtio_spi_id_table,
+ .probe = virtio_spi_probe,
+};
+module_virtio_driver(virtio_spi_driver);
+
+MODULE_AUTHOR("OpenSynergy GmbH");
+MODULE_AUTHOR("Haixu Cui <quic_haixcui@quicinc.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Virtio SPI bus driver");
--
2.34.1
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v9 3/3] SPI: Add virtio SPI driver
2025-08-28 9:34 ` [PATCH v9 3/3] SPI: Add virtio SPI driver Haixu Cui
@ 2025-09-01 12:07 ` Andy Shevchenko
2025-09-02 15:54 ` Harald Mommer
2025-09-03 9:04 ` Haixu Cui
0 siblings, 2 replies; 9+ messages in thread
From: Andy Shevchenko @ 2025-09-01 12:07 UTC (permalink / raw)
To: Haixu Cui
Cc: harald.mommer, quic_msavaliy, broonie, virtio-dev, viresh.kumar,
linux-spi, linux-kernel, hdanton, qiang4.zhang, alex.bennee,
quic_ztu, virtualization
On Thu, Aug 28, 2025 at 05:34:51PM +0800, Haixu Cui wrote:
> This is the virtio SPI Linux kernel driver.
...
> +#include <linux/completion.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/spi/spi.h>
> +#include <linux/stddef.h>
A lot of headers are still missing. See below.
...
> +struct virtio_spi_priv {
> + /* The virtio device we're associated with */
> + struct virtio_device *vdev;
> + /* Pointer to the virtqueue */
> + struct virtqueue *vq;
> + /* Copy of config space mode_func_supported */
> + u32 mode_func_supported;
uXX (in particular u32) is defined in types.h.
> + /* Copy of config space max_freq_hz */
> + u32 max_freq_hz;
> +};
...
> +static int virtio_spi_set_delays(struct spi_transfer_head *th,
> + struct spi_device *spi,
> + struct spi_transfer *xfer)
> +{
> + int cs_setup;
> + int cs_word_delay_xfer;
> + int cs_word_delay_spi;
> + int delay;
> + int cs_hold;
> + int cs_inactive;
> + int cs_change_delay;
> +
> + cs_setup = spi_delay_to_ns(&spi->cs_setup, xfer);
> + if (cs_setup < 0) {
> + dev_warn(&spi->dev, "Cannot convert cs_setup\n");
dev_warn() et al. are defined in dev_printk.h.
> + return cs_setup;
> + }
> + th->cs_setup_ns = cpu_to_le32(cs_setup);
This requires
#include <asm/byteorder.h>
> + cs_word_delay_xfer = spi_delay_to_ns(&xfer->word_delay, xfer);
> + if (cs_word_delay_xfer < 0) {
> + dev_warn(&spi->dev, "Cannot convert cs_word_delay_xfer\n");
> + return cs_word_delay_xfer;
> + }
> + cs_word_delay_spi = spi_delay_to_ns(&spi->word_delay, xfer);
> + if (cs_word_delay_spi < 0) {
> + dev_warn(&spi->dev, "Cannot convert cs_word_delay_spi\n");
> + return cs_word_delay_spi;
> + }
> +
> + th->word_delay_ns = cpu_to_le32(max(cs_word_delay_spi, cs_word_delay_xfer));
max() is defined in math.h.
> + delay = spi_delay_to_ns(&xfer->delay, xfer);
> + if (delay < 0) {
> + dev_warn(&spi->dev, "Cannot convert delay\n");
> + return delay;
> + }
> + cs_hold = spi_delay_to_ns(&spi->cs_hold, xfer);
> + if (cs_hold < 0) {
> + dev_warn(&spi->dev, "Cannot convert cs_hold\n");
> + return cs_hold;
> + }
> + th->cs_delay_hold_ns = cpu_to_le32(delay + cs_hold);
> +
> + cs_inactive = spi_delay_to_ns(&spi->cs_inactive, xfer);
> + if (cs_inactive < 0) {
> + dev_warn(&spi->dev, "Cannot convert cs_inactive\n");
> + return cs_inactive;
> + }
> + cs_change_delay = spi_delay_to_ns(&xfer->cs_change_delay, xfer);
> + if (cs_change_delay < 0) {
> + dev_warn(&spi->dev, "Cannot convert cs_change_delay\n");
> + return cs_change_delay;
> + }
> + th->cs_change_delay_inactive_ns =
> + cpu_to_le32(cs_inactive + cs_change_delay);
> +
> + return 0;
> +}
...
> +static int virtio_spi_transfer_one(struct spi_controller *ctrl,
> + struct spi_device *spi,
> + struct spi_transfer *xfer)
> +{
> + struct virtio_spi_priv *priv = spi_controller_get_devdata(ctrl);
> + struct virtio_spi_req *spi_req __free(kfree);
This is incorrect template. It's one of the exceptions when we mix code and
definitions...
> + struct spi_transfer_head *th;
> + struct scatterlist sg_out_head, sg_out_payload;
> + struct scatterlist sg_in_result, sg_in_payload;
+ scatterlist.h
> + struct scatterlist *sgs[4];
> + unsigned int outcnt = 0;
> + unsigned int incnt = 0;
> + int ret;
> + spi_req = kzalloc(sizeof(*spi_req), GFP_KERNEL);
...so this should be
struct virtio_spi_req *spi_req __free(kfree) =
kzalloc(sizeof(*spi_req), GFP_KERNEL);
(or on one line if you are okay with a 100 limit).
And do not forget to include cleanup.h (__free() macro) and
slab.h (kzalloc() API).
> + if (!spi_req)
> + return -ENOMEM;
+ errno.h
But since you already have IS_ERR()/PTR_ERR() use, just
+ err.h
> + init_completion(&spi_req->completion);
> +
> + th = &spi_req->transfer_head;
> +
> + /* Fill struct spi_transfer_head */
> + th->chip_select_id = spi_get_chipselect(spi, 0);
> + th->bits_per_word = spi->bits_per_word;
> + th->cs_change = xfer->cs_change;
> + th->tx_nbits = xfer->tx_nbits;
> + th->rx_nbits = xfer->rx_nbits;
> + th->reserved[0] = 0;
> + th->reserved[1] = 0;
> + th->reserved[2] = 0;
> +
> + static_assert(VIRTIO_SPI_CPHA == SPI_CPHA,
> + "VIRTIO_SPI_CPHA must match SPI_CPHA");
> + static_assert(VIRTIO_SPI_CPOL == SPI_CPOL,
> + "VIRTIO_SPI_CPOL must match SPI_CPOL");
> + static_assert(VIRTIO_SPI_CS_HIGH == SPI_CS_HIGH,
> + "VIRTIO_SPI_CS_HIGH must match SPI_CS_HIGH");
> + static_assert(VIRTIO_SPI_MODE_LSB_FIRST == SPI_LSB_FIRST,
> + "VIRTIO_SPI_MODE_LSB_FIRST must match SPI_LSB_FIRST");
> +
> + th->mode = cpu_to_le32(spi->mode & VIRTIO_SPI_MODE_MASK);
> + if (spi->mode & SPI_LOOP)
> + th->mode |= cpu_to_le32(VIRTIO_SPI_MODE_LOOP);
> +
> + th->freq = cpu_to_le32(xfer->speed_hz);
> +
> + ret = virtio_spi_set_delays(th, spi, xfer);
> + if (ret)
> + goto msg_done;
> +
> + /* Set buffers */
> + spi_req->tx_buf = xfer->tx_buf;
> + spi_req->rx_buf = xfer->rx_buf;
> +
> + /* Prepare sending of virtio message */
> + init_completion(&spi_req->completion);
> +
> + sg_init_one(&sg_out_head, th, sizeof(*th));
> + sgs[outcnt] = &sg_out_head;
> + outcnt++;
> +
> + if (spi_req->tx_buf) {
> + sg_init_one(&sg_out_payload, spi_req->tx_buf, xfer->len);
> + sgs[outcnt] = &sg_out_payload;
> + outcnt++;
> + }
> +
> + if (spi_req->rx_buf) {
> + sg_init_one(&sg_in_payload, spi_req->rx_buf, xfer->len);
> + sgs[outcnt] = &sg_in_payload;
> + incnt++;
> + }
> +
> + sg_init_one(&sg_in_result, &spi_req->result,
> + sizeof(struct spi_transfer_result));
> + sgs[outcnt + incnt] = &sg_in_result;
> + incnt++;
> +
> + ret = virtqueue_add_sgs(priv->vq, sgs, outcnt, incnt, spi_req,
> + GFP_KERNEL);
> + if (ret)
> + goto msg_done;
> +
> + /* Simple implementation: There can be only one transfer in flight */
> + virtqueue_kick(priv->vq);
> +
> + wait_for_completion(&spi_req->completion);
> +
> + /* Read result from message and translate return code */
> + switch (spi_req->result.result) {
> + case VIRTIO_SPI_TRANS_OK:
> + break;
> + case VIRTIO_SPI_PARAM_ERR:
> + ret = -EINVAL;
> + break;
> + case VIRTIO_SPI_TRANS_ERR:
> + ret = -EIO;
> + break;
> + default:
> + ret = -EIO;
> + break;
> + }
> +
> +msg_done:
> + if (ret)
> + ctrl->cur_msg->status = ret;
> +
> + return ret;
> +}
...
> +static int virtio_spi_find_vqs(struct virtio_spi_priv *priv)
> +{
> + struct virtqueue *vq;
> +
> + vq = virtio_find_single_vq(priv->vdev, virtio_spi_msg_done, "spi-rq");
> + if (IS_ERR(vq))
> + return PTR_ERR(vq);
See above.
> + priv->vq = vq;
> + return 0;
> +}
...
> +static int virtio_spi_probe(struct virtio_device *vdev)
> +{
> + struct virtio_spi_priv *priv;
> + struct spi_controller *ctrl;
> + int ret;
> + u32 bus_num;
> +
> + ctrl = devm_spi_alloc_host(&vdev->dev, sizeof(*priv));
> + if (!ctrl)
> + return -ENOMEM;
> +
> + priv = spi_controller_get_devdata(ctrl);
> + priv->vdev = vdev;
> + vdev->priv = priv;
> + device_set_node(&ctrl->dev, dev_fwnode(&vdev->dev));
> + dev_set_drvdata(&vdev->dev, ctrl);
> + ret = device_property_read_u32(&vdev->dev, "spi,bus-num", &bus_num);
+ device.h
+ property.h
> + if (ret || bus_num > S16_MAX)
+ limits.h
> + ctrl->bus_num = -1;
> + else
> + ctrl->bus_num = bus_num;
But why do we need this property at all? And where is it documented in the
device tree bindings?
> + virtio_spi_read_config(vdev);
> +
> + ctrl->transfer_one = virtio_spi_transfer_one;
> +
> + ret = virtio_spi_find_vqs(priv);
> + if (ret)
> + return dev_err_probe(&vdev->dev, ret, "Cannot setup virtqueues\n");
> +
> + /* Register cleanup for virtqueues using devm */
> + ret = devm_add_action_or_reset(&vdev->dev, virtio_spi_del_vq, vdev);
> + if (ret)
> + return dev_err_probe(&vdev->dev, ret, "Cannot register virtqueue cleanup\n");
> +
> + /* Use devm version to register controller */
> + ret = devm_spi_register_controller(&vdev->dev, ctrl);
> + if (ret)
> + return dev_err_probe(&vdev->dev, ret, "Cannot register controller\n");
> +
> + return 0;
> +}
...
> +static struct virtio_device_id virtio_spi_id_table[] = {
The type is or should be defined in mod_devicetable.h.
> + { VIRTIO_ID_SPI, VIRTIO_DEV_ANY_ID },
> + {}
> +};
...
> +static const struct dev_pm_ops virtio_spi_pm_ops = {
The type is defined in pm.h.
> + .freeze = pm_sleep_ptr(virtio_spi_freeze),
> + .restore = pm_sleep_ptr(virtio_spi_restore),
> +};
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v9 3/3] SPI: Add virtio SPI driver
2025-09-01 12:07 ` Andy Shevchenko
@ 2025-09-02 15:54 ` Harald Mommer
2025-09-02 16:28 ` Mark Brown
2025-09-03 9:04 ` Haixu Cui
1 sibling, 1 reply; 9+ messages in thread
From: Harald Mommer @ 2025-09-02 15:54 UTC (permalink / raw)
To: Andy Shevchenko, Haixu Cui
Cc: quic_msavaliy, broonie, virtio-dev, viresh.kumar, linux-spi,
linux-kernel, hdanton, qiang4.zhang, alex.bennee, quic_ztu,
virtualization
Hello,
On 9/1/25 14:07, Andy Shevchenko wrote:
> On Thu, Aug 28, 2025 at 05:34:51PM +0800, Haixu Cui wrote:
>> This is the virtio SPI Linux kernel driver.
>
> ...
>
>> +#include <linux/completion.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/spi/spi.h>
>> +#include <linux/stddef.h>
>
> A lot of headers are still missing. See below.
The driver should compile with the set of included headers.
>
> ...
>
>
>> +struct virtio_spi_priv {
>> + /* The virtio device we're associated with */
>> + struct virtio_device *vdev;
>> + /* Pointer to the virtqueue */
>> + struct virtqueue *vq;
>> + /* Copy of config space mode_func_supported */
>> + u32 mode_func_supported;
>
> uXX (in particular u32) is defined in types.h.
>
>> + /* Copy of config space max_freq_hz */
>> + u32 max_freq_hz;
>> +};
u32 is for sure defined in types.h. But looking into another device (gpio-virtio.c) I see that there is also u8 and u32 used and I don't see that there types.h is included directly in the C file. It must be included indirectly as the driver compiles. Same happens here.
I'm not aware of a requirement in the style guide for the Linux kernel that all used headers have to be included directly in the C file but this does not mean that such a requirement does not exist. Can anyone point my nose on it?
Regards
Harald Mommer
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v9 3/3] SPI: Add virtio SPI driver
2025-09-02 15:54 ` Harald Mommer
@ 2025-09-02 16:28 ` Mark Brown
0 siblings, 0 replies; 9+ messages in thread
From: Mark Brown @ 2025-09-02 16:28 UTC (permalink / raw)
To: Harald Mommer
Cc: Andy Shevchenko, Haixu Cui, quic_msavaliy, virtio-dev,
viresh.kumar, linux-spi, linux-kernel, hdanton, qiang4.zhang,
alex.bennee, quic_ztu, virtualization
[-- Attachment #1: Type: text/plain, Size: 703 bytes --]
On Tue, Sep 02, 2025 at 05:54:39PM +0200, Harald Mommer wrote:
> I'm not aware of a requirement in the style guide for the Linux kernel
> that all used headers have to be included directly in the C file but
> this does not mean that such a requirement does not exist. Can anyone
> point my nose on it?
It's a general best practice for maintainability, I'm not aware of
anywhere where it's specifically written down but it's something people
commonly try to do. That said so long as things compile I'm not going
to block anything on this sort of extremely pedantic review, especially
at this late stage in review. I would have a lot more time for patches
for such issues, this is rather stop energy.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v9 3/3] SPI: Add virtio SPI driver
2025-09-01 12:07 ` Andy Shevchenko
2025-09-02 15:54 ` Harald Mommer
@ 2025-09-03 9:04 ` Haixu Cui
2025-09-03 10:27 ` Andy Shevchenko
1 sibling, 1 reply; 9+ messages in thread
From: Haixu Cui @ 2025-09-03 9:04 UTC (permalink / raw)
To: Andy Shevchenko
Cc: harald.mommer, quic_msavaliy, broonie, virtio-dev, viresh.kumar,
linux-spi, linux-kernel, hdanton, qiang4.zhang, alex.bennee,
quic_ztu, virtualization
Hi Andy,
On 9/1/2025 8:07 PM, Andy Shevchenko wrote:
> On Thu, Aug 28, 2025 at 05:34:51PM +0800, Haixu Cui wrote:
>> This is the virtio SPI Linux kernel driver.
>
> ...
>
>> +#include <linux/completion.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/spi/spi.h>
>> +#include <linux/stddef.h>
>
> A lot of headers are still missing. See below.
>
> ...
>
This driver compiles successfully, and I believe all required
definitions are resolved through indirect inclusion. For example, since
I included virtio.h, there is no need to explicitly include device.h,
scatterlist.h or types.h.
I avoided redundant #includes to keep the code clean and minimal.
If there are any essential headers I’ve overlooked, please feel free to
highlight them—I’ll gladly include them in the next revision.
>
>> +static int virtio_spi_transfer_one(struct spi_controller *ctrl,
>> + struct spi_device *spi,
>> + struct spi_transfer *xfer)
>> +{
>> + struct virtio_spi_priv *priv = spi_controller_get_devdata(ctrl);
>
>> + struct virtio_spi_req *spi_req __free(kfree);
>
> This is incorrect template. It's one of the exceptions when we mix code and
> definitions...
>> + spi_req = kzalloc(sizeof(*spi_req), GFP_KERNEL);
>
> ...so this should be
>
> struct virtio_spi_req *spi_req __free(kfree) =
> kzalloc(sizeof(*spi_req), GFP_KERNEL);
>
> (or on one line if you are okay with a 100 limit).
>
I plan to update the code as follows:
struct virtio_spi_req *spi_req __free(kfree) = NULL;
spi_req = kzalloc(sizeof(*spi_req), GFP_KERNEL);
if(!spi_req)
return -ENOMEM;
This follows the pattern used in
virtio_net(https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/net/virtio_net.c?h=v6.17-rc4#n3746)
I'd like to check if this style is acceptable here, thanks.
>
>> + if (ret || bus_num > S16_MAX)
>
> + limits.h
>
>> + ctrl->bus_num = -1;
>> + else
>> + ctrl->bus_num = bus_num;
>
> But why do we need this property at all? And where is it documented in the
> device tree bindings?
I’ve reviewed other SPI drivers in the kernel, and it seems that bus_num
is not a required property in most cases. I’ll remove the related code
in the next revision.
Thanks again.
BR
Haixu Cui
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v9 3/3] SPI: Add virtio SPI driver
2025-09-03 9:04 ` Haixu Cui
@ 2025-09-03 10:27 ` Andy Shevchenko
0 siblings, 0 replies; 9+ messages in thread
From: Andy Shevchenko @ 2025-09-03 10:27 UTC (permalink / raw)
To: Haixu Cui
Cc: harald.mommer, quic_msavaliy, broonie, virtio-dev, viresh.kumar,
linux-spi, linux-kernel, hdanton, qiang4.zhang, alex.bennee,
quic_ztu, virtualization
On Wed, Sep 03, 2025 at 05:04:46PM +0800, Haixu Cui wrote:
> On 9/1/2025 8:07 PM, Andy Shevchenko wrote:
> > On Thu, Aug 28, 2025 at 05:34:51PM +0800, Haixu Cui wrote:
> > > This is the virtio SPI Linux kernel driver.
...
> > > +#include <linux/completion.h>
> > > +#include <linux/interrupt.h>
> > > +#include <linux/io.h>
> > > +#include <linux/module.h>
> > > +#include <linux/spi/spi.h>
> > > +#include <linux/stddef.h>
> >
> > A lot of headers are still missing. See below.
>
> This driver compiles successfully, and I believe all required definitions
> are resolved through indirect inclusion. For example, since I included
> virtio.h, there is no need to explicitly include device.h, scatterlist.h or
> types.h.
>
> I avoided redundant #includes to keep the code clean and minimal.
>
> If there are any essential headers I’ve overlooked, please feel free to
> highlight them—I’ll gladly include them in the next revision.
The rationale is described on https://include-what-you-use.org/.
...
> I plan to update the code as follows:
>
> struct virtio_spi_req *spi_req __free(kfree) = NULL;
> spi_req = kzalloc(sizeof(*spi_req), GFP_KERNEL);
> if(!spi_req)
> return -ENOMEM;
>
> This follows the pattern used in
> virtio_net(https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/net/virtio_net.c?h=v6.17-rc4#n3746)
>
> I'd like to check if this style is acceptable here, thanks.
The style is fine. The potential issue (not now and probably never) is that the
scope of the variable in this case is different which might lead to unexpected
side-effects. That said, You can go with it.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2025-09-03 10:28 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-28 9:34 [PATCH v9 0/3] Virtio SPI Linux driver Haixu Cui
2025-08-28 9:34 ` [PATCH v9 1/3] virtio: Add ID for virtio SPI Haixu Cui
2025-08-28 9:34 ` [PATCH v9 2/3] virtio-spi: Add virtio-spi.h Haixu Cui
2025-08-28 9:34 ` [PATCH v9 3/3] SPI: Add virtio SPI driver Haixu Cui
2025-09-01 12:07 ` Andy Shevchenko
2025-09-02 15:54 ` Harald Mommer
2025-09-02 16:28 ` Mark Brown
2025-09-03 9:04 ` Haixu Cui
2025-09-03 10:27 ` Andy Shevchenko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).