[PATCH v15 2/8] i3c: master: Add controller-only device operation helpers
From: Lakshay Piplani
Date: Mon Aug 17 2026 - 06:40:10 EST
The generic I3C device attach and IBI paths perform both core-level
bookkeeping and controller-specific operations. An I3C hub, however,
maintains a logical device descriptor on the virtual downstream bus and
a separate parent-facing descriptor used by the physical parent
controller.
Add controller-only helpers that invoke the controller callbacks
without updating the bus device list, address-slot state, or generic IBI
lifecycle. This allows the hub framework to keep the logical descriptor
associated with the virtual controller while using a permanent
parent-facing descriptor for operations handled by the physical
controller.
Add helpers for:
- attaching, reattaching, and detaching a device from a controller;
- requesting and freeing controller IBI resources;
- enabling and disabling IBIs at the controller;
- recycling controller IBI slots.
Refactor the existing generic core paths to use these helpers, keeping
the current behaviour unchanged for regular I3C devices.
Also release the generic IBI workqueue and state when the controller
request callback fails.
The helpers are declared in the I3C internal header because they are
intended for use by the generic I3C hub framework rather than by
individual controller drivers.
Signed-off-by: Lakshay Piplani <lakshay.piplani@xxxxxxx>
Signed-off-by: Aman Kumar Pandey <aman.kumarpandey@xxxxxxx>
Signed-off-by: Vikash Bansal <vikash.bansal@xxxxxxx>
---
Changes in v15:
- Rework the patch to introduce controller-only attach, reattach and detach
helpers for use by the I3C hub core
- Add controller-only helpers for requesting, freeing, enabling, disabling
and recycling IBI resources
---
---
drivers/i3c/internals.h | 14 +++
drivers/i3c/master.c | 228 ++++++++++++++++++++++++++++++++++------
2 files changed, 212 insertions(+), 30 deletions(-)
diff --git a/drivers/i3c/internals.h b/drivers/i3c/internals.h
index 0f1f3f766623..f56f5f23a394 100644
--- a/drivers/i3c/internals.h
+++ b/drivers/i3c/internals.h
@@ -22,6 +22,20 @@ int i3c_dev_setdasa_locked(struct i3c_dev_desc *dev);
int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev,
struct i3c_xfer *xfers,
int nxfers, enum i3c_xfer_mode mode);
+
+int i3c_master_attach_i3c_dev_controller(struct i3c_dev_desc *dev);
+int i3c_master_reattach_i3c_dev_controller(struct i3c_dev_desc *dev,
+ u8 old_dyn_addr);
+void i3c_master_detach_i3c_dev_controller(struct i3c_dev_desc *dev);
+
+int i3c_dev_disable_ibi_controller_locked(struct i3c_dev_desc *dev);
+int i3c_dev_enable_ibi_controller_locked(struct i3c_dev_desc *dev);
+int i3c_dev_request_ibi_controller_locked(struct i3c_dev_desc *dev,
+ const struct i3c_ibi_setup *req);
+void i3c_dev_free_ibi_controller_locked(struct i3c_dev_desc *dev);
+void i3c_dev_recycle_ibi_slot_controller(struct i3c_dev_desc *dev,
+ struct i3c_ibi_slot *slot);
+
int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev);
int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev);
int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 03fb41f0786c..7ae6b3aaa9b4 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -1764,6 +1764,27 @@ static int i3c_master_get_i3c_addrs(struct i3c_dev_desc *dev)
return -EBUSY;
}
+/**
+ * i3c_master_attach_i3c_dev_controller() - Attach device state to controller
+ * @dev: I3C device descriptor
+ *
+ * Invoke the current controller's attach callback without changing address
+ * slot state or adding the device to the controller's device list.
+ *
+ * Return: 0 on success, or a negative error code returned by the controller.
+ */
+int i3c_master_attach_i3c_dev_controller(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev);
+
+ /* Do not attach the master device itself. */
+ if (master->this != dev && master->ops->attach_i3c_dev)
+ return master->ops->attach_i3c_dev(dev);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(i3c_master_attach_i3c_dev_controller);
+
static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master,
struct i3c_dev_desc *dev)
{
@@ -1781,12 +1802,10 @@ static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master,
return ret;
/* Do not attach the master device itself. */
- if (master->this != dev && master->ops->attach_i3c_dev) {
- ret = master->ops->attach_i3c_dev(dev);
- if (ret) {
- i3c_master_put_i3c_addrs(dev);
- return ret;
- }
+ ret = i3c_master_attach_i3c_dev_controller(dev);
+ if (ret) {
+ i3c_master_put_i3c_addrs(dev);
+ return ret;
}
list_add_tail(&dev->common.node, &master->bus.devs.i3c);
@@ -1794,6 +1813,28 @@ static int i3c_master_attach_i3c_dev(struct i3c_master_controller *master,
return 0;
}
+/**
+ * i3c_master_reattach_i3c_dev_controller() - Reattach controller device state
+ * @dev: I3C device descriptor
+ * @old_dyn_addr: Previous dynamic address
+ *
+ * Invoke the current controller's reattach callback without modifying the
+ * controller's address-slot state.
+ *
+ * Return: 0 on success, or a negative error code returned by the controller.
+ */
+int i3c_master_reattach_i3c_dev_controller(struct i3c_dev_desc *dev,
+ u8 old_dyn_addr)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev);
+
+ if (master->ops->reattach_i3c_dev)
+ return master->ops->reattach_i3c_dev(dev, old_dyn_addr);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(i3c_master_reattach_i3c_dev_controller);
+
/**
* i3c_master_reattach_i3c_dev_locked() - reattach an I3C device with a new address
* @dev: I3C device descriptor to reattach
@@ -1824,25 +1865,36 @@ int i3c_master_reattach_i3c_dev_locked(struct i3c_dev_desc *dev,
I3C_ADDR_SLOT_FREE);
}
- if (master->ops->reattach_i3c_dev) {
- ret = master->ops->reattach_i3c_dev(dev, old_dyn_addr);
- if (ret) {
- i3c_master_put_i3c_addrs(dev);
- return ret;
- }
+ ret = i3c_master_reattach_i3c_dev_controller(dev, old_dyn_addr);
+ if (ret) {
+ i3c_master_put_i3c_addrs(dev);
+ return ret;
}
return 0;
}
EXPORT_SYMBOL_GPL(i3c_master_reattach_i3c_dev_locked);
-static void i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev)
+/**
+ * i3c_master_detach_i3c_dev_controller() - Detach device state from controller
+ * @dev: I3C device descriptor
+ *
+ * Invoke the current controller's detach callback without releasing address
+ * slots or removing the device from the controller's device list.
+ */
+void i3c_master_detach_i3c_dev_controller(struct i3c_dev_desc *dev)
{
struct i3c_master_controller *master = i3c_dev_get_master(dev);
/* Do not detach the master device itself. */
if (master->this != dev && master->ops->detach_i3c_dev)
master->ops->detach_i3c_dev(dev);
+}
+EXPORT_SYMBOL_GPL(i3c_master_detach_i3c_dev_controller);
+
+static void i3c_master_detach_i3c_dev(struct i3c_dev_desc *dev)
+{
+ i3c_master_detach_i3c_dev_controller(dev);
i3c_master_put_i3c_addrs(dev);
list_del(&dev->common.node);
@@ -3201,6 +3253,26 @@ i3c_generic_ibi_alloc_pool(struct i3c_dev_desc *dev,
}
EXPORT_SYMBOL_GPL(i3c_generic_ibi_alloc_pool);
+/**
+ * i3c_dev_recycle_ibi_slot_controller() - Recycle an IBI slot through
+ * the current controller
+ * @dev: I3C device descriptor
+ * @slot: IBI slot to recycle
+ *
+ * Invoke the current controller's IBI slot recycling callback.
+ *
+ * The controller is responsible for synchronizing access to its IBI pool.
+ */
+void i3c_dev_recycle_ibi_slot_controller(struct i3c_dev_desc *dev,
+ struct i3c_ibi_slot *slot)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev);
+
+ if (master->ops->recycle_ibi_slot)
+ master->ops->recycle_ibi_slot(dev, slot);
+}
+EXPORT_SYMBOL_GPL(i3c_dev_recycle_ibi_slot_controller);
+
/**
* i3c_generic_ibi_get_free_slot() - Get a free slot from a generic IBI pool
* @pool: the pool to query an IBI slot on
@@ -3522,6 +3594,32 @@ int i3c_dev_do_xfers_locked(struct i3c_dev_desc *dev, struct i3c_xfer *xfers,
}
EXPORT_SYMBOL_GPL(i3c_dev_do_xfers_locked);
+/**
+ * i3c_dev_disable_ibi_controller_locked() - Disable IBI in the controller
+ * @dev: I3C device descriptor
+ *
+ * Invoke the current controller's IBI disable callback without waiting for
+ * pending IBIs or updating the generic IBI enabled state.
+ *
+ * Context: The caller must serialize access to @dev->ibi and the generic
+ * IBI lifecycle.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int i3c_dev_disable_ibi_controller_locked(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev);
+
+ if (!dev->ibi)
+ return -EINVAL;
+
+ if (!master->ops->disable_ibi)
+ return -EOPNOTSUPP;
+
+ return master->ops->disable_ibi(dev);
+}
+EXPORT_SYMBOL_GPL(i3c_dev_disable_ibi_controller_locked);
+
/**
* i3c_dev_disable_ibi_locked() - Disable IBIs coming from a specific device
* @dev: device on which IBIs should be disabled
@@ -3534,14 +3632,9 @@ EXPORT_SYMBOL_GPL(i3c_dev_do_xfers_locked);
*/
int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev)
{
- struct i3c_master_controller *master;
int ret;
- if (!dev->ibi)
- return -EINVAL;
-
- master = i3c_dev_get_master(dev);
- ret = master->ops->disable_ibi(dev);
+ ret = i3c_dev_disable_ibi_controller_locked(dev);
if (ret)
return ret;
@@ -3555,6 +3648,32 @@ int i3c_dev_disable_ibi_locked(struct i3c_dev_desc *dev)
}
EXPORT_SYMBOL_GPL(i3c_dev_disable_ibi_locked);
+/**
+ * i3c_dev_enable_ibi_controller_locked() - Enable controller IBI resources
+ * @dev: I3C device descriptor
+ *
+ * Invoke the current controller's IBI enable callback without updating the
+ * generic IBI enabled state.
+ *
+ * Context: The caller must serialize access to @dev->ibi and the generic
+ * IBI lifecycle.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int i3c_dev_enable_ibi_controller_locked(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev);
+
+ if (!dev->ibi)
+ return -EINVAL;
+
+ if (!master->ops->enable_ibi)
+ return -EOPNOTSUPP;
+
+ return master->ops->enable_ibi(dev);
+}
+EXPORT_SYMBOL_GPL(i3c_dev_enable_ibi_controller_locked);
+
/**
* i3c_dev_enable_ibi_locked() - Enable IBIs from a specific device (lock held)
* @dev: device on which IBIs should be enabled
@@ -3571,13 +3690,9 @@ EXPORT_SYMBOL_GPL(i3c_dev_disable_ibi_locked);
*/
int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev)
{
- struct i3c_master_controller *master = i3c_dev_get_master(dev);
int ret;
- if (!dev->ibi)
- return -EINVAL;
-
- ret = master->ops->enable_ibi(dev);
+ ret = i3c_dev_enable_ibi_controller_locked(dev);
if (!ret)
dev->ibi->enabled = true;
@@ -3585,6 +3700,35 @@ int i3c_dev_enable_ibi_locked(struct i3c_dev_desc *dev)
}
EXPORT_SYMBOL_GPL(i3c_dev_enable_ibi_locked);
+/**
+ * i3c_dev_request_ibi_controller_locked() - Request controller IBI resources
+ * @dev: I3C device descriptor
+ * @req: IBI setup request
+ *
+ * Invoke the current controller's IBI request callback without allocating the
+ * generic IBI object or workqueue. The caller must ensure that @dev->ibi has
+ * already been initialized.
+ *
+ * Context: The caller must serialize access to @dev->ibi and the generic
+ * IBI lifecycle.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+int i3c_dev_request_ibi_controller_locked(struct i3c_dev_desc *dev,
+ const struct i3c_ibi_setup *req)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev);
+
+ if (!dev->ibi)
+ return -EINVAL;
+
+ if (!master->ops->request_ibi)
+ return -EOPNOTSUPP;
+
+ return master->ops->request_ibi(dev, req);
+}
+EXPORT_SYMBOL_GPL(i3c_dev_request_ibi_controller_locked);
+
/**
* i3c_dev_request_ibi_locked() - Request an IBI
* @dev: device for which we should enable IBIs
@@ -3600,13 +3744,9 @@ EXPORT_SYMBOL_GPL(i3c_dev_enable_ibi_locked);
int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
const struct i3c_ibi_setup *req)
{
- struct i3c_master_controller *master = i3c_dev_get_master(dev);
struct i3c_device_ibi_info *ibi;
int ret;
- if (!master->ops->request_ibi)
- return -EOPNOTSUPP;
-
if (dev->ibi)
return -EBUSY;
@@ -3627,8 +3767,15 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
ibi->num_slots = req->num_slots;
dev->ibi = ibi;
- ret = master->ops->request_ibi(dev, req);
+ ret = i3c_dev_request_ibi_controller_locked(dev, req);
if (ret) {
+ /*
+ * The controller request callback failed, so tear down the
+ * workqueue allocated above before freeing the IBI object.
+ * This is the owner of the workqueue, so it must destroy it
+ * here to avoid leaking it on the error path.
+ */
+ destroy_workqueue(ibi->wq);
kfree(ibi);
dev->ibi = NULL;
}
@@ -3637,6 +3784,27 @@ int i3c_dev_request_ibi_locked(struct i3c_dev_desc *dev,
}
EXPORT_SYMBOL_GPL(i3c_dev_request_ibi_locked);
+/**
+ * i3c_dev_free_ibi_controller_locked() - Free controller IBI resources
+ * @dev: I3C device descriptor
+ *
+ * Invoke the current controller's IBI free callback without destroying the
+ * generic IBI workqueue or freeing @dev->ibi.
+ *
+ * Context: The caller must serialize access to @dev->ibi and the generic
+ * IBI lifecycle.
+ */
+void i3c_dev_free_ibi_controller_locked(struct i3c_dev_desc *dev)
+{
+ struct i3c_master_controller *master = i3c_dev_get_master(dev);
+
+ if (!dev->ibi)
+ return;
+
+ master->ops->free_ibi(dev);
+}
+EXPORT_SYMBOL_GPL(i3c_dev_free_ibi_controller_locked);
+
/**
* i3c_dev_free_ibi_locked() - Free all resources needed for IBI handling
* @dev: device on which you want to release IBI resources
@@ -3667,7 +3835,7 @@ void i3c_dev_free_ibi_locked(struct i3c_dev_desc *dev)
dev_err(&master->dev, "Failed to disable IBI before freeing\n");
}
- master->ops->free_ibi(dev);
+ i3c_dev_free_ibi_controller_locked(dev);
if (dev->ibi->wq) {
destroy_workqueue(dev->ibi->wq);
--
2.25.1