blob: 5390287680844a4da5ff555168640a8583d39929 [file]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2026 NXP
* Generic I3C Hub core implementing virtual controller operations.
*/
#include <linux/i3c/device.h>
#include <linux/i3c/hub.h>
#include <linux/lockdep.h>
#include "internals.h"
/**
* struct i3c_hub_dev_data - Per-downstream-device hub state
* @parent_desc: Permanent parent-facing descriptor whose master points at the
* physical parent controller, used to forward controller-specific
* operations there.
*
* The logical descriptor on the virtual hub bus keeps its master aimed at the
* virtual hub controller and is never modified. This separate descriptor lets
* the controller-only core helpers resolve the physical parent without racing
* concurrent readers on the virtual bus.
*/
struct i3c_hub_dev_data {
struct i3c_dev_desc parent_desc;
};
/*
* All i3c_bus rw_semaphores are initialized from a single call site in the
* I3C core, so lockdep assigns them one shared class. When a hub forwards an
* operation it takes the parent bus lock while already holding its own virtual
* bus lock, which lockdep then reports as recursive locking on that shared
* class. The bus maintenance and normal-use helpers use plain down_write() and
* down_read(), which always acquire with subclass 0, so lockdep_set_subclass()
* cannot separate them; a distinct lock_class_key per nesting level is used
* instead.
*
* A top-level hub uses depth 1, a hub behind another hub uses depth 2, and so
* on, so a virtual bus lock never shares a class with the parent bus lock it
* nests under. Sibling ports on the same hub share a class, which is safe
* because they are never nested against each other. The array must stay a
* file-local definition: lockdep keys are identified by their address, so a
* single set of unique objects is required.
*
* The depth bound is generous; exceeding it only loses lockdep coverage, not
* correctness.
*/
#define I3C_HUB_MAX_LOCK_DEPTH 8
static struct lock_class_key i3c_hub_bus_lock_keys[I3C_HUB_MAX_LOCK_DEPTH];
/*
* The hub routing mutex (hub->lock) serializes port switching and forwarding.
* A child hub holds its routing mutex while reaching a parent hub that takes
* its own, so it needs the same per-depth lock_class_key treatment as the bus
* lock above, keyed identically (top-level hub depth 1, and so on). The class
* is assigned once in i3c_hub_init(), not per port, because all ports on a hub
* share this single routing mutex.
*/
static struct lock_class_key i3c_hub_routing_lock_keys[I3C_HUB_MAX_LOCK_DEPTH];
/**
* i3c_hub_controller_depth() - Count hub nesting levels above a controller
* @controller: Virtual hub controller being initialized
*
* Walk the parent chain and count how many stacked hub controllers lead to
* @controller. A top-level hub attached to a physical controller returns 1.
* The walk stops at the first non-hub (physical) controller.
*
* Return: The hub nesting depth (>= 1 for a hub controller).
*/
static unsigned int
i3c_hub_controller_depth(struct i3c_master_controller *controller)
{
struct i3c_hub_controller *hub_controller;
unsigned int depth = 0;
while (controller && controller->ops == &i3c_hub_master_ops) {
hub_controller = dev_get_drvdata(&controller->dev);
if (!hub_controller)
break;
controller = hub_controller->parent;
depth++;
}
return depth;
}
/**
* i3c_hub_master_bus_init() - Bind controller to hub device
* @controller: Virtual controller for a hub port
*
* Associates the virtual controller with the hub device descriptor so that
* transfers are executed through the hub on the parent bus.
*/
static int i3c_hub_master_bus_init(struct i3c_master_controller *controller)
{
struct i3c_hub_controller *hub_controller;
struct i3c_hub *hub;
unsigned int depth;
hub_controller = dev_get_drvdata(&controller->dev);
if (!hub_controller || !hub_controller->hub)
return -ENODEV;
hub = hub_controller->hub;
if (!hub->hub_dev)
return -ENODEV;
/*
* Give this virtual bus lock a lockdep class keyed on its hub nesting
* depth before the core runs the first DAA (which forwards to the
* parent bus and takes the parent lock while this one is held). The
* lock is not held here, and controller->ops is already set, so the
* class can be assigned safely. Deeper hubs than the key array
* supports fall back to the shared class and may warn under lockdep,
* but still function correctly.
*/
depth = i3c_hub_controller_depth(controller);
if (depth >= 1 && depth <= I3C_HUB_MAX_LOCK_DEPTH)
lockdep_set_class(&controller->bus.lock,
&i3c_hub_bus_lock_keys[depth - 1]);
else
WARN_ONCE(1, "i3c-hub: nesting depth %u exceeds lockdep support\n",
depth);
controller->this = hub->hub_dev->desc;
return 0;
}
static void i3c_hub_master_bus_cleanup(struct i3c_master_controller *controller)
{
controller->this = NULL;
}
static int i3c_hub_attach_i3c_dev(struct i3c_dev_desc *dev)
{
struct i3c_master_controller *controller = i3c_dev_get_master(dev);
struct i3c_hub_controller *hub_controller;
struct i3c_hub_dev_data *data;
struct i3c_master_controller *parent;
struct i3c_hub *hub;
int ret;
hub_controller = dev_get_drvdata(&controller->dev);
if (!hub_controller || !hub_controller->hub)
return -ENODEV;
hub = hub_controller->hub;
if (!hub->hub_dev)
return -ENODEV;
parent = i3c_dev_get_master(hub->hub_dev->desc);
if (!parent)
return -ENODEV;
data = kzalloc_obj(*data);
if (!data)
return -ENOMEM;
/* Initialize the parent-facing descriptor to target the physical parent. */
INIT_LIST_HEAD(&data->parent_desc.common.node);
mutex_init(&data->parent_desc.ibi_lock);
data->parent_desc.common.master = parent;
data->parent_desc.info = dev->info;
i3c_bus_maintenance_lock(&parent->bus);
ret = i3c_master_attach_i3c_dev_controller_locked(&data->parent_desc);
i3c_bus_maintenance_unlock(&parent->bus);
if (ret) {
mutex_destroy(&data->parent_desc.ibi_lock);
kfree(data);
return ret;
}
/* Link the hub-private data (see struct i3c_hub_dev_data). */
i3c_dev_set_master_data(dev, data);
return 0;
}
static int i3c_hub_reattach_i3c_dev(struct i3c_dev_desc *dev,
u8 old_dyn_addr)
{
struct i3c_hub_dev_data *data = i3c_dev_get_master_data(dev);
struct i3c_master_controller *parent;
int ret;
if (!data)
return -ENODEV;
parent = i3c_dev_get_master(&data->parent_desc);
if (!parent)
return -ENODEV;
/*
* Reattach must not race asynchronous IBI delivery on the parent-facing
* descriptor. Once IBI resources are requested the parent controller may
* use parent_desc concurrently, so reject a reattach that arrives while
* the generic IBI object is still live.
*/
if (WARN_ON_ONCE(data->parent_desc.ibi))
return -EBUSY;
/*
* Re-sync device information after the address change and reattach
* under the parent bus lock so both updates are applied as one
* operation with respect to parent controller state.
*/
i3c_bus_maintenance_lock(&parent->bus);
data->parent_desc.info = dev->info;
ret = i3c_master_reattach_i3c_dev_controller_locked(&data->parent_desc,
old_dyn_addr);
i3c_bus_maintenance_unlock(&parent->bus);
return ret;
}
static void i3c_hub_detach_i3c_dev(struct i3c_dev_desc *dev)
{
struct i3c_hub_dev_data *data = i3c_dev_get_master_data(dev);
struct i3c_master_controller *parent;
if (!data)
return;
parent = i3c_dev_get_master(&data->parent_desc);
/*
* parent_desc.ibi should already be cleared by i3c_hub_free_ibi()
* before we get here. If it is still set, the kfree(data) below frees
* a descriptor the parent controller can still reach via un-flushed
* asynchronous IBI work (use-after-free, not just a leak).
*/
WARN_ON_ONCE(data->parent_desc.ibi);
if (parent) {
i3c_bus_maintenance_lock(&parent->bus);
i3c_master_detach_i3c_dev_controller_locked(&data->parent_desc);
i3c_bus_maintenance_unlock(&parent->bus);
}
i3c_dev_set_master_data(dev, NULL);
mutex_destroy(&data->parent_desc.ibi_lock);
kfree(data);
}
/**
* i3c_hub_do_daa() - Perform DAA via hub port
* @hub: Hub instance
* @controller: Virtual controller for a hub port
*
* Enables the port connection, performs DAA on the parent controller,
* then disables the connection.
*/
static int i3c_hub_do_daa(struct i3c_hub *hub,
struct i3c_master_controller *controller)
{
struct i3c_master_controller *parent;
int ret;
if (!hub || !hub->hub_dev)
return -ENODEV;
parent = i3c_dev_get_master(hub->hub_dev->desc);
if (!parent)
return -ENODEV;
/*
* Lock order: hub routing mutex before the parent bus lock (taken here
* inside i3c_master_do_daa()). The depth-keyed lockdep classes above
* keep this nesting acyclic when the parent is itself a hub.
*/
mutex_lock(&hub->lock);
i3c_hub_enable_port(controller);
/*
* Downstream devices reachable through hub target-port routes share the
* parent controller's I3C address space. The hub gates access to a
* target-port network, but it does not create an independent dynamic
* address domain per virtual bus.
*
* Run DAA on the parent controller so dynamic addresses remain unique
* across all downstream devices, even when they are behind different
* target ports.
*
* The current hub model shares the parent controller's dynamic-address
* domain and does not support independent per-port DAA domains, so a
* DAA result does not go stale behind a port toggle. Addresses reserved
* from the Device Tree are held for the lifetime of the parent bus and
* are never released on a port toggle, so enabling or disabling a port
* only gates reachability and never causes an address to be handed out
* twice.
*/
ret = i3c_master_do_daa(parent);
i3c_hub_disable_port(controller);
mutex_unlock(&hub->lock);
return ret;
}
static bool i3c_hub_supports_ccc_cmd(struct i3c_hub *hub,
const struct i3c_ccc_cmd *cmd)
{
struct i3c_master_controller *parent;
if (!hub || !hub->hub_dev)
return false;
parent = i3c_dev_get_master(hub->hub_dev->desc);
if (!parent)
return false;
return i3c_master_supports_ccc_cmd(parent, cmd);
}
/**
* i3c_hub_send_ccc_cmd() - Send CCC through hub port
* @hub: Hub instance
* @controller: Virtual controller
* @cmd: CCC command
*
* Enables the port connection while issuing CCC on the parent controller.
*
* A hub does not isolate the CCC address space of its virtual buses from the
* parent bus. A broadcast CCC issued on one virtual bus is forwarded to the
* parent controller and therefore reaches every device on the parent bus, as
* well as the downstream devices of any other connected target port. Drivers
* that need per-device scope must use the directed form of the command.
*/
static int i3c_hub_send_ccc_cmd(struct i3c_hub *hub,
struct i3c_master_controller *controller,
struct i3c_ccc_cmd *cmd)
{
struct i3c_master_controller *parent;
int ret;
if (!hub || !hub->hub_dev)
return -ENODEV;
parent = i3c_dev_get_master(hub->hub_dev->desc);
if (!parent)
return -ENODEV;
mutex_lock(&hub->lock);
i3c_hub_enable_port(controller);
ret = i3c_master_send_ccc_cmd(parent, cmd);
i3c_hub_disable_port(controller);
mutex_unlock(&hub->lock);
return ret;
}
/**
* i3c_hub_master_priv_xfers() - Execute private transfers via hub
* @dev: Target device descriptor
* @xfers: Transfer array
* @nxfers: Number of transfers
* @mode: Transfer mode (SDR, HDR, etc.)
*
* Refreshes the parent-facing device info (while no IBI is pending) and
* forwards private transfers through the hub to the parent controller.
*/
static int i3c_hub_master_priv_xfers(struct i3c_dev_desc *dev,
struct i3c_xfer *xfers,
int nxfers,
enum i3c_xfer_mode mode)
{
struct i3c_master_controller *controller = i3c_dev_get_master(dev);
struct i3c_hub_controller *hub_controller;
struct i3c_master_controller *parent;
struct i3c_hub_dev_data *data;
struct i3c_hub *hub;
int ret;
hub_controller = dev_get_drvdata(&controller->dev);
if (!hub_controller || !hub_controller->hub)
return -ENODEV;
hub = hub_controller->hub;
data = i3c_dev_get_master_data(dev);
if (!data)
return -ENODEV;
parent = i3c_dev_get_master(&data->parent_desc);
if (!parent)
return -ENODEV;
/* Lock order: hub routing mutex before the parent bus lock (see do_daa). */
mutex_lock(&hub->lock);
/*
* Only refresh the parent-facing info while no IBI is requested; once
* parent_desc.ibi is set it must stay immutable (see i3c_hub_request_ibi()).
*/
if (!data->parent_desc.ibi)
data->parent_desc.info = dev->info;
i3c_hub_enable_port(controller);
i3c_bus_normaluse_lock(&parent->bus);
ret = i3c_dev_do_xfers_locked(&data->parent_desc, xfers,
nxfers, mode);
i3c_bus_normaluse_unlock(&parent->bus);
i3c_hub_disable_port(controller);
mutex_unlock(&hub->lock);
return ret;
}
static int i3c_hub_attach_i2c_dev(struct i2c_dev_desc *dev)
{
return -EOPNOTSUPP;
}
static void i3c_hub_detach_i2c_dev(struct i2c_dev_desc *dev)
{
}
static int i3c_hub_i2c_xfers(struct i2c_dev_desc *dev,
struct i2c_msg *xfers, int nxfers)
{
return -EOPNOTSUPP;
}
static int i3c_hub_master_do_daa(struct i3c_master_controller *controller)
{
struct i3c_hub_controller *hub_controller;
struct i3c_hub *hub;
hub_controller = dev_get_drvdata(&controller->dev);
if (!hub_controller || !hub_controller->hub)
return -ENODEV;
hub = hub_controller->hub;
return i3c_hub_do_daa(hub, controller);
}
static int i3c_hub_master_send_ccc_cmd(struct i3c_master_controller *controller,
struct i3c_ccc_cmd *cmd)
{
struct i3c_hub_controller *hub_controller;
struct i3c_hub *hub;
hub_controller = dev_get_drvdata(&controller->dev);
if (!hub_controller || !hub_controller->hub)
return -ENODEV;
hub = hub_controller->hub;
if (!hub->hub_dev)
return -ENODEV;
/*
* Do not forward broadcast RSTDAA through the hub. The hub itself
* is visible on the parent bus, so forwarding RSTDAA would also
* reset the hub dynamic address. Downstream RSTDAA is not supported
* by the hub virtual-controller model.
*/
if (cmd->id == I3C_CCC_RSTDAA(true))
return 0;
return i3c_hub_send_ccc_cmd(hub, controller, cmd);
}
static bool i3c_hub_master_supports_ccc_cmd(struct i3c_master_controller *controller,
const struct i3c_ccc_cmd *cmd)
{
struct i3c_hub_controller *hub_controller;
struct i3c_hub *hub;
hub_controller = dev_get_drvdata(&controller->dev);
if (!hub_controller || !hub_controller->hub)
return false;
hub = hub_controller->hub;
return i3c_hub_supports_ccc_cmd(hub, cmd);
}
/**
* i3c_hub_request_ibi() - Request IBI through parent controller
* @desc: Target device descriptor
* @req: IBI setup
*
* Publishes the generic IBI object on the permanent parent-facing descriptor
* and requests IBI for a device connected through the hub. The parent-facing
* descriptor references the same IBI object so the physical controller uses
* the logical workqueue, pending counter and client device during
* asynchronous IBI delivery.
*/
static int i3c_hub_request_ibi(struct i3c_dev_desc *desc,
const struct i3c_ibi_setup *req)
{
struct i3c_master_controller *controller = i3c_dev_get_master(desc);
struct i3c_hub_controller *hub_controller;
struct i3c_master_controller *parent;
struct i3c_hub_dev_data *data;
struct i3c_hub *hub;
int ret;
hub_controller = dev_get_drvdata(&controller->dev);
if (!hub_controller || !hub_controller->hub)
return -ENODEV;
hub = hub_controller->hub;
data = i3c_dev_get_master_data(desc);
if (!data)
return -ENODEV;
parent = i3c_dev_get_master(&data->parent_desc);
if (!parent)
return -ENODEV;
/*
* Publish the final device information snapshot together with the
* generic IBI object under hub->lock. Keep the parent-facing
* information immutable while parent_desc.ibi is set and the parent
* controller may use the descriptor asynchronously.
*/
mutex_lock(&hub->lock);
data->parent_desc.info = desc->info;
data->parent_desc.dev = desc->dev;
data->parent_desc.ibi = desc->ibi;
mutex_unlock(&hub->lock);
i3c_bus_normaluse_lock(&parent->bus);
ret = i3c_dev_request_ibi_controller_locked(&data->parent_desc, req);
i3c_bus_normaluse_unlock(&parent->bus);
if (ret) {
mutex_lock(&hub->lock);
data->parent_desc.ibi = NULL;
data->parent_desc.dev = NULL;
mutex_unlock(&hub->lock);
}
return ret;
}
static void i3c_hub_free_ibi(struct i3c_dev_desc *desc)
{
struct i3c_master_controller *controller = i3c_dev_get_master(desc);
struct i3c_hub_controller *hub_controller;
struct i3c_master_controller *parent;
struct i3c_hub_dev_data *data;
struct i3c_hub *hub;
hub_controller = dev_get_drvdata(&controller->dev);
if (!hub_controller || !hub_controller->hub)
return;
hub = hub_controller->hub;
data = i3c_dev_get_master_data(desc);
if (!data || !data->parent_desc.ibi)
return;
parent = i3c_dev_get_master(&data->parent_desc);
if (!parent)
return;
i3c_bus_normaluse_lock(&parent->bus);
i3c_dev_free_ibi_controller_locked(&data->parent_desc);
i3c_bus_normaluse_unlock(&parent->bus);
/*
* The outer generic IBI free path owns and releases desc->ibi after
* this callback returns.
*/
mutex_lock(&hub->lock);
data->parent_desc.ibi = NULL;
data->parent_desc.dev = NULL;
mutex_unlock(&hub->lock);
}
/**
* i3c_hub_enable_ibi() - Enable IBI via hub port
* @desc: Target device descriptor
*
* Enables port connection and forwards the IBI enable request to the parent
* controller.
*/
static int i3c_hub_enable_ibi(struct i3c_dev_desc *desc)
{
struct i3c_master_controller *controller = i3c_dev_get_master(desc);
struct i3c_hub_controller *hub_controller;
struct i3c_master_controller *parent;
struct i3c_hub_dev_data *data;
struct i3c_hub *hub;
int ret;
hub_controller = dev_get_drvdata(&controller->dev);
if (!hub_controller || !hub_controller->hub)
return -ENODEV;
hub = hub_controller->hub;
data = i3c_dev_get_master_data(desc);
if (!data || !data->parent_desc.ibi)
return -ENODEV;
parent = i3c_dev_get_master(&data->parent_desc);
if (!parent)
return -ENODEV;
mutex_lock(&hub->lock);
i3c_hub_enable_port(controller);
i3c_bus_maintenance_lock(&parent->bus);
ret = i3c_dev_enable_ibi_controller_locked(&data->parent_desc);
i3c_bus_maintenance_unlock(&parent->bus);
i3c_hub_disable_port(controller);
mutex_unlock(&hub->lock);
return ret;
}
/**
* i3c_hub_disable_ibi() - Disable IBI via hub port
* @desc: Target device descriptor
*
* Enables port connection and forwards the IBI disable request to the parent
* controller.
*/
static int i3c_hub_disable_ibi(struct i3c_dev_desc *desc)
{
struct i3c_master_controller *controller = i3c_dev_get_master(desc);
struct i3c_hub_controller *hub_controller;
struct i3c_master_controller *parent;
struct i3c_hub_dev_data *data;
struct i3c_hub *hub;
int ret;
hub_controller = dev_get_drvdata(&controller->dev);
if (!hub_controller || !hub_controller->hub)
return -ENODEV;
hub = hub_controller->hub;
data = i3c_dev_get_master_data(desc);
if (!data || !data->parent_desc.ibi)
return -ENODEV;
parent = i3c_dev_get_master(&data->parent_desc);
if (!parent)
return -ENODEV;
mutex_lock(&hub->lock);
i3c_hub_enable_port(controller);
i3c_bus_maintenance_lock(&parent->bus);
ret = i3c_dev_disable_ibi_controller_locked(&data->parent_desc);
i3c_bus_maintenance_unlock(&parent->bus);
i3c_hub_disable_port(controller);
mutex_unlock(&hub->lock);
return ret;
}
static void i3c_hub_recycle_ibi_slot(struct i3c_dev_desc *desc,
struct i3c_ibi_slot *slot)
{
struct i3c_hub_dev_data *data = i3c_dev_get_master_data(desc);
if (!data)
return;
i3c_dev_recycle_ibi_slot_controller(&data->parent_desc, slot);
}
const struct i3c_master_controller_ops i3c_hub_master_ops = {
.bus_init = i3c_hub_master_bus_init,
.bus_cleanup = i3c_hub_master_bus_cleanup,
.attach_i3c_dev = i3c_hub_attach_i3c_dev,
.reattach_i3c_dev = i3c_hub_reattach_i3c_dev,
.detach_i3c_dev = i3c_hub_detach_i3c_dev,
.do_daa = i3c_hub_master_do_daa,
.supports_ccc_cmd = i3c_hub_master_supports_ccc_cmd,
.send_ccc_cmd = i3c_hub_master_send_ccc_cmd,
.i3c_xfers = i3c_hub_master_priv_xfers,
.attach_i2c_dev = i3c_hub_attach_i2c_dev,
.detach_i2c_dev = i3c_hub_detach_i2c_dev,
.i2c_xfers = i3c_hub_i2c_xfers,
.request_ibi = i3c_hub_request_ibi,
.free_ibi = i3c_hub_free_ibi,
.enable_ibi = i3c_hub_enable_ibi,
.disable_ibi = i3c_hub_disable_ibi,
.recycle_ibi_slot = i3c_hub_recycle_ibi_slot,
};
EXPORT_SYMBOL_GPL(i3c_hub_master_ops);
/**
* i3c_hub_set_routing_lock_class() - Key the routing mutex for lockdep
* @hub: Hub instance whose routing mutex has just been initialized
*
* Give @hub->lock a lockdep class keyed on the hub nesting depth, so that a
* child hub taking its routing mutex while a parent hub holds its own is not
* reported as recursive locking. Does nothing when lockdep is disabled.
*/
static void i3c_hub_set_routing_lock_class(struct i3c_hub *hub)
{
struct i3c_master_controller *parent;
unsigned int depth;
if (!IS_ENABLED(CONFIG_LOCKDEP))
return;
/*
* The routing mutex has the same hub nesting depth as the virtual
* controllers this hub exposes, so the parent controller is one level
* shallower. Keying it once here, rather than per port, avoids
* reclassifying the single shared routing mutex from a later port that
* may already have used it.
*/
parent = i3c_dev_get_master(hub->hub_dev->desc);
depth = i3c_hub_controller_depth(parent) + 1;
if (WARN_ONCE(depth > I3C_HUB_MAX_LOCK_DEPTH,
"i3c-hub: routing lock depth %u exceeds lockdep support\n",
depth))
depth = I3C_HUB_MAX_LOCK_DEPTH;
lockdep_set_class(&hub->lock, &i3c_hub_routing_lock_keys[depth - 1]);
}
/**
* i3c_hub_init() - Initialize hub context
* @hub: Hub instance to initialize
* @ops: Vendor callbacks used to connect and disconnect target ports
* @hub_dev: I3C device representing the hub on the parent bus
*
* Store the vendor callbacks and the hub device in @hub and initialize the
* routing mutex used to serialize port switching and forwarding.
*
* @hub_dev must be a registered I3C device, so it already has a descriptor
* and a parent controller.
*/
void i3c_hub_init(struct i3c_hub *hub,
const struct i3c_hub_ops *ops,
struct i3c_device *hub_dev)
{
hub->ops = ops;
hub->hub_dev = hub_dev;
mutex_init(&hub->lock);
i3c_hub_set_routing_lock_class(hub);
}
EXPORT_SYMBOL_GPL(i3c_hub_init);
/**
* i3c_hub_reserve_parent_addrslots_from_dt() - Reserve child addresses in parent bus.
* @hubc: I3C hub controller for a target-port virtual bus.
* @node: Target-port bus Device Tree node.
*
* Reserve parent bus address slots for the assigned dynamic address of each
* downstream I3C device described in the Device Tree, so that parent DAA does
* not hand the same address to another device. Downstream devices behind hub
* target ports share the parent controller's dynamic address space.
*
* The reservations describe a fixed Device Tree topology and are kept for the
* lifetime of the parent bus; they are not released when a target port is
* disconnected. If the hub is removed and probed again it re-reads the same
* Device Tree and reserves the same addresses, so repeated probing does not
* consume additional address space.
*
* Only the assigned dynamic addresses are reserved here. A hub that supports
* hot-joining downstream devices would additionally have to reserve the static
* addresses of every device that may appear. Supporting runtime hub hotplug
* would also require tracking ownership of these reservations and releasing
* them when a hub is removed.
*
* Return: 0 on success, or a negative error code.
*/
int i3c_hub_reserve_parent_addrslots_from_dt(struct i3c_hub_controller *hubc,
struct device_node *node)
{
struct i3c_master_controller *parent = hubc->parent;
enum i3c_addr_slot_status status;
u32 assigned_addr;
u32 reg[3];
int ret;
if (!parent || !node)
return -ENODEV;
for_each_available_child_of_node_scoped(node, child) {
/*
* Only consider addressable bus nodes: a valid "reg" is
* required to describe a device, but its static-address value
* does not affect the reservation below.
*/
ret = of_property_read_variable_u32_array(child, "reg", reg, 1, 3);
if (ret < 0)
continue;
ret = of_property_read_u32(child, "assigned-address", &assigned_addr);
if (ret)
continue;
/* Skip nodes without a usable dynamic address. */
if (!assigned_addr || assigned_addr > I3C_MAX_ADDR)
continue;
i3c_bus_maintenance_lock(&parent->bus);
status = i3c_bus_get_addr_slot_status(&parent->bus,
assigned_addr);
if (status == I3C_ADDR_SLOT_FREE)
i3c_bus_set_addr_slot_status(&parent->bus,
assigned_addr,
I3C_ADDR_SLOT_I3C_DEV);
i3c_bus_maintenance_unlock(&parent->bus);
}
return 0;
}
EXPORT_SYMBOL_GPL(i3c_hub_reserve_parent_addrslots_from_dt);
MODULE_AUTHOR("Aman Kumar Pandey <aman.kumarpandey@nxp.com>");
MODULE_AUTHOR("Vikash Bansal <vikash.bansal@nxp.com>");
MODULE_AUTHOR("Lakshay Piplani <lakshay.piplani@nxp.com>");
MODULE_DESCRIPTION("Generic I3C hub support");
MODULE_LICENSE("GPL");