* [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev
@ 2026-09-21 23:05 Sam Agazaryan
2026-09-21 23:05 ` [PATCH v5 1/5] i3c: master: export i3c_masterdev_type Sam Agazaryan
` (5 more replies)
0 siblings, 6 replies; 9+ messages in thread
From: Sam Agazaryan @ 2026-09-21 23:05 UTC (permalink / raw)
To: linux-i3c, Alexandre Belloni, Frank Li
Cc: Greg Kroah-Hartman, Wolfram Sang, Arnd Bergmann, Adrian Hunter,
Meagan Lloyd, Vitor Soares, Oleksandr Shulzhenko,
Boris Brezillon, linux-kernel, Sam Agazaryan
This patch series introduces the i3cdev module, exposing unbound I3C
target devices to userspace via character device nodes (/dev/bus/i3c/*),
along with the i3ctransfer userspace utility in tools/i3c/.
Userspace access to I3C targets is needed for devices that do not have a
kernel driver bound to them, such as targets in ROM/bootloader recovery
mode (e.g., OCP Secure Firmware Recovery v1.1 and Caliptra Silicon Root of
Trust recovery flows), as well as hardware bring-up and diagnostics.
When a kernel driver later binds to an I3C device (for example via
dynamic module loading), i3cdev automatically detaches via the
BUS_NOTIFY_BIND_DRIVER notifier so kernel drivers always take
precedence.
Testing status:
This v5 series has been compile-tested across all modified I3C controller
drivers with W=1. Posting v5 now so collaborators and controller owners
can test the unified UAPI and actual_len updates on their respective
hardware and provide Tested-by tags while we complete final hardware
verification of the v5 updates on our platform.
Changes in v5:
- Updated Patch 2/5 ("i3c: master: add i3c_for_each_dev helper"):
- Dropped mutex_lock(&i3c_core_lock) around bus_for_each_dev() to avoid
lock inversion and deadlock with external callbacks (sashiko-bot).
- Added Patch 3/5 ("i3c: use actual_len for read transfers"):
- Clarified @actual_len kerneldoc in <linux/i3c/device.h>
(Adrian Hunter).
- Incorporated Meagan Lloyd's controller updates (adi, dw, cdns,
mipi-i3c-hci, renesas) so all I3C controller drivers populate
xfer->actual_len on reads without mutating xfer->len
(Adrian Hunter, Meagan Lloyd).
- Updated mctp-i3c to read xfer.actual_len instead of xfer.len
(Adrian Hunter).
- Updated Patch 4/5 ("i3c: add i3cdev module to expose i3c dev in /dev"):
- Unified the UAPI around struct i3c_ioc_xfer and I3C_IOC_XFER(N) in
<uapi/linux/i3c/i3cdev.h>, supporting both SDR and HDR modes via a
mode field and union { __u8 rnw; __u8 cmd; } (Frank Li).
- Added __u16 actual_len to struct i3c_ioc_xfer and updated
i3cdev_do_xfer() to copy actual_len bytes to userspace and write
actual_len back to userspace via put_user() on read transfers
(Adrian Hunter, Frank Li).
- Tracked active i3cdev instances in a private list (i3cdev_list)
instead of using i3cdev_set_drvdata()/i3cdev_get_drvdata() so i3cdev
never clobbers client drivers' dev->driver_data (Meagan Lloyd).
- Validated direction field and verified that reserved padding pad[2] is
zeroed (Greg KH).
- Clamped read()/write() byte count to type_max(xfers.len) (u16) to
prevent silent integer truncation and unbounded kernel allocations
(sashiko-bot).
- Used ida_alloc_max() capped to MINORMASK to prevent minor number
overflow (sashiko-bot).
- Added i3cdev_attach_lock mutex and idempotency check in
i3cdev_attach()/i3cdev_detach() to serialize concurrent attach/detach
calls (sashiko-bot).
- Updated bus notifier to handle BUS_NOTIFY_DRIVER_NOT_BOUND and return
NOTIFY_OK instead of raw errno values (sashiko-bot).
- Added Patch 5/5 ("tools: i3c: add i3ctransfer utility"):
- Added tools/i3c/i3ctransfer.c (adapted from Vitor Soares's
i3c-tools) updated for struct i3c_ioc_xfer, supporting SDR and HDR
(-m, -c) modes and reporting actual_len bytes on reads
(Wolfram Sang).
- Note to Vitor Soares: Since your original i3ctransfer commit in
i3c-tools did not include a Signed-off-by tag, could you please
reply with your Signed-off-by / Acked-by for Patch 5/5?
Changes in v4:
- Dropped dev_open count tracking and used cdev_device_add/del with
mutex locking to prevent use-after-free on detach
(Greg KH, Wolfram Sang).
- Fixed 32/64-bit UAPI alignment and added compat_ptr_ioctl.
- Switched to dynamic minor allocation via IDA.
Sam Agazaryan (2):
i3c: use actual_len for read transfers
tools: i3c: add i3ctransfer utility
Vitor Soares (3):
i3c: master: export i3c_masterdev_type
i3c: master: add i3c_for_each_dev helper
i3c: add i3cdev module to expose i3c dev in /dev
MAINTAINERS | 2 +
drivers/i3c/Kconfig | 11 +
drivers/i3c/Makefile | 1 +
drivers/i3c/i3cdev.c | 491 +++++++++++++++++++++++++
drivers/i3c/internals.h | 4 +
drivers/i3c/master.c | 9 +-
drivers/i3c/master/adi-i3c-master.c | 5 +-
drivers/i3c/master/dw-i3c-master.c | 2 +-
drivers/i3c/master/i3c-master-cdns.c | 5 +-
drivers/i3c/master/mipi-i3c-hci/core.c | 2 +-
drivers/i3c/master/renesas-i3c.c | 3 +
drivers/net/mctp/mctp-i3c.c | 10 +-
include/linux/i3c/device.h | 2 +-
include/uapi/linux/i3c/i3cdev.h | 57 +++
tools/Makefile | 13 +-
tools/i3c/Build | 1 +
tools/i3c/Makefile | 58 +++
tools/i3c/i3ctransfer.c | 307 ++++++++++++++++
18 files changed, 966 insertions(+), 17 deletions(-)
create mode 100644 drivers/i3c/i3cdev.c
create mode 100644 include/uapi/linux/i3c/i3cdev.h
create mode 100644 tools/i3c/Build
create mode 100644 tools/i3c/Makefile
create mode 100644 tools/i3c/i3ctransfer.c
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 1/5] i3c: master: export i3c_masterdev_type
2026-09-21 23:05 [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev Sam Agazaryan
@ 2026-09-21 23:05 ` Sam Agazaryan
2026-09-21 23:06 ` [PATCH v5 2/5] i3c: master: add i3c_for_each_dev helper Sam Agazaryan
` (4 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Sam Agazaryan @ 2026-09-21 23:05 UTC (permalink / raw)
To: linux-i3c, Alexandre Belloni, Frank Li
Cc: Greg Kroah-Hartman, Wolfram Sang, Arnd Bergmann, Adrian Hunter,
Meagan Lloyd, Vitor Soares, Oleksandr Shulzhenko,
Boris Brezillon, linux-kernel, Sam Agazaryan
From: Vitor Soares <vitor.soares@toradex.com>
Export i3c_masterdev_type so i3cdev module can verify if an i3c device
is a master.
Signed-off-by: Vitor Soares <vitor.soares@toradex.com>
Signed-off-by: Sam Agazaryan <samagazaryan@google.com>
---
drivers/i3c/internals.h | 2 ++
drivers/i3c/master.c | 3 ++-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h
index 86a36b951e0d..cbc00c862104 100644
--- a/drivers/i3c/internals.h
+++ b/drivers/i3c/internals.h
@@ -11,6 +11,8 @@
#include <linux/i3c/master.h>
#include <linux/io.h>
+extern const struct device_type i3c_masterdev_type;
+
int __must_check i3c_bus_rpm_get(struct i3c_bus *bus);
void i3c_bus_rpm_put(struct i3c_bus *bus);
bool i3c_bus_rpm_ibi_allowed(struct i3c_bus *bus);
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index afcd7a21a3e6..e8a9f8c3041e 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -864,9 +864,10 @@ static void i3c_masterdev_release(struct device *dev)
i3c_master_free_i3c_dev(master->this);
}
-static const struct device_type i3c_masterdev_type = {
+const struct device_type i3c_masterdev_type = {
.groups = i3c_masterdev_groups,
};
+EXPORT_SYMBOL_GPL(i3c_masterdev_type);
static void i3c_master_shutdown(struct i3c_master_controller *master)
{
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 2/5] i3c: master: add i3c_for_each_dev helper
2026-09-21 23:05 [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev Sam Agazaryan
2026-09-21 23:05 ` [PATCH v5 1/5] i3c: master: export i3c_masterdev_type Sam Agazaryan
@ 2026-09-21 23:06 ` Sam Agazaryan
2026-09-21 23:06 ` [PATCH v5 3/5] i3c: use actual_len for read transfers Sam Agazaryan
` (3 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Sam Agazaryan @ 2026-09-21 23:06 UTC (permalink / raw)
To: linux-i3c, Alexandre Belloni, Frank Li
Cc: Greg Kroah-Hartman, Wolfram Sang, Arnd Bergmann, Adrian Hunter,
Meagan Lloyd, Vitor Soares, Oleksandr Shulzhenko,
Boris Brezillon, linux-kernel, Sam Agazaryan
From: Vitor Soares <vitor.soares@toradex.com>
Introduce i3c_for_each_dev(), an i3c device iterator for use by i3cdev.
Signed-off-by: Vitor Soares <vitor.soares@toradex.com>
Signed-off-by: Sam Agazaryan <samagazaryan@google.com>
---
drivers/i3c/internals.h | 2 ++
drivers/i3c/master.c | 6 ++++++
2 files changed, 8 insertions(+)
diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h
index cbc00c862104..3c53a5108b81 100644
--- a/drivers/i3c/internals.h
+++ b/drivers/i3c/internals.h
@@ -79,4 +79,6 @@ static inline struct i3c_master_controller *i3c_bus_to_i3c_master(struct i3c_bus
return container_of(i3cbus, struct i3c_master_controller, bus);
}
+int i3c_for_each_dev(void *data, int (*fn)(struct device *, void *));
+
#endif /* I3C_INTERNAL_H */
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index e8a9f8c3041e..07540ea73aec 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -4011,6 +4011,12 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
}
EXPORT_SYMBOL_GPL(i3c_dev_free_ibi_locked);
+int i3c_for_each_dev(void *data, int (*fn)(struct device *, void *))
+{
+ return bus_for_each_dev(&i3c_bus_type, NULL, data, fn);
+}
+EXPORT_SYMBOL_GPL(i3c_for_each_dev);
+
static int __init i3c_init(void)
{
int res;
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 3/5] i3c: use actual_len for read transfers
2026-09-21 23:05 [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev Sam Agazaryan
2026-09-21 23:05 ` [PATCH v5 1/5] i3c: master: export i3c_masterdev_type Sam Agazaryan
2026-09-21 23:06 ` [PATCH v5 2/5] i3c: master: add i3c_for_each_dev helper Sam Agazaryan
@ 2026-09-21 23:06 ` Sam Agazaryan
2026-09-24 11:53 ` Adrian Hunter
2026-09-21 23:06 ` [PATCH v5 4/5] i3c: add i3cdev module to expose i3c dev in /dev Sam Agazaryan
` (2 subsequent siblings)
5 siblings, 1 reply; 9+ messages in thread
From: Sam Agazaryan @ 2026-09-21 23:06 UTC (permalink / raw)
To: linux-i3c, Alexandre Belloni, Frank Li
Cc: Greg Kroah-Hartman, Wolfram Sang, Arnd Bergmann, Adrian Hunter,
Meagan Lloyd, Vitor Soares, Oleksandr Shulzhenko,
Boris Brezillon, linux-kernel, Sam Agazaryan
Currently only the amd and svc I3C controller drivers populate
i3c_xfer.actual_len on reads, while dw-i3c-master and mipi-i3c-hci
overwrite i3c_xfer.len with the received byte count and leave actual_len
as 0. Because of this inconsistency, mctp-i3c reads xfer.len instead of
xfer.actual_len, making it fail on controllers that do not mutate len.
Unify read length reporting across the subsystem:
- Clarify in <linux/i3c/device.h> that actual_len reflects the actual
number of bytes transferred on reads.
- Update adi, cdns, dw, mipi-i3c-hci, and renesas controller drivers to
populate actual_len on read transfers without mutating len.
- Update mctp-i3c to read xfer.actual_len instead of xfer.len.
Co-developed-by: Meagan Lloyd <meaganlloyd@linux.microsoft.com>
Signed-off-by: Meagan Lloyd <meaganlloyd@linux.microsoft.com>
Signed-off-by: Sam Agazaryan <samagazaryan@google.com>
---
drivers/i3c/master/adi-i3c-master.c | 5 ++++-
drivers/i3c/master/dw-i3c-master.c | 2 +-
drivers/i3c/master/i3c-master-cdns.c | 5 ++++-
drivers/i3c/master/mipi-i3c-hci/core.c | 2 +-
drivers/i3c/master/renesas-i3c.c | 3 +++
drivers/net/mctp/mctp-i3c.c | 10 +++++-----
include/linux/i3c/device.h | 2 +-
7 files changed, 19 insertions(+), 10 deletions(-)
diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c
index b35386260350..d033bfd4ecc2 100644
--- a/drivers/i3c/master/adi-i3c-master.c
+++ b/drivers/i3c/master/adi-i3c-master.c
@@ -412,8 +412,11 @@ static int adi_i3c_master_i3c_xfers(struct i3c_dev_desc *dev,
ret = xfer->ret;
- for (i = 0; i < nxfers; i++)
+ for (i = 0; i < nxfers; i++) {
xfers[i].err = adi_i3c_cmd_get_err(&xfer->cmds[i]);
+ if (xfers[i].rnw)
+ xfers[i].actual_len = xfer->cmds[i].rx_len;
+ }
return ret;
}
diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
index 4563d8761ba0..28e8eb293747 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -1029,7 +1029,7 @@ static int dw_i3c_master_i3c_xfers(struct i3c_dev_desc *dev,
struct dw_i3c_cmd *cmd = &xfer->cmds[i];
if (i3c_xfers[i].rnw)
- i3c_xfers[i].len = cmd->rx_len;
+ i3c_xfers[i].actual_len = cmd->rx_len;
}
ret = xfer->ret;
diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c
index 2d98c1ce9b12..331062c10b31 100644
--- a/drivers/i3c/master/i3c-master-cdns.c
+++ b/drivers/i3c/master/i3c-master-cdns.c
@@ -795,8 +795,11 @@ static int cdns_i3c_master_i3c_xfers(struct i3c_dev_desc *dev,
ret = cdns_xfer->ret;
- for (i = 0; i < nxfers; i++)
+ for (i = 0; i < nxfers; i++) {
xfers[i].err = cdns_i3c_cmd_get_err(&cdns_xfer->cmds[i]);
+ if (xfers[i].rnw)
+ xfers[i].actual_len = cdns_xfer->cmds[i].rx_len;
+ }
cdns_i3c_master_free_xfer(cdns_xfer);
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index dadf049bd4b5..f74c02fab9e3 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -509,7 +509,7 @@ static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev,
goto out;
for (i = 0; i < nxfers; i++) {
if (i3c_xfers[i].rnw)
- i3c_xfers[i].len = RESP_DATA_LENGTH(xfer[i].response);
+ i3c_xfers[i].actual_len = RESP_DATA_LENGTH(xfer[i].response);
if (RESP_STATUS(xfer[i].response) != RESP_SUCCESS) {
ret = -EIO;
goto out;
diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c
index 28c0927a0179..a3dcb4cd9b0e 100644
--- a/drivers/i3c/master/renesas-i3c.c
+++ b/drivers/i3c/master/renesas-i3c.c
@@ -918,6 +918,9 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_
time_left = renesas_i3c_wait_xfer(i3c, xfer);
if (!time_left)
xfer_failed = true;
+
+ if (i3c_xfers[i].rnw)
+ i3c_xfers[i].actual_len = cmd->rx_count;
}
if (xfer_failed)
diff --git a/drivers/net/mctp/mctp-i3c.c b/drivers/net/mctp/mctp-i3c.c
index 6d2bbae7477b..5f94c11a1712 100644
--- a/drivers/net/mctp/mctp-i3c.c
+++ b/drivers/net/mctp/mctp-i3c.c
@@ -131,12 +131,12 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi)
if (rc < 0)
goto err;
- if (WARN_ON_ONCE(xfer.len > mi->mrl)) {
+ if (WARN_ON_ONCE(xfer.actual_len > mi->mrl)) {
/* Bad i3c bus driver */
rc = -EIO;
goto err;
}
- if (xfer.len < MCTP_I3C_MINLEN) {
+ if (xfer.actual_len < MCTP_I3C_MINLEN) {
stats->rx_length_errors++;
rc = -EIO;
goto err;
@@ -145,15 +145,15 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi)
/* check PEC, including address byte */
addr = mi->addr << 1 | 1;
pec = i2c_smbus_pec(0, &addr, 1);
- pec = i2c_smbus_pec(pec, xfer.data.in, xfer.len - 1);
- if (pec != ((u8 *)xfer.data.in)[xfer.len - 1]) {
+ pec = i2c_smbus_pec(pec, xfer.data.in, xfer.actual_len - 1);
+ if (pec != ((u8 *)xfer.data.in)[xfer.actual_len - 1]) {
stats->rx_crc_errors++;
rc = -EINVAL;
goto err;
}
/* Remove PEC */
- skb_trim(skb, xfer.len - 1);
+ skb_trim(skb, xfer.actual_len - 1);
cb = __mctp_cb(skb);
cb->halen = PID_SIZE;
diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h
index 971d53349b6f..f868a7bf2bc9 100644
--- a/include/linux/i3c/device.h
+++ b/include/linux/i3c/device.h
@@ -59,7 +59,7 @@ enum i3c_xfer_mode {
* @rnw: encodes the transfer direction. true for a read, false for a write
* @cmd: Read/Write command in HDR mode, read: 0x80 - 0xff, write: 0x00 - 0x7f
* @len: transfer length in bytes of the transfer
- * @actual_len: actual length in bytes are transferred by the controller
+ * @actual_len: actual length in bytes transferred by the controller on read
* @data: input/output buffer
* @data.in: input buffer. Must point to a DMA-able buffer
* @data.out: output buffer. Must point to a DMA-able buffer
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 4/5] i3c: add i3cdev module to expose i3c dev in /dev
2026-09-21 23:05 [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev Sam Agazaryan
` (2 preceding siblings ...)
2026-09-21 23:06 ` [PATCH v5 3/5] i3c: use actual_len for read transfers Sam Agazaryan
@ 2026-09-21 23:06 ` Sam Agazaryan
2026-09-21 23:06 ` [PATCH v5 5/5] tools: i3c: add i3ctransfer utility Sam Agazaryan
2026-09-25 9:08 ` [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev Andy Shevchenko
5 siblings, 0 replies; 9+ messages in thread
From: Sam Agazaryan @ 2026-09-21 23:06 UTC (permalink / raw)
To: linux-i3c, Alexandre Belloni, Frank Li
Cc: Greg Kroah-Hartman, Wolfram Sang, Arnd Bergmann, Adrian Hunter,
Meagan Lloyd, Vitor Soares, Oleksandr Shulzhenko,
Boris Brezillon, linux-kernel, Sam Agazaryan
From: Vitor Soares <vitor.soares@toradex.com>
Add userspace character device support for I3C transfers via /dev.
The module allows userspace programs to interact directly with I3C
targets that do not have a kernel driver bound to them, such as devices
in ROM/bootloader recovery mode (e.g. OCP Secure Firmware Recovery v1.1
and Caliptra Silicon Root of Trust recovery flows).
Features:
- Dynamically exposes /dev/bus/i3c/<device> character devices for I3C
devices when unbound from kernel drivers.
- Dynamically allocates character device minor numbers using the IDA
allocator.
- Implements SDR and HDR transfers via I3C_IOC_XFER ioctl with 64-bit
aligned UAPI data structures and actual_len read reporting.
- Supports compat_ptr_ioctl for 32-bit userspace on 64-bit kernels.
- Uses cdev_device_add/cdev_device_del with device refcounting to ensure
safe lifecycle management and prevent use-after-free on driver detach.
Signed-off-by: Vitor Soares <vitor.soares@toradex.com>
Co-developed-by: Oleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>
Signed-off-by: Oleksandr Shulzhenko <oleksandr.shulzhenko.viktorovych@intel.com>
Co-developed-by: Sam Agazaryan <samagazaryan@google.com>
Signed-off-by: Sam Agazaryan <samagazaryan@google.com>
---
MAINTAINERS | 1 +
drivers/i3c/Kconfig | 11 +
drivers/i3c/Makefile | 1 +
drivers/i3c/i3cdev.c | 491 ++++++++++++++++++++++++++++++++
include/uapi/linux/i3c/i3cdev.h | 57 ++++
5 files changed, 561 insertions(+)
create mode 100644 drivers/i3c/i3cdev.c
create mode 100644 include/uapi/linux/i3c/i3cdev.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 81a9a02c919d..30a5cb12c4f0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12364,6 +12364,7 @@ F: Documentation/driver-api/i3c
F: drivers/i3c/
F: include/dt-bindings/i3c/
F: include/linux/i3c/
+F: include/uapi/linux/i3c/
IBM Operation Panel Input Driver
M: Eddie James <eajames@linux.ibm.com>
diff --git a/drivers/i3c/Kconfig b/drivers/i3c/Kconfig
index 626c54b386d5..166875837ec6 100644
--- a/drivers/i3c/Kconfig
+++ b/drivers/i3c/Kconfig
@@ -20,6 +20,17 @@ menuconfig I3C
will be called i3c.
if I3C
+
+config I3CDEV
+ tristate "I3C device interface"
+ help
+ Say Y here to use i3c-* device files, usually found in the /dev
+ directory on your system. They make it possible to have user-space
+ programs use the I3C devices.
+
+ This support is also available as a module. If so, the module
+ will be called i3cdev.
+
source "drivers/i3c/master/Kconfig"
endif # I3C
diff --git a/drivers/i3c/Makefile b/drivers/i3c/Makefile
index 11982efbc6d9..606d422841b2 100644
--- a/drivers/i3c/Makefile
+++ b/drivers/i3c/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
i3c-y := device.o master.o
obj-$(CONFIG_I3C) += i3c.o
+obj-$(CONFIG_I3CDEV) += i3cdev.o
obj-$(CONFIG_I3C) += master/
diff --git a/drivers/i3c/i3cdev.c b/drivers/i3c/i3cdev.c
new file mode 100644
index 000000000000..309ce8181209
--- /dev/null
+++ b/drivers/i3c/i3cdev.c
@@ -0,0 +1,491 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020 Synopsys, Inc. and/or its affiliates.
+ * Copyright (c) 2026 Google LLC
+ *
+ * Author: Vitor Soares <soares@synopsys.com>
+ * Author: Sam Agazaryan <samagazaryan@google.com>
+ */
+
+#include <linux/cdev.h>
+#include <linux/cleanup.h>
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/overflow.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#include <linux/i3c/i3cdev.h>
+
+#include "internals.h"
+
+struct i3cdev_data {
+ struct list_head list;
+ struct i3c_device *i3c;
+ struct device dev;
+ struct mutex xfer_lock; /* prevent detach while transferring */
+ struct cdev cdev;
+ int id;
+};
+
+static DEFINE_IDA(i3cdev_ida);
+static LIST_HEAD(i3cdev_list);
+static DEFINE_MUTEX(i3cdev_attach_lock);
+static dev_t i3cdev_number;
+#define I3C_MINORS (MINORMASK + 1)
+
+static void i3cdev_dev_release(struct device *dev)
+{
+ struct i3cdev_data *i3cdev = container_of(dev, struct i3cdev_data, dev);
+
+ ida_free(&i3cdev_ida, i3cdev->id);
+ kfree(i3cdev);
+}
+
+static struct i3cdev_data *i3cdev_get_by_i3c(struct i3c_device *i3c)
+{
+ struct i3cdev_data *i3cdev;
+
+ list_for_each_entry(i3cdev, &i3cdev_list, list) {
+ if (i3cdev->i3c == i3c)
+ return i3cdev;
+ }
+
+ return NULL;
+}
+
+static struct i3cdev_data *get_free_i3cdev(struct i3c_device *i3c)
+{
+ struct i3cdev_data *i3cdev;
+ int id;
+
+ id = ida_alloc_max(&i3cdev_ida, MINORMASK, GFP_KERNEL);
+ if (id < 0) {
+ pr_err("i3cdev: no minor number available!\n");
+ return ERR_PTR(id);
+ }
+
+ i3cdev = kzalloc_obj(*i3cdev, GFP_KERNEL);
+ if (!i3cdev) {
+ ida_free(&i3cdev_ida, id);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ i3cdev->i3c = i3c;
+ i3cdev->id = id;
+ list_add_tail(&i3cdev->list, &i3cdev_list);
+
+ return i3cdev;
+}
+
+static ssize_t
+i3cdev_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
+{
+ char *tmp __free(kfree) = NULL;
+ struct i3cdev_data *i3cdev = file->private_data;
+ struct i3c_xfer xfers = {
+ .rnw = true,
+ };
+ struct i3c_device *i3c;
+ int ret;
+
+ count = min_t(size_t, count, type_max(xfers.len));
+ xfers.len = count;
+
+ tmp = kzalloc(count, GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ guard(mutex)(&i3cdev->xfer_lock);
+ i3c = i3cdev->i3c;
+ if (!i3c || i3c->dev.driver)
+ return -ENODEV;
+
+ xfers.data.in = tmp;
+
+ dev_dbg(&i3c->dev, "Reading %zu bytes.\n", count);
+
+ ret = i3c_device_do_xfers(i3c, &xfers, 1, I3C_SDR);
+ if (ret)
+ return ret;
+
+ if (copy_to_user(buf, tmp, xfers.actual_len))
+ return -EFAULT;
+
+ return xfers.actual_len;
+}
+
+static ssize_t
+i3cdev_write(struct file *file, const char __user *buf, size_t count,
+ loff_t *f_pos)
+{
+ void *tmp __free(kfree) = NULL;
+ struct i3cdev_data *i3cdev = file->private_data;
+ struct i3c_xfer xfers = {
+ .rnw = false,
+ };
+ struct i3c_device *i3c;
+ int ret;
+
+ count = min_t(size_t, count, type_max(xfers.len));
+ xfers.len = count;
+
+ tmp = memdup_user(buf, count);
+ if (IS_ERR(tmp))
+ return PTR_ERR(tmp);
+
+ guard(mutex)(&i3cdev->xfer_lock);
+ i3c = i3cdev->i3c;
+ if (!i3c || i3c->dev.driver)
+ return -ENODEV;
+
+ xfers.data.out = tmp;
+
+ dev_dbg(&i3c->dev, "Writing %zu bytes.\n", count);
+
+ ret = i3c_device_do_xfers(i3c, &xfers, 1, I3C_SDR);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static int
+i3cdev_do_xfer(struct i3c_device *dev, struct i3c_ioc_xfer *xfers,
+ struct i3c_ioc_xfer __user *u_xfers, unsigned int nxfers)
+{
+ struct i3c_xfer *k_xfers __free(kfree) = NULL;
+ enum i3c_xfer_mode mode = xfers[0].mode;
+ u8 **data_ptrs;
+ int i, j, nalloc, ret = 0;
+
+ /* Since we have nxfers we may allocate k_xfer + *data_ptrs together */
+ k_xfers = kcalloc(nxfers, sizeof(*k_xfers) + sizeof(*data_ptrs),
+ GFP_KERNEL);
+ if (!k_xfers)
+ return -ENOMEM;
+
+ /* set data_ptrs to be after nxfers * i3c_xfer */
+ data_ptrs = (void *)k_xfers + (nxfers * sizeof(*k_xfers));
+
+ for (i = 0; i < nxfers; i++) {
+ bool is_read;
+
+ if (xfers[i].mode != mode) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if (memchr_inv(xfers[i].pad, 0, sizeof(xfers[i].pad))) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if (mode == I3C_SDR) {
+ if (xfers[i].rnw != I3C_DEV_DIR_WRITE &&
+ xfers[i].rnw != I3C_DEV_DIR_READ) {
+ ret = -EINVAL;
+ break;
+ }
+ is_read = xfers[i].rnw == I3C_DEV_DIR_READ;
+ } else {
+ is_read = xfers[i].cmd & 0x80;
+ }
+
+ if (is_read) {
+ data_ptrs[i] = kzalloc(xfers[i].len, GFP_KERNEL);
+ if (!data_ptrs[i]) {
+ ret = -ENOMEM;
+ break;
+ }
+ k_xfers[i].data.in = data_ptrs[i];
+ } else {
+ data_ptrs[i] = memdup_user(u64_to_user_ptr(xfers[i].data),
+ xfers[i].len);
+ if (IS_ERR(data_ptrs[i])) {
+ ret = PTR_ERR(data_ptrs[i]);
+ break;
+ }
+ k_xfers[i].data.out = data_ptrs[i];
+ }
+
+ k_xfers[i].cmd = xfers[i].cmd;
+ k_xfers[i].len = xfers[i].len;
+ }
+ nalloc = i;
+
+ if (ret < 0)
+ goto err_free_mem;
+
+ ret = i3c_device_do_xfers(dev, k_xfers, nxfers, mode);
+ if (ret)
+ goto err_free_mem;
+
+ for (i = 0; i < nxfers; i++) {
+ bool is_read = (mode == I3C_SDR) ?
+ (xfers[i].rnw == I3C_DEV_DIR_READ) :
+ (xfers[i].cmd & 0x80);
+
+ if (is_read) {
+ if (copy_to_user(u64_to_user_ptr(xfers[i].data),
+ data_ptrs[i], k_xfers[i].actual_len) ||
+ put_user(k_xfers[i].actual_len,
+ &u_xfers[i].actual_len)) {
+ ret = -EFAULT;
+ break;
+ }
+ }
+ }
+
+err_free_mem:
+ for (j = 0; j < nalloc; j++)
+ kfree(data_ptrs[j]);
+ return ret;
+}
+
+static struct i3c_ioc_xfer *
+i3cdev_get_ioc_xfer(unsigned int cmd, struct i3c_ioc_xfer __user *u_xfers,
+ unsigned int *nxfers)
+{
+ u32 tmp = _IOC_SIZE(cmd);
+
+ if ((tmp % sizeof(struct i3c_ioc_xfer)) != 0)
+ return ERR_PTR(-EINVAL);
+
+ *nxfers = tmp / sizeof(struct i3c_ioc_xfer);
+ if (*nxfers == 0)
+ return ERR_PTR(-EINVAL);
+
+ return memdup_user(u_xfers, tmp);
+}
+
+static int
+i3cdev_ioc_xfer(struct i3c_device *i3c, unsigned int cmd,
+ struct i3c_ioc_xfer __user *u_xfers)
+{
+ struct i3c_ioc_xfer *k_xfers __free(kfree) = NULL;
+ unsigned int nxfers;
+
+ k_xfers = i3cdev_get_ioc_xfer(cmd, u_xfers, &nxfers);
+ if (IS_ERR(k_xfers))
+ return PTR_ERR(k_xfers);
+
+ return i3cdev_do_xfer(i3c, k_xfers, u_xfers, nxfers);
+}
+
+static long
+i3cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct i3cdev_data *i3cdev = file->private_data;
+ struct i3c_device *i3c;
+
+ if (_IOC_TYPE(cmd) != I3C_DEV_IOC_MAGIC)
+ return -ENOTTY;
+
+ /* Use the xfer_lock to prevent device detach during ioctl call */
+ guard(mutex)(&i3cdev->xfer_lock);
+ i3c = i3cdev->i3c;
+ if (!i3c || i3c->dev.driver)
+ return -ENODEV;
+
+ dev_dbg(&i3c->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n", cmd, arg);
+
+ /* Check command number and direction */
+ if (_IOC_NR(cmd) == _IOC_NR(I3C_IOC_XFER(0)) &&
+ _IOC_DIR(cmd) == (_IOC_READ | _IOC_WRITE))
+ return i3cdev_ioc_xfer(i3c, cmd,
+ (struct i3c_ioc_xfer __user *)arg);
+
+ return -ENOTTY;
+}
+
+static int i3cdev_open(struct inode *inode, struct file *file)
+{
+ struct i3cdev_data *i3cdev = container_of(inode->i_cdev,
+ struct i3cdev_data,
+ cdev);
+ file->private_data = i3cdev;
+
+ return 0;
+}
+
+static int i3cdev_release(struct inode *inode, struct file *file)
+{
+ file->private_data = NULL;
+
+ return 0;
+}
+
+static const struct file_operations i3cdev_fops = {
+ .owner = THIS_MODULE,
+ .read = i3cdev_read,
+ .write = i3cdev_write,
+ .unlocked_ioctl = i3cdev_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+ .open = i3cdev_open,
+ .release = i3cdev_release,
+};
+
+/* ------------------------------------------------------------------------- */
+
+static const struct class i3cdev_class = {
+ .name = "i3cdev",
+};
+
+static int i3cdev_attach(struct device *dev, void *dummy)
+{
+ struct i3cdev_data *i3cdev;
+ struct i3c_device *i3c;
+ int res;
+
+ if (dev->type == &i3c_masterdev_type)
+ return 0;
+
+ i3c = dev_to_i3cdev(dev);
+
+ guard(mutex)(&i3cdev_attach_lock);
+ if (dev->driver || i3cdev_get_by_i3c(i3c))
+ return 0;
+
+ /* Get a device */
+ i3cdev = get_free_i3cdev(i3c);
+ if (IS_ERR(i3cdev))
+ return PTR_ERR(i3cdev);
+
+ mutex_init(&i3cdev->xfer_lock);
+ cdev_init(&i3cdev->cdev, &i3cdev_fops);
+ i3cdev->cdev.owner = THIS_MODULE;
+
+ device_initialize(&i3cdev->dev);
+ i3cdev->dev.devt = MKDEV(MAJOR(i3cdev_number), i3cdev->id);
+ i3cdev->dev.class = &i3cdev_class;
+ i3cdev->dev.parent = &i3c->dev;
+ i3cdev->dev.release = i3cdev_dev_release;
+
+ res = dev_set_name(&i3cdev->dev, "bus!i3c!%s", dev_name(&i3c->dev));
+ if (res)
+ goto error_put_dev;
+
+ res = cdev_device_add(&i3cdev->cdev, &i3cdev->dev);
+ if (res)
+ goto error_put_dev;
+
+ pr_debug("i3cdev: I3C device [%s] registered as minor %d\n",
+ dev_name(&i3c->dev), i3cdev->id);
+ return 0;
+
+error_put_dev:
+ list_del(&i3cdev->list);
+ put_device(&i3cdev->dev);
+ return res;
+}
+
+static int i3cdev_detach(struct device *dev, void *dummy)
+{
+ struct i3cdev_data *i3cdev;
+ struct i3c_device *i3c;
+
+ if (dev->type == &i3c_masterdev_type)
+ return 0;
+
+ i3c = dev_to_i3cdev(dev);
+
+ guard(mutex)(&i3cdev_attach_lock);
+ i3cdev = i3cdev_get_by_i3c(i3c);
+ if (!i3cdev)
+ return 0;
+
+ list_del(&i3cdev->list);
+
+ /* Prevent transfers while cdev removal */
+ scoped_guard(mutex, &i3cdev->xfer_lock)
+ i3cdev->i3c = NULL;
+
+ cdev_device_del(&i3cdev->cdev, &i3cdev->dev);
+ put_device(&i3cdev->dev);
+
+ pr_debug("i3cdev: device [%s] unregistered\n", dev_name(&i3c->dev));
+
+ return 0;
+}
+
+static int i3cdev_notifier_call(struct notifier_block *nb,
+ unsigned long action,
+ void *data)
+{
+ struct device *dev = data;
+
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ case BUS_NOTIFY_UNBOUND_DRIVER:
+ case BUS_NOTIFY_DRIVER_NOT_BOUND:
+ i3cdev_attach(dev, NULL);
+ break;
+ case BUS_NOTIFY_DEL_DEVICE:
+ case BUS_NOTIFY_REMOVED_DEVICE:
+ case BUS_NOTIFY_BIND_DRIVER:
+ i3cdev_detach(dev, NULL);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block i3cdev_notifier = {
+ .notifier_call = i3cdev_notifier_call,
+};
+
+static int __init i3cdev_init(void)
+{
+ int res;
+
+ /* Dynamically request unused major number */
+ res = alloc_chrdev_region(&i3cdev_number, 0, I3C_MINORS, "i3c");
+ if (res)
+ goto out;
+
+ /* Register device class to populate sysfs entries */
+ res = class_register(&i3cdev_class);
+ if (res)
+ goto out_unreg_chrdev;
+
+ /* Keep track of busses which have devices to add or remove later */
+ res = bus_register_notifier(&i3c_bus_type, &i3cdev_notifier);
+ if (res)
+ goto out_unreg_class;
+
+ /* Bind to already existing device without driver right away */
+ i3c_for_each_dev(NULL, i3cdev_attach);
+
+ return 0;
+
+out_unreg_class:
+ class_unregister(&i3cdev_class);
+out_unreg_chrdev:
+ unregister_chrdev_region(i3cdev_number, I3C_MINORS);
+out:
+ pr_err("%s: Driver Initialisation failed\n", __FILE__);
+ return res;
+}
+
+static void __exit i3cdev_exit(void)
+{
+ bus_unregister_notifier(&i3c_bus_type, &i3cdev_notifier);
+ i3c_for_each_dev(NULL, i3cdev_detach);
+ class_unregister(&i3cdev_class);
+ unregister_chrdev_region(i3cdev_number, I3C_MINORS);
+}
+
+MODULE_AUTHOR("Vitor Soares <soares@synopsys.com>");
+MODULE_DESCRIPTION("I3C /dev entries driver");
+MODULE_LICENSE("GPL");
+
+module_init(i3cdev_init);
+module_exit(i3cdev_exit);
diff --git a/include/uapi/linux/i3c/i3cdev.h b/include/uapi/linux/i3c/i3cdev.h
new file mode 100644
index 000000000000..69e901017378
--- /dev/null
+++ b/include/uapi/linux/i3c/i3cdev.h
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2020 Synopsys, Inc. and/or its affiliates.
+ * Copyright (c) 2026 Google LLC
+ *
+ * Author: Vitor Soares <vitor.soares@synopsys.com>
+ */
+
+#ifndef _UAPI_I3C_DEV_H_
+#define _UAPI_I3C_DEV_H_
+
+#include <linux/types.h>
+#include <linux/ioctl.h>
+
+/* IOCTL commands */
+#define I3C_DEV_IOC_MAGIC 0x07
+
+#define I3C_DEV_DIR_WRITE 0
+#define I3C_DEV_DIR_READ 1
+
+#define I3C_XFER_MODE_HDR_DDR 0
+#define I3C_XFER_MODE_HDR_TSP 1
+#define I3C_XFER_MODE_HDR_TSL 2
+#define I3C_XFER_MODE_SDR 31
+
+/**
+ * struct i3c_ioc_xfer - I3C ioctl transfer
+ * @data: Holds pointer to userspace buffer with transmit/receive data.
+ * @len: Length of data buffer, in bytes.
+ * @actual_len: Actual length of data transferred on read, in bytes (output).
+ * @rnw: Transfer direction for SDR mode (I3C_DEV_DIR_WRITE or I3C_DEV_DIR_READ).
+ * @cmd: Command byte for HDR mode (0x00-0x7f write, 0x80-0xff read).
+ * @mode: Transfer mode (I3C_XFER_MODE_SDR, I3C_XFER_MODE_HDR_DDR, etc.).
+ * @pad: Reserved for future extensions; must be zeroed.
+ */
+struct i3c_ioc_xfer {
+ __u64 data;
+ __u16 len;
+ __u16 actual_len;
+ union {
+ __u8 rnw;
+ __u8 cmd;
+ };
+ __u8 mode;
+ __u8 pad[2];
+};
+
+#define __I3C_XFER_SIZE(type) \
+ ((((sizeof(struct i3c_ioc_xfer)) * (type)) < (1 << _IOC_SIZEBITS)) \
+ ? ((sizeof(struct i3c_ioc_xfer)) * (type)) : 0)
+
+#define I3C_XFER_SIZE(N) __I3C_XFER_SIZE(N)
+
+#define I3C_IOC_XFER(N) \
+ _IOC(_IOC_READ | _IOC_WRITE, I3C_DEV_IOC_MAGIC, 30, I3C_XFER_SIZE(N))
+
+#endif
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v5 5/5] tools: i3c: add i3ctransfer utility
2026-09-21 23:05 [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev Sam Agazaryan
` (3 preceding siblings ...)
2026-09-21 23:06 ` [PATCH v5 4/5] i3c: add i3cdev module to expose i3c dev in /dev Sam Agazaryan
@ 2026-09-21 23:06 ` Sam Agazaryan
2026-09-25 9:08 ` [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev Andy Shevchenko
5 siblings, 0 replies; 9+ messages in thread
From: Sam Agazaryan @ 2026-09-21 23:06 UTC (permalink / raw)
To: linux-i3c, Alexandre Belloni, Frank Li
Cc: Greg Kroah-Hartman, Wolfram Sang, Arnd Bergmann, Adrian Hunter,
Meagan Lloyd, Vitor Soares, Oleksandr Shulzhenko,
Boris Brezillon, linux-kernel, Sam Agazaryan
Add the i3ctransfer userspace utility to tools/i3c/ for testing and
interacting with I3C target devices via /dev/bus/i3c/* character
devices.
Based on the i3ctransfer tool from i3c-tools by Vitor Soares, updated
for the unified I3C_IOC_XFER UAPI (struct i3c_ioc_xfer):
- Supports SDR and HDR (HDR-DDR, HDR-TSP, HDR-TSL) transfer modes
via -m/--mode and -c/--command options.
- Reports actual_len bytes received on read transfers.
Signed-off-by: Sam Agazaryan <samagazaryan@google.com>
---
Note to Vitor Soares:
This patch ports your GPL-2.0 i3ctransfer utility from i3c-tools into
tools/i3c/ and updates it for struct i3c_ioc_xfer (SDR/HDR modes and
actual_len read reporting). Since the original GitHub commit did not
include a Signed-off-by tag, could you please reply with your
Signed-off-by or Acked-by so we can properly record your sign-off?
MAINTAINERS | 1 +
tools/Makefile | 13 +-
tools/i3c/Build | 1 +
tools/i3c/Makefile | 58 ++++++++
tools/i3c/i3ctransfer.c | 307 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 374 insertions(+), 6 deletions(-)
create mode 100644 tools/i3c/Build
create mode 100644 tools/i3c/Makefile
create mode 100644 tools/i3c/i3ctransfer.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 30a5cb12c4f0..8a4166ebc9dc 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -12365,6 +12365,7 @@ F: drivers/i3c/
F: include/dt-bindings/i3c/
F: include/linux/i3c/
F: include/uapi/linux/i3c/
+F: tools/i3c/
IBM Operation Panel Input Driver
M: Eddie James <eajames@linux.ibm.com>
diff --git a/tools/Makefile b/tools/Makefile
index cb40961a740f..7c17e7555fd7 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -20,6 +20,7 @@ help:
@echo ' freefall - laptop accelerometer program for disk protection'
@echo ' gpio - GPIO tools'
@echo ' hv - tools used when in Hyper-V clients'
+ @echo ' i3c - I3C tools'
@echo ' iio - IIO tools'
@echo ' intel-speed-select - Intel Speed Select tool'
@echo ' kvm_stat - top-like utility for displaying kvm statistics'
@@ -70,7 +71,7 @@ acpi: FORCE
cpupower: FORCE
$(call descend,power/$@)
-counter dma firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtool leds wmi firmware debugging tracing: FORCE
+counter dma firewire hv guest bootconfig spi usb virtio mm bpf i3c iio gpio objtool leds wmi firmware debugging tracing: FORCE
$(call descend,$@)
bpf/%: FORCE
@@ -126,7 +127,7 @@ ynl: FORCE
all: acpi counter cpupower dma gpio hv firewire \
perf selftests bootconfig spi turbostat usb \
virtio mm bpf x86_energy_perf_policy \
- tmon freefall iio objtool kvm_stat wmi \
+ tmon freefall i3c iio objtool kvm_stat wmi \
debugging tracing thermal thermometer thermal-engine ynl
acpi_install:
@@ -135,7 +136,7 @@ acpi_install:
cpupower_install:
$(call descend,power/$(@:_install=),install)
-counter_install dma_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install mm_install bpf_install objtool_install wmi_install debugging_install tracing_install:
+counter_install dma_install firewire_install gpio_install hv_install i3c_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install mm_install bpf_install objtool_install wmi_install debugging_install tracing_install:
$(call descend,$(@:_install=),install)
selftests_install:
@@ -166,7 +167,7 @@ ynl_install:
$(call descend,net/$(@:_install=),install)
install: acpi_install counter_install cpupower_install dma_install gpio_install \
- hv_install firewire_install iio_install \
+ hv_install firewire_install i3c_install iio_install \
perf_install selftests_install turbostat_install usb_install \
virtio_install mm_install bpf_install x86_energy_perf_policy_install \
tmon_install freefall_install objtool_install kvm_stat_install \
@@ -179,7 +180,7 @@ acpi_clean:
cpupower_clean:
$(call descend,power/cpupower,clean)
-counter_clean dma_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean firmware_clean debugging_clean tracing_clean:
+counter_clean dma_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean i3c_clean iio_clean gpio_clean objtool_clean leds_clean firmware_clean debugging_clean tracing_clean:
$(call descend,$(@:_clean=),clean)
libapi_clean:
@@ -227,7 +228,7 @@ ynl_clean:
clean: acpi_clean counter_clean cpupower_clean dma_clean hv_clean firewire_clean \
perf_clean selftests_clean turbostat_clean bootconfig_clean spi_clean usb_clean virtio_clean \
- mm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \
+ mm_clean bpf_clean i3c_clean iio_clean x86_energy_perf_policy_clean tmon_clean \
freefall_clean build_clean libbpf_clean libsubcmd_clean \
gpio_clean objtool_clean leds_clean wmi_clean firmware_clean debugging_clean \
intel-speed-select_clean tracing_clean thermal_clean thermometer_clean thermal-engine_clean \
diff --git a/tools/i3c/Build b/tools/i3c/Build
new file mode 100644
index 000000000000..6a22328ad983
--- /dev/null
+++ b/tools/i3c/Build
@@ -0,0 +1 @@
+i3ctransfer-y += i3ctransfer.o
diff --git a/tools/i3c/Makefile b/tools/i3c/Makefile
new file mode 100644
index 000000000000..43ce8769185b
--- /dev/null
+++ b/tools/i3c/Makefile
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-2.0-only
+include ../scripts/Makefile.include
+
+bindir ?= /usr/bin
+
+ifeq ($(srctree),)
+srctree := $(patsubst %/,%,$(dir $(CURDIR)))
+srctree := $(patsubst %/,%,$(dir $(srctree)))
+endif
+
+# Do not use make's built-in rules
+# (this improves performance and avoids hard-to-debug behaviour);
+MAKEFLAGS += -r
+
+CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include
+
+ALL_TARGETS := i3ctransfer
+ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS))
+
+all: $(ALL_PROGRAMS)
+
+export srctree OUTPUT CC LD CFLAGS
+include $(srctree)/tools/build/Makefile.include
+
+#
+# We need the following to be outside of kernel tree
+#
+$(OUTPUT)include/linux/i3c: ../../include/uapi/linux/i3c
+ mkdir -p $(OUTPUT)include/linux/i3c 2>&1 || true
+ ln -sf $(CURDIR)/../../include/uapi/linux/i3c/i3cdev.h $@
+
+prepare: $(OUTPUT)include/linux/i3c
+
+#
+# i3ctransfer
+#
+I3CTRANSFER_IN := $(OUTPUT)i3ctransfer-in.o
+$(I3CTRANSFER_IN): prepare FORCE
+ $(Q)$(MAKE) $(build)=i3ctransfer
+$(OUTPUT)i3ctransfer: $(I3CTRANSFER_IN)
+ $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
+
+clean:
+ rm -f $(ALL_PROGRAMS)
+ rm -rf $(OUTPUT)include/
+ find $(or $(OUTPUT),.) -name '*.o' -delete
+ find $(or $(OUTPUT),.) -name '\.*.o.d' -delete
+ find $(or $(OUTPUT),.) -name '\.*.o.cmd' -delete
+
+install: $(ALL_PROGRAMS)
+ install -d -m 755 $(DESTDIR)$(bindir); \
+ for program in $(ALL_PROGRAMS); do \
+ install $$program $(DESTDIR)$(bindir); \
+ done
+
+FORCE:
+
+.PHONY: all install clean FORCE prepare
diff --git a/tools/i3c/i3ctransfer.c b/tools/i3c/i3ctransfer.c
new file mode 100644
index 000000000000..a5b071e63530
--- /dev/null
+++ b/tools/i3c/i3ctransfer.c
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 Synopsys, Inc. and/or its affiliates.
+ * Copyright (c) 2026 Google LLC
+ *
+ * Author: Vitor Soares <vitor.soares@synopsys.com>
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <linux/i3c/i3cdev.h>
+
+#define VERSION "0.2"
+
+static const char *sopts = "d:m:c:r:w:vh";
+static const struct option lopts[] = {
+ {"device", required_argument, NULL, 'd' },
+ {"mode", required_argument, NULL, 'm' },
+ {"command", required_argument, NULL, 'c' },
+ {"read", required_argument, NULL, 'r' },
+ {"write", required_argument, NULL, 'w' },
+ {"help", no_argument, NULL, 'h' },
+ {"version", no_argument, NULL, 'v' },
+ {0, 0, 0, 0}
+};
+
+static void print_usage(const char *name)
+{
+ fprintf(stderr, "usage: %s options...\n", name);
+ fprintf(stderr, " options:\n");
+ fprintf(stderr, " -d --device <dev> device to use.\n");
+ fprintf(stderr,
+ " -m --mode <mode> sdr (default), hdr-ddr, hdr-tsp, hdr-tsl.\n");
+ fprintf(stderr,
+ " -c --command <cmd> HDR command byte (0x00-0x7f W, 0x80-0xff R).\n");
+ fprintf(stderr, " -r --read <len> read data length in bytes.\n");
+ fprintf(stderr,
+ " -w --write <block> write comma-separated bytes (e.g. 0x01,0x02).\n");
+ fprintf(stderr, " -h --help output usage message and exit.\n");
+ fprintf(stderr, " -v --version output version number and exit.\n");
+}
+
+static int parse_mode(const char *arg, uint8_t *mode)
+{
+ char *endptr;
+ long val;
+
+ if (!strcmp(arg, "sdr")) {
+ *mode = I3C_XFER_MODE_SDR;
+ return 0;
+ }
+ if (!strcmp(arg, "hdr-ddr") || !strcmp(arg, "ddr")) {
+ *mode = I3C_XFER_MODE_HDR_DDR;
+ return 0;
+ }
+ if (!strcmp(arg, "hdr-tsp") || !strcmp(arg, "tsp")) {
+ *mode = I3C_XFER_MODE_HDR_TSP;
+ return 0;
+ }
+ if (!strcmp(arg, "hdr-tsl") || !strcmp(arg, "tsl")) {
+ *mode = I3C_XFER_MODE_HDR_TSL;
+ return 0;
+ }
+
+ val = strtol(arg, &endptr, 0);
+ if (*endptr == '\0' &&
+ (val == I3C_XFER_MODE_HDR_DDR ||
+ val == I3C_XFER_MODE_HDR_TSP ||
+ val == I3C_XFER_MODE_HDR_TSL ||
+ val == I3C_XFER_MODE_SDR)) {
+ *mode = (uint8_t)val;
+ return 0;
+ }
+
+ fprintf(stderr, "Error: invalid transfer mode '%s'\n", arg);
+ return -1;
+}
+
+static int rx_args_to_xfer(struct i3c_ioc_xfer *xfer, char *arg,
+ uint8_t mode, int cmd)
+{
+ char *endptr;
+ uint8_t *tmp;
+ long len;
+
+ len = strtol(arg, &endptr, 0);
+ if (*endptr != '\0' || len <= 0 || len > UINT16_MAX) {
+ fprintf(stderr, "Error: invalid read length '%s'\n", arg);
+ return -1;
+ }
+
+ if (mode == I3C_XFER_MODE_SDR) {
+ xfer->rnw = I3C_DEV_DIR_READ;
+ } else {
+ if (cmd < 0 || !(cmd & 0x80)) {
+ fprintf(stderr,
+ "Error: HDR read requires command (-c) in range 0x80..0xff\n");
+ return -1;
+ }
+ xfer->cmd = (uint8_t)cmd;
+ }
+
+ tmp = calloc(len, sizeof(uint8_t));
+ if (!tmp)
+ return -1;
+
+ xfer->mode = mode;
+ xfer->len = (uint16_t)len;
+ xfer->data = (uintptr_t)tmp;
+
+ return 0;
+}
+
+static int w_args_to_xfer(struct i3c_ioc_xfer *xfer, char *arg,
+ uint8_t mode, int cmd)
+{
+ char *data_ptrs[256];
+ int len, i = 0;
+ uint8_t *tmp;
+
+ if (mode == I3C_XFER_MODE_SDR) {
+ xfer->rnw = I3C_DEV_DIR_WRITE;
+ } else {
+ if (cmd < 0 || (cmd & 0x80)) {
+ fprintf(stderr,
+ "Error: HDR write requires command (-c) in range 0x00..0x7f\n");
+ return -1;
+ }
+ xfer->cmd = (uint8_t)cmd;
+ }
+
+ data_ptrs[i] = strtok(arg, ",");
+ while (data_ptrs[i] && i < 255)
+ data_ptrs[++i] = strtok(NULL, ",");
+
+ if (i == 0) {
+ fprintf(stderr, "Error: empty write data block\n");
+ return -1;
+ }
+
+ tmp = calloc(i, sizeof(uint8_t));
+ if (!tmp)
+ return -1;
+
+ for (len = 0; len < i; len++) {
+ char *endptr;
+ long val = strtol(data_ptrs[len], &endptr, 0);
+
+ if (*endptr != '\0' || val < 0 || val > UINT8_MAX) {
+ fprintf(stderr, "Error: invalid byte value '%s'\n",
+ data_ptrs[len]);
+ free(tmp);
+ return -1;
+ }
+ tmp[len] = (uint8_t)val;
+ }
+
+ xfer->mode = mode;
+ xfer->len = (uint16_t)len;
+ xfer->data = (uintptr_t)tmp;
+
+ return 0;
+}
+
+static void print_rx_data(const struct i3c_ioc_xfer *xfer)
+{
+ const uint8_t *tmp = (const uint8_t *)(uintptr_t)xfer->data;
+ int i;
+
+ fprintf(stdout, " received data (%u bytes):\n", xfer->actual_len);
+ for (i = 0; i < xfer->actual_len; i++)
+ fprintf(stdout, " 0x%02x\n", tmp[i]);
+}
+
+int main(int argc, char *argv[])
+{
+ uint8_t mode = I3C_XFER_MODE_SDR;
+ struct i3c_ioc_xfer *xfers;
+ char *device = NULL;
+ int file, ret, opt, i;
+ int nxfers = 0;
+ int cmd = -1;
+
+ while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != EOF) {
+ switch (opt) {
+ case 'h':
+ print_usage(argv[0]);
+ return EXIT_SUCCESS;
+ case 'v':
+ fprintf(stderr, "%s - %s\n", argv[0], VERSION);
+ return EXIT_SUCCESS;
+ case 'd':
+ device = optarg;
+ break;
+ case 'm':
+ if (parse_mode(optarg, &mode))
+ return EXIT_FAILURE;
+ break;
+ case 'c': {
+ char *endptr;
+ long val = strtol(optarg, &endptr, 0);
+
+ if (*endptr != '\0' || val < 0 || val > UINT8_MAX) {
+ fprintf(stderr, "Error: invalid command '%s'\n",
+ optarg);
+ return EXIT_FAILURE;
+ }
+ cmd = (int)val;
+ break;
+ }
+ case 'r':
+ case 'w':
+ nxfers++;
+ break;
+ default:
+ print_usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (!device || nxfers == 0) {
+ print_usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ file = open(device, O_RDWR);
+ if (file < 0) {
+ fprintf(stderr, "Error: failed to open %s: %s\n",
+ device, strerror(errno));
+ return EXIT_FAILURE;
+ }
+
+ xfers = calloc(nxfers, sizeof(*xfers));
+ if (!xfers) {
+ close(file);
+ return EXIT_FAILURE;
+ }
+
+ optind = 1;
+ nxfers = 0;
+ cmd = -1;
+
+ while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != EOF) {
+ switch (opt) {
+ case 'h':
+ case 'v':
+ case 'd':
+ case 'm':
+ break;
+ case 'c':
+ cmd = (int)strtol(optarg, NULL, 0);
+ break;
+ case 'r':
+ if (rx_args_to_xfer(&xfers[nxfers], optarg, mode, cmd)) {
+ ret = EXIT_FAILURE;
+ goto err_free;
+ }
+ nxfers++;
+ break;
+ case 'w':
+ if (w_args_to_xfer(&xfers[nxfers], optarg, mode, cmd)) {
+ ret = EXIT_FAILURE;
+ goto err_free;
+ }
+ nxfers++;
+ break;
+ }
+ }
+
+ if (ioctl(file, I3C_IOC_XFER(nxfers), xfers) < 0) {
+ fprintf(stderr, "Error: transfer failed: %s\n", strerror(errno));
+ ret = EXIT_FAILURE;
+ goto err_free;
+ }
+
+ for (i = 0; i < nxfers; i++) {
+ bool is_read = (mode == I3C_XFER_MODE_SDR) ?
+ (xfers[i].rnw == I3C_DEV_DIR_READ) :
+ (xfers[i].cmd & 0x80);
+
+ fprintf(stdout, "Success on message %d\n", i);
+ if (is_read)
+ print_rx_data(&xfers[i]);
+ }
+
+ ret = EXIT_SUCCESS;
+
+err_free:
+ for (i = 0; i < nxfers; i++)
+ free((void *)(uintptr_t)xfers[i].data);
+ free(xfers);
+ close(file);
+
+ return ret;
+}
--
2.55.0.1082.g2b9226bbc0-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v5 3/5] i3c: use actual_len for read transfers
2026-09-21 23:06 ` [PATCH v5 3/5] i3c: use actual_len for read transfers Sam Agazaryan
@ 2026-09-24 11:53 ` Adrian Hunter
0 siblings, 0 replies; 9+ messages in thread
From: Adrian Hunter @ 2026-09-24 11:53 UTC (permalink / raw)
To: Sam Agazaryan, linux-i3c, Alexandre Belloni, Frank Li
Cc: Greg Kroah-Hartman, Wolfram Sang, Arnd Bergmann, Meagan Lloyd,
Vitor Soares, Oleksandr Shulzhenko, Boris Brezillon,
linux-kernel
On 22/09/2026 02:06, Sam Agazaryan wrote:
> Currently only the amd and svc I3C controller drivers populate
> i3c_xfer.actual_len on reads, while dw-i3c-master and mipi-i3c-hci
> overwrite i3c_xfer.len with the received byte count and leave actual_len
> as 0. Because of this inconsistency, mctp-i3c reads xfer.len instead of
> xfer.actual_len, making it fail on controllers that do not mutate len.
>
> Unify read length reporting across the subsystem:
> - Clarify in <linux/i3c/device.h> that actual_len reflects the actual
> number of bytes transferred on reads.
> - Update adi, cdns, dw, mipi-i3c-hci, and renesas controller drivers to
> populate actual_len on read transfers without mutating len.
> - Update mctp-i3c to read xfer.actual_len instead of xfer.len.
>
> Co-developed-by: Meagan Lloyd <meaganlloyd@linux.microsoft.com>
> Signed-off-by: Meagan Lloyd <meaganlloyd@linux.microsoft.com>
> Signed-off-by: Sam Agazaryan <samagazaryan@google.com>
Looks good.
1 issue from Sashiko:
https://sashiko.dev/#/patchset/20260921230603.2518652-1-samagazaryan%40google.com?part=3
Also i3c_dev_do_xfers_locked() should probably clamp actual_len to len.
> ---
> drivers/i3c/master/adi-i3c-master.c | 5 ++++-
> drivers/i3c/master/dw-i3c-master.c | 2 +-
> drivers/i3c/master/i3c-master-cdns.c | 5 ++++-
> drivers/i3c/master/mipi-i3c-hci/core.c | 2 +-
> drivers/i3c/master/renesas-i3c.c | 3 +++
> drivers/net/mctp/mctp-i3c.c | 10 +++++-----
> include/linux/i3c/device.h | 2 +-
> 7 files changed, 19 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/i3c/master/adi-i3c-master.c b/drivers/i3c/master/adi-i3c-master.c
> index b35386260350..d033bfd4ecc2 100644
> --- a/drivers/i3c/master/adi-i3c-master.c
> +++ b/drivers/i3c/master/adi-i3c-master.c
> @@ -412,8 +412,11 @@ static int adi_i3c_master_i3c_xfers(struct i3c_dev_desc *dev,
>
> ret = xfer->ret;
>
> - for (i = 0; i < nxfers; i++)
> + for (i = 0; i < nxfers; i++) {
> xfers[i].err = adi_i3c_cmd_get_err(&xfer->cmds[i]);
> + if (xfers[i].rnw)
> + xfers[i].actual_len = xfer->cmds[i].rx_len;
> + }
>
> return ret;
> }
> diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
> index 4563d8761ba0..28e8eb293747 100644
> --- a/drivers/i3c/master/dw-i3c-master.c
> +++ b/drivers/i3c/master/dw-i3c-master.c
> @@ -1029,7 +1029,7 @@ static int dw_i3c_master_i3c_xfers(struct i3c_dev_desc *dev,
> struct dw_i3c_cmd *cmd = &xfer->cmds[i];
>
> if (i3c_xfers[i].rnw)
> - i3c_xfers[i].len = cmd->rx_len;
> + i3c_xfers[i].actual_len = cmd->rx_len;
> }
>
> ret = xfer->ret;
> diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c
> index 2d98c1ce9b12..331062c10b31 100644
> --- a/drivers/i3c/master/i3c-master-cdns.c
> +++ b/drivers/i3c/master/i3c-master-cdns.c
> @@ -795,8 +795,11 @@ static int cdns_i3c_master_i3c_xfers(struct i3c_dev_desc *dev,
>
> ret = cdns_xfer->ret;
>
> - for (i = 0; i < nxfers; i++)
> + for (i = 0; i < nxfers; i++) {
> xfers[i].err = cdns_i3c_cmd_get_err(&cdns_xfer->cmds[i]);
> + if (xfers[i].rnw)
> + xfers[i].actual_len = cdns_xfer->cmds[i].rx_len;
> + }
>
> cdns_i3c_master_free_xfer(cdns_xfer);
>
> diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
> index dadf049bd4b5..f74c02fab9e3 100644
> --- a/drivers/i3c/master/mipi-i3c-hci/core.c
> +++ b/drivers/i3c/master/mipi-i3c-hci/core.c
> @@ -509,7 +509,7 @@ static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev,
> goto out;
> for (i = 0; i < nxfers; i++) {
> if (i3c_xfers[i].rnw)
> - i3c_xfers[i].len = RESP_DATA_LENGTH(xfer[i].response);
> + i3c_xfers[i].actual_len = RESP_DATA_LENGTH(xfer[i].response);
> if (RESP_STATUS(xfer[i].response) != RESP_SUCCESS) {
> ret = -EIO;
> goto out;
> diff --git a/drivers/i3c/master/renesas-i3c.c b/drivers/i3c/master/renesas-i3c.c
> index 28c0927a0179..a3dcb4cd9b0e 100644
> --- a/drivers/i3c/master/renesas-i3c.c
> +++ b/drivers/i3c/master/renesas-i3c.c
> @@ -918,6 +918,9 @@ static int renesas_i3c_i3c_xfers(struct i3c_dev_desc *dev, struct i3c_xfer *i3c_
> time_left = renesas_i3c_wait_xfer(i3c, xfer);
> if (!time_left)
> xfer_failed = true;
> +
> + if (i3c_xfers[i].rnw)
> + i3c_xfers[i].actual_len = cmd->rx_count;
> }
>
> if (xfer_failed)
> diff --git a/drivers/net/mctp/mctp-i3c.c b/drivers/net/mctp/mctp-i3c.c
> index 6d2bbae7477b..5f94c11a1712 100644
> --- a/drivers/net/mctp/mctp-i3c.c
> +++ b/drivers/net/mctp/mctp-i3c.c
> @@ -131,12 +131,12 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi)
> if (rc < 0)
> goto err;
>
> - if (WARN_ON_ONCE(xfer.len > mi->mrl)) {
> + if (WARN_ON_ONCE(xfer.actual_len > mi->mrl)) {
> /* Bad i3c bus driver */
> rc = -EIO;
> goto err;
> }
> - if (xfer.len < MCTP_I3C_MINLEN) {
> + if (xfer.actual_len < MCTP_I3C_MINLEN) {
> stats->rx_length_errors++;
> rc = -EIO;
> goto err;
> @@ -145,15 +145,15 @@ static int mctp_i3c_read(struct mctp_i3c_device *mi)
> /* check PEC, including address byte */
> addr = mi->addr << 1 | 1;
> pec = i2c_smbus_pec(0, &addr, 1);
> - pec = i2c_smbus_pec(pec, xfer.data.in, xfer.len - 1);
> - if (pec != ((u8 *)xfer.data.in)[xfer.len - 1]) {
> + pec = i2c_smbus_pec(pec, xfer.data.in, xfer.actual_len - 1);
> + if (pec != ((u8 *)xfer.data.in)[xfer.actual_len - 1]) {
> stats->rx_crc_errors++;
> rc = -EINVAL;
> goto err;
> }
>
> /* Remove PEC */
> - skb_trim(skb, xfer.len - 1);
> + skb_trim(skb, xfer.actual_len - 1);
>
> cb = __mctp_cb(skb);
> cb->halen = PID_SIZE;
> diff --git a/include/linux/i3c/device.h b/include/linux/i3c/device.h
> index 971d53349b6f..f868a7bf2bc9 100644
> --- a/include/linux/i3c/device.h
> +++ b/include/linux/i3c/device.h
> @@ -59,7 +59,7 @@ enum i3c_xfer_mode {
> * @rnw: encodes the transfer direction. true for a read, false for a write
> * @cmd: Read/Write command in HDR mode, read: 0x80 - 0xff, write: 0x00 - 0x7f
> * @len: transfer length in bytes of the transfer
> - * @actual_len: actual length in bytes are transferred by the controller
> + * @actual_len: actual length in bytes transferred by the controller on read
> * @data: input/output buffer
> * @data.in: input buffer. Must point to a DMA-able buffer
> * @data.out: output buffer. Must point to a DMA-able buffer
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev
2026-09-21 23:05 [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev Sam Agazaryan
` (4 preceding siblings ...)
2026-09-21 23:06 ` [PATCH v5 5/5] tools: i3c: add i3ctransfer utility Sam Agazaryan
@ 2026-09-25 9:08 ` Andy Shevchenko
2026-09-25 10:57 ` Wolfram Sang
5 siblings, 1 reply; 9+ messages in thread
From: Andy Shevchenko @ 2026-09-25 9:08 UTC (permalink / raw)
To: Sam Agazaryan
Cc: linux-i3c, Alexandre Belloni, Frank Li, Greg Kroah-Hartman,
Wolfram Sang, Arnd Bergmann, Adrian Hunter, Meagan Lloyd,
Vitor Soares, Oleksandr Shulzhenko, Boris Brezillon,
linux-kernel
On Mon, Sep 21, 2026 at 11:05:58PM +0000, Sam Agazaryan wrote:
> This patch series introduces the i3cdev module, exposing unbound I3C
> target devices to userspace via character device nodes (/dev/bus/i3c/*),
> along with the i3ctransfer userspace utility in tools/i3c/.
>
> Userspace access to I3C targets is needed for devices that do not have a
> kernel driver bound to them, such as targets in ROM/bootloader recovery
> mode (e.g., OCP Secure Firmware Recovery v1.1 and Caliptra Silicon Root of
> Trust recovery flows), as well as hardware bring-up and diagnostics.
>
> When a kernel driver later binds to an I3C device (for example via
> dynamic module loading), i3cdev automatically detaches via the
> BUS_NOTIFY_BIND_DRIVER notifier so kernel drivers always take
> precedence.
>
> Testing status:
> This v5 series has been compile-tested across all modified I3C controller
> drivers with W=1. Posting v5 now so collaborators and controller owners
> can test the unified UAPI and actual_len updates on their respective
> hardware and provide Tested-by tags while we complete final hardware
> verification of the v5 updates on our platform.
I do not know how we end up here. The not-settled yet discussion is in
thread with aqt_dCazxTDR4cpk@ashevche-desk.local.
But I have a big concern about exposing i3c to user space in the way we have it
in i2c. Taking into account that i2c is an odd bus and might lead even to HW
*physical* breakage, I would thing 100 times before making the same mistake
in i3c. If you ever want to do this, this must not be user visible feature
(hidden under expert and debug and maybe even more guards for the starter).
Personally from my perspective this is no go, but I'm not a maintainer here.
P.S.
And you need to gather the opinion of Wolfram as well.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev
2026-09-25 9:08 ` [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev Andy Shevchenko
@ 2026-09-25 10:57 ` Wolfram Sang
0 siblings, 0 replies; 9+ messages in thread
From: Wolfram Sang @ 2026-09-25 10:57 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Sam Agazaryan, linux-i3c, Alexandre Belloni, Frank Li,
Greg Kroah-Hartman, Arnd Bergmann, Adrian Hunter, Meagan Lloyd,
Vitor Soares, Oleksandr Shulzhenko, Boris Brezillon,
linux-kernel
Hi Andy,
> And you need to gather the opinion of Wolfram as well.
Dunno if I can help here. When it comes to driver development, a dev
interface is surely useful but can stay in some debug corner. However,
cover letter says:
>> Userspace access to I3C targets is needed for devices that do not have a
>> kernel driver bound to them, such as targets in ROM/bootloader recovery
>> mode (e.g., OCP Secure Firmware Recovery v1.1 and Caliptra Silicon Root of
>> Trust recovery flows), as well as hardware bring-up and diagnostics.
I don't know anything about these recovery mechanisms and their needs. I
might have time to look into I3C things after all the conferences in
Prague, but no promises...
Happy hacking,
Wolfram
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-09-25 10:57 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-21 23:05 [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev Sam Agazaryan
2026-09-21 23:05 ` [PATCH v5 1/5] i3c: master: export i3c_masterdev_type Sam Agazaryan
2026-09-21 23:06 ` [PATCH v5 2/5] i3c: master: add i3c_for_each_dev helper Sam Agazaryan
2026-09-21 23:06 ` [PATCH v5 3/5] i3c: use actual_len for read transfers Sam Agazaryan
2026-09-24 11:53 ` Adrian Hunter
2026-09-21 23:06 ` [PATCH v5 4/5] i3c: add i3cdev module to expose i3c dev in /dev Sam Agazaryan
2026-09-21 23:06 ` [PATCH v5 5/5] tools: i3c: add i3ctransfer utility Sam Agazaryan
2026-09-25 9:08 ` [PATCH v5 0/5] i3c: add i3cdev module to expose i3c dev in /dev Andy Shevchenko
2026-09-25 10:57 ` Wolfram Sang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®