* [PATCH 1/8] i3c: master: Make hot-join workqueue freezable to block hot-join during suspend
2026-05-12 12:17 [PATCH 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
@ 2026-05-12 12:17 ` Adrian Hunter
2026-05-12 16:09 ` Frank Li
2026-05-12 12:17 ` [PATCH 2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock Adrian Hunter
` (6 subsequent siblings)
7 siblings, 1 reply; 24+ messages in thread
From: Adrian Hunter @ 2026-05-12 12:17 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, linux-i3c, linux-kernel
The I3C master workqueue (master->wq) is used to defer work that needs
thread context and the bus maintenance lock, most notably Hot Join
processing (which calls i3c_master_do_daa() to assign dynamic addresses
to newly joined devices).
Currently the workqueue keeps running across system suspend, which can
race with the suspend path:
- do_daa() may execute after the controller has been suspended,
issuing bus transactions on a powered-down or otherwise unusable
controller.
- New I3C devices can be enumerated and added to the bus mid-suspend,
registering driver model objects at a point where the I3C subsystem
and its consumers are not prepared to handle them.
Mark the workqueue WQ_FREEZABLE so its workers are frozen for the
duration of system suspend/hibernate and resumed afterwards. This
naturally defers any pending or newly queued Hot Join work until the
system (and the controller) is fully resumed, closing both races
without adding explicit suspend/resume synchronization in the master
drivers.
Update the kerneldoc for struct i3c_master_controller::wq to reflect
that the workqueue is freezable.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
drivers/i3c/master.c | 2 +-
include/linux/i3c/master.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 5cd4e5da2233..ab11e2d79aab 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -3079,7 +3079,7 @@ int i3c_master_register(struct i3c_master_controller *master,
if (ret)
goto err_put_dev;
- master->wq = alloc_workqueue("%s", WQ_PERCPU, 0, dev_name(parent));
+ master->wq = alloc_workqueue("%s", WQ_PERCPU | WQ_FREEZABLE, 0, dev_name(parent));
if (!master->wq) {
ret = -ENOMEM;
goto err_put_dev;
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index 592b646f6134..e6112e5f6608 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -515,7 +515,7 @@ struct i3c_master_controller_ops {
* @boardinfo.i2c: list of I2C boardinfo objects
* @boardinfo: board-level information attached to devices connected on the bus
* @bus: I3C bus exposed by this master
- * @wq: workqueue which can be used by master
+ * @wq: freezable workqueue which can be used by master
* drivers if they need to postpone operations that need to take place
* in a thread context. Typical examples are Hot Join processing which
* requires taking the bus lock in maintenance, which in turn, can only
--
2.51.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH 1/8] i3c: master: Make hot-join workqueue freezable to block hot-join during suspend
2026-05-12 12:17 ` [PATCH 1/8] i3c: master: Make hot-join workqueue freezable to block hot-join during suspend Adrian Hunter
@ 2026-05-12 16:09 ` Frank Li
0 siblings, 0 replies; 24+ messages in thread
From: Frank Li @ 2026-05-12 16:09 UTC (permalink / raw)
To: Adrian Hunter; +Cc: alexandre.belloni, linux-i3c, linux-kernel
On Tue, May 12, 2026 at 03:17:25PM +0300, Adrian Hunter wrote:
> The I3C master workqueue (master->wq) is used to defer work that needs
> thread context and the bus maintenance lock, most notably Hot Join
> processing (which calls i3c_master_do_daa() to assign dynamic addresses
> to newly joined devices).
>
> Currently the workqueue keeps running across system suspend, which can
> race with the suspend path:
>
> - do_daa() may execute after the controller has been suspended,
> issuing bus transactions on a powered-down or otherwise unusable
> controller.
> - New I3C devices can be enumerated and added to the bus mid-suspend,
> registering driver model objects at a point where the I3C subsystem
> and its consumers are not prepared to handle them.
>
> Mark the workqueue WQ_FREEZABLE so its workers are frozen for the
> duration of system suspend/hibernate and resumed afterwards. This
> naturally defers any pending or newly queued Hot Join work until the
> system (and the controller) is fully resumed, closing both races
> without adding explicit suspend/resume synchronization in the master
> drivers.
>
> Update the kerneldoc for struct i3c_master_controller::wq to reflect
> that the workqueue is freezable.
>
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock
2026-05-12 12:17 [PATCH 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
2026-05-12 12:17 ` [PATCH 1/8] i3c: master: Make hot-join workqueue freezable to block hot-join during suspend Adrian Hunter
@ 2026-05-12 12:17 ` Adrian Hunter
2026-05-12 16:11 ` Frank Li
2026-05-12 12:17 ` [PATCH 3/8] i3c: master: Consolidate Hot-Join DAA work in the core Adrian Hunter
` (5 subsequent siblings)
7 siblings, 1 reply; 24+ messages in thread
From: Adrian Hunter @ 2026-05-12 12:17 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, linux-i3c, linux-kernel
i3c_set_hotjoin() dispatches the controller's enable_hotjoin() or
disable_hotjoin() op and updates master->hotjoin under
i3c_bus_normaluse_lock(). That lock is a read-side acquisition of
bus->lock (down_read()), so it does not exclude concurrent callers.
The hotjoin sysfs attribute can be opened multiple times, and writes
through different opens are not serialized. Two concurrent writers
to "hotjoin" can therefore race in i3c_set_hotjoin(), with the
controller op and the master->hotjoin store from one call interleaving
with the other. The hardware enable/disable state and the value reported
by hotjoin_show() can end up out of sync.
Take i3c_bus_maintenance_lock() instead. Toggling Hot Join enable
changes bus state and is conceptually a maintenance operation, so the
write-side acquisition of bus->lock is the appropriate lock and
serializes concurrent callers against each other and against other
maintenance operations.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
drivers/i3c/master.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index ab11e2d79aab..38ffc8713167 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -649,7 +649,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
return ret;
}
- i3c_bus_normaluse_lock(&master->bus);
+ i3c_bus_maintenance_lock(&master->bus);
if (enable)
ret = master->ops->enable_hotjoin(master);
@@ -659,7 +659,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
if (!ret)
master->hotjoin = enable;
- i3c_bus_normaluse_unlock(&master->bus);
+ i3c_bus_maintenance_unlock(&master->bus);
if ((enable && ret) || (!enable && !ret) || master->rpm_ibi_allowed)
i3c_master_rpm_put(master);
--
2.51.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH 2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock
2026-05-12 12:17 ` [PATCH 2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock Adrian Hunter
@ 2026-05-12 16:11 ` Frank Li
2026-05-12 19:42 ` David Nyström
2026-05-13 5:09 ` Adrian Hunter
0 siblings, 2 replies; 24+ messages in thread
From: Frank Li @ 2026-05-12 16:11 UTC (permalink / raw)
To: Adrian Hunter; +Cc: alexandre.belloni, linux-i3c, linux-kernel
On Tue, May 12, 2026 at 03:17:26PM +0300, Adrian Hunter wrote:
> i3c_set_hotjoin() dispatches the controller's enable_hotjoin() or
> disable_hotjoin() op and updates master->hotjoin under
> i3c_bus_normaluse_lock(). That lock is a read-side acquisition of
> bus->lock (down_read()), so it does not exclude concurrent callers.
>
> The hotjoin sysfs attribute can be opened multiple times, and writes
> through different opens are not serialized. Two concurrent writers
> to "hotjoin" can therefore race in i3c_set_hotjoin(), with the
> controller op and the master->hotjoin store from one call interleaving
> with the other. The hardware enable/disable state and the value reported
> by hotjoin_show() can end up out of sync.
>
> Take i3c_bus_maintenance_lock() instead. Toggling Hot Join enable
> changes bus state and is conceptually a maintenance operation, so the
> write-side acquisition of bus->lock is the appropriate lock and
> serializes concurrent callers against each other and against other
> maintenance operations.
It should be bug fix, add fix tag here.
Frank
>
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
> drivers/i3c/master.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
> index ab11e2d79aab..38ffc8713167 100644
> --- a/drivers/i3c/master.c
> +++ b/drivers/i3c/master.c
> @@ -649,7 +649,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
> return ret;
> }
>
> - i3c_bus_normaluse_lock(&master->bus);
> + i3c_bus_maintenance_lock(&master->bus);
>
> if (enable)
> ret = master->ops->enable_hotjoin(master);
> @@ -659,7 +659,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
> if (!ret)
> master->hotjoin = enable;
>
> - i3c_bus_normaluse_unlock(&master->bus);
> + i3c_bus_maintenance_unlock(&master->bus);
>
> if ((enable && ret) || (!enable && !ret) || master->rpm_ibi_allowed)
> i3c_master_rpm_put(master);
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock
2026-05-12 16:11 ` Frank Li
@ 2026-05-12 19:42 ` David Nyström
2026-05-13 5:01 ` Adrian Hunter
2026-05-13 5:09 ` Adrian Hunter
1 sibling, 1 reply; 24+ messages in thread
From: David Nyström @ 2026-05-12 19:42 UTC (permalink / raw)
To: Frank Li; +Cc: Adrian Hunter, alexandre.belloni, linux-i3c, linux-kernel
On Tue, 12 May 2026, Frank Li wrote:
> On Tue, May 12, 2026 at 03:17:26PM +0300, Adrian Hunter wrote:
>> i3c_set_hotjoin() dispatches the controller's enable_hotjoin() or
>> disable_hotjoin() op and updates master->hotjoin under
>> i3c_bus_normaluse_lock(). That lock is a read-side acquisition of
>> bus->lock (down_read()), so it does not exclude concurrent callers.
>>
>> The hotjoin sysfs attribute can be opened multiple times, and writes
>> through different opens are not serialized. Two concurrent writers
>> to "hotjoin" can therefore race in i3c_set_hotjoin(), with the
>> controller op and the master->hotjoin store from one call interleaving
>> with the other. The hardware enable/disable state and the value reported
>> by hotjoin_show() can end up out of sync.
>>
>> Take i3c_bus_maintenance_lock() instead. Toggling Hot Join enable
>> changes bus state and is conceptually a maintenance operation, so the
>> write-side acquisition of bus->lock is the appropriate lock and
>> serializes concurrent callers against each other and against other
>> maintenance operations.
>
> It should be bug fix, add fix tag here.
Agreed, Fixes: "i3c: master: Add sysfs option to rescan bus via entdaa"
Is this series headed for 7.1-rc3 ? if not, its probably wise to revert
the sysfs addition from 7.1-rc
> Frank
>>
>> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
>> ---
>> drivers/i3c/master.c | 4 ++--
>> 1 file changed, 2 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
>> index ab11e2d79aab..38ffc8713167 100644
>> --- a/drivers/i3c/master.c
>> +++ b/drivers/i3c/master.c
>> @@ -649,7 +649,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
>> return ret;
>> }
>>
>> - i3c_bus_normaluse_lock(&master->bus);
>> + i3c_bus_maintenance_lock(&master->bus);
>>
>> if (enable)
>> ret = master->ops->enable_hotjoin(master);
>> @@ -659,7 +659,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
>> if (!ret)
>> master->hotjoin = enable;
>>
>> - i3c_bus_normaluse_unlock(&master->bus);
>> + i3c_bus_maintenance_unlock(&master->bus);
>>
>> if ((enable && ret) || (!enable && !ret) || master->rpm_ibi_allowed)
>> i3c_master_rpm_put(master);
>> --
>> 2.51.0
>>
>
> --
> linux-i3c mailing list
> linux-i3c@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-i3c
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock
2026-05-12 19:42 ` David Nyström
@ 2026-05-13 5:01 ` Adrian Hunter
2026-05-13 10:21 ` David Nyström
0 siblings, 1 reply; 24+ messages in thread
From: Adrian Hunter @ 2026-05-13 5:01 UTC (permalink / raw)
To: David Nyström, Frank Li; +Cc: alexandre.belloni, linux-i3c, linux-kernel
On 12/05/2026 22:42, David Nyström wrote:
>
>
> On Tue, 12 May 2026, Frank Li wrote:
>
>> On Tue, May 12, 2026 at 03:17:26PM +0300, Adrian Hunter wrote:
>>> i3c_set_hotjoin() dispatches the controller's enable_hotjoin() or
>>> disable_hotjoin() op and updates master->hotjoin under
>>> i3c_bus_normaluse_lock(). That lock is a read-side acquisition of
>>> bus->lock (down_read()), so it does not exclude concurrent callers.
>>>
>>> The hotjoin sysfs attribute can be opened multiple times, and writes
>>> through different opens are not serialized. Two concurrent writers
>>> to "hotjoin" can therefore race in i3c_set_hotjoin(), with the
>>> controller op and the master->hotjoin store from one call interleaving
>>> with the other. The hardware enable/disable state and the value reported
>>> by hotjoin_show() can end up out of sync.
>>>
>>> Take i3c_bus_maintenance_lock() instead. Toggling Hot Join enable
>>> changes bus state and is conceptually a maintenance operation, so the
>>> write-side acquisition of bus->lock is the appropriate lock and
>>> serializes concurrent callers against each other and against other
>>> maintenance operations.
>>
>> It should be bug fix, add fix tag here.
>
> Agreed, Fixes: "i3c: master: Add sysfs option to rescan bus via entdaa"
> Is this series headed for 7.1-rc3 ? if not, its probably wise to revert the sysfs addition from 7.1-rc
"i3c: master: Add sysfs option to rescan bus via entdaa" added "do_daa".
"hotjoin" is a different sysfs attribute.
>
>> Frank
>>>
>>> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
>>> ---
>>> drivers/i3c/master.c | 4 ++--
>>> 1 file changed, 2 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
>>> index ab11e2d79aab..38ffc8713167 100644
>>> --- a/drivers/i3c/master.c
>>> +++ b/drivers/i3c/master.c
>>> @@ -649,7 +649,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
>>> return ret;
>>> }
>>>
>>> - i3c_bus_normaluse_lock(&master->bus);
>>> + i3c_bus_maintenance_lock(&master->bus);
>>>
>>> if (enable)
>>> ret = master->ops->enable_hotjoin(master);
>>> @@ -659,7 +659,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
>>> if (!ret)
>>> master->hotjoin = enable;
>>>
>>> - i3c_bus_normaluse_unlock(&master->bus);
>>> + i3c_bus_maintenance_unlock(&master->bus);
>>>
>>> if ((enable && ret) || (!enable && !ret) || master->rpm_ibi_allowed)
>>> i3c_master_rpm_put(master);
>>> --
>>> 2.51.0
>>>
>>
>> --
>> linux-i3c mailing list
>> linux-i3c@lists.infradead.org
>> http://lists.infradead.org/mailman/listinfo/linux-i3c
>>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock
2026-05-13 5:01 ` Adrian Hunter
@ 2026-05-13 10:21 ` David Nyström
0 siblings, 0 replies; 24+ messages in thread
From: David Nyström @ 2026-05-13 10:21 UTC (permalink / raw)
To: Adrian Hunter
Cc: David Nyström, Frank Li, alexandre.belloni, linux-i3c,
linux-kernel
[-- Attachment #1: Type: text/plain, Size: 3023 bytes --]
On Wed, 13 May 2026, Adrian Hunter wrote:
> On 12/05/2026 22:42, David Nyström wrote:
>>
>>
>> On Tue, 12 May 2026, Frank Li wrote:
>>
>>> On Tue, May 12, 2026 at 03:17:26PM +0300, Adrian Hunter wrote:
>>>> i3c_set_hotjoin() dispatches the controller's enable_hotjoin() or
>>>> disable_hotjoin() op and updates master->hotjoin under
>>>> i3c_bus_normaluse_lock(). That lock is a read-side acquisition of
>>>> bus->lock (down_read()), so it does not exclude concurrent callers.
>>>>
>>>> The hotjoin sysfs attribute can be opened multiple times, and writes
>>>> through different opens are not serialized. Two concurrent writers
>>>> to "hotjoin" can therefore race in i3c_set_hotjoin(), with the
>>>> controller op and the master->hotjoin store from one call interleaving
>>>> with the other. The hardware enable/disable state and the value reported
>>>> by hotjoin_show() can end up out of sync.
>>>>
>>>> Take i3c_bus_maintenance_lock() instead. Toggling Hot Join enable
>>>> changes bus state and is conceptually a maintenance operation, so the
>>>> write-side acquisition of bus->lock is the appropriate lock and
>>>> serializes concurrent callers against each other and against other
>>>> maintenance operations.
>>>
>>> It should be bug fix, add fix tag here.
>>
>> Agreed, Fixes: "i3c: master: Add sysfs option to rescan bus via entdaa"
>> Is this series headed for 7.1-rc3 ? if not, its probably wise to revert the sysfs addition from 7.1-rc
>
> "i3c: master: Add sysfs option to rescan bus via entdaa" added "do_daa".
> "hotjoin" is a different sysfs attribute.
Replied to wrong patch, correct patch commented now.
>>
>>> Frank
>>>>
>>>> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
>>>> ---
>>>> drivers/i3c/master.c | 4 ++--
>>>> 1 file changed, 2 insertions(+), 2 deletions(-)
>>>>
>>>> diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
>>>> index ab11e2d79aab..38ffc8713167 100644
>>>> --- a/drivers/i3c/master.c
>>>> +++ b/drivers/i3c/master.c
>>>> @@ -649,7 +649,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
>>>> return ret;
>>>> }
>>>>
>>>> - i3c_bus_normaluse_lock(&master->bus);
>>>> + i3c_bus_maintenance_lock(&master->bus);
>>>>
>>>> if (enable)
>>>> ret = master->ops->enable_hotjoin(master);
>>>> @@ -659,7 +659,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
>>>> if (!ret)
>>>> master->hotjoin = enable;
>>>>
>>>> - i3c_bus_normaluse_unlock(&master->bus);
>>>> + i3c_bus_maintenance_unlock(&master->bus);
>>>>
>>>> if ((enable && ret) || (!enable && !ret) || master->rpm_ibi_allowed)
>>>> i3c_master_rpm_put(master);
>>>> --
>>>> 2.51.0
>>>>
>>>
>>> --
>>> linux-i3c mailing list
>>> linux-i3c@lists.infradead.org
>>> http://lists.infradead.org/mailman/listinfo/linux-i3c
>>>
>
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock
2026-05-12 16:11 ` Frank Li
2026-05-12 19:42 ` David Nyström
@ 2026-05-13 5:09 ` Adrian Hunter
1 sibling, 0 replies; 24+ messages in thread
From: Adrian Hunter @ 2026-05-13 5:09 UTC (permalink / raw)
To: Frank Li; +Cc: alexandre.belloni, linux-i3c, linux-kernel
On 12/05/2026 19:11, Frank Li wrote:
> On Tue, May 12, 2026 at 03:17:26PM +0300, Adrian Hunter wrote:
>> i3c_set_hotjoin() dispatches the controller's enable_hotjoin() or
>> disable_hotjoin() op and updates master->hotjoin under
>> i3c_bus_normaluse_lock(). That lock is a read-side acquisition of
>> bus->lock (down_read()), so it does not exclude concurrent callers.
>>
>> The hotjoin sysfs attribute can be opened multiple times, and writes
>> through different opens are not serialized. Two concurrent writers
>> to "hotjoin" can therefore race in i3c_set_hotjoin(), with the
>> controller op and the master->hotjoin store from one call interleaving
>> with the other. The hardware enable/disable state and the value reported
>> by hotjoin_show() can end up out of sync.
>>
>> Take i3c_bus_maintenance_lock() instead. Toggling Hot Join enable
>> changes bus state and is conceptually a maintenance operation, so the
>> write-side acquisition of bus->lock is the appropriate lock and
>> serializes concurrent callers against each other and against other
>> maintenance operations.
>
> It should be bug fix, add fix tag here.
Ok
Note, currently it can only go wrong if user space is trying to enable
and disable hotjoin at the same time, which is already broken - user
space itself needs a way to synchronize its hotjoin policy.
>
> Frank
>>
>> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
>> ---
>> drivers/i3c/master.c | 4 ++--
>> 1 file changed, 2 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
>> index ab11e2d79aab..38ffc8713167 100644
>> --- a/drivers/i3c/master.c
>> +++ b/drivers/i3c/master.c
>> @@ -649,7 +649,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
>> return ret;
>> }
>>
>> - i3c_bus_normaluse_lock(&master->bus);
>> + i3c_bus_maintenance_lock(&master->bus);
>>
>> if (enable)
>> ret = master->ops->enable_hotjoin(master);
>> @@ -659,7 +659,7 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
>> if (!ret)
>> master->hotjoin = enable;
>>
>> - i3c_bus_normaluse_unlock(&master->bus);
>> + i3c_bus_maintenance_unlock(&master->bus);
>>
>> if ((enable && ret) || (!enable && !ret) || master->rpm_ibi_allowed)
>> i3c_master_rpm_put(master);
>> --
>> 2.51.0
>>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 3/8] i3c: master: Consolidate Hot-Join DAA work in the core
2026-05-12 12:17 [PATCH 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
2026-05-12 12:17 ` [PATCH 1/8] i3c: master: Make hot-join workqueue freezable to block hot-join during suspend Adrian Hunter
2026-05-12 12:17 ` [PATCH 2/8] i3c: master: Serialize i3c_set_hotjoin() with the maintenance lock Adrian Hunter
@ 2026-05-12 12:17 ` Adrian Hunter
2026-05-12 16:16 ` Frank Li
2026-05-12 12:17 ` [PATCH 4/8] i3c: master: Ensure Hot-Join operations are stopped on shutdown Adrian Hunter
` (4 subsequent siblings)
7 siblings, 1 reply; 24+ messages in thread
From: Adrian Hunter @ 2026-05-12 12:17 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, linux-i3c, linux-kernel
Three master drivers (dw-i3c-master, i3c-master-cdns, svc-i3c-master)
each carry an essentially identical Hot-Join handler: a struct
work_struct embedded in their private state, a work function that just
calls i3c_master_do_daa() on the embedded i3c_master_controller, plus
matching INIT_WORK()/cancel_work_sync() boilerplate in probe/remove (and
shutdown for dw-i3c). The IBI/ISR paths then queue that work onto
master->wq, which already lives in the core.
Move this pattern into the I3C core:
- Add struct work_struct hj_work to struct i3c_master_controller and
initialise it in i3c_master_register() with a core-provided handler
i3c_master_hj_work_fn() that performs i3c_master_do_daa().
- Cancel the work in i3c_master_unregister() so all controllers get
correct teardown ordering against the workqueue for free.
- Export i3c_master_queue_hotjoin() as the single entry point drivers
call from their Hot-Join IBI handler.
Convert the three existing users to the new API: drop their private
hj_work fields, work functions, INIT_WORK() and cancel_work_sync()
calls, and replace the queue_work(master->wq, &drv->hj_work) call sites
with i3c_master_queue_hotjoin(&drv->base). The dw-i3c shutdown path
still needs to flush pending Hot-Join work before tearing down the
hardware, so it is updated to cancel master->base.hj_work directly.
No functional change intended: the work is still queued on the same
master->wq, runs the same i3c_master_do_daa(), and is cancelled at
controller teardown. Future Hot-Join improvements now only need to
be made in one place.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
drivers/i3c/master.c | 21 +++++++++++++++++++++
drivers/i3c/master/dw-i3c-master.c | 15 ++-------------
drivers/i3c/master/dw-i3c-master.h | 2 --
drivers/i3c/master/i3c-master-cdns.c | 14 +-------------
drivers/i3c/master/svc-i3c-master.c | 14 +-------------
include/linux/i3c/master.h | 4 ++++
6 files changed, 29 insertions(+), 41 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 38ffc8713167..cdb5cb2aa65d 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -633,6 +633,13 @@ static ssize_t i2c_scl_frequency_show(struct device *dev,
}
static DEVICE_ATTR_RO(i2c_scl_frequency);
+static void i3c_master_hj_work_fn(struct work_struct *work)
+{
+ struct i3c_master_controller *master = container_of(work, typeof(*master), hj_work);
+
+ i3c_master_do_daa(master);
+}
+
static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
{
int ret;
@@ -711,6 +718,18 @@ int i3c_master_disable_hotjoin(struct i3c_master_controller *master)
}
EXPORT_SYMBOL_GPL(i3c_master_disable_hotjoin);
+/**
+ * i3c_master_queue_hotjoin - Queue DAA processing after a Hot-Join event
+ * @master: I3C master object
+ *
+ * Queue the hot-join worker on the master's workqueue.
+ */
+void i3c_master_queue_hotjoin(struct i3c_master_controller *master)
+{
+ queue_work(master->wq, &master->hj_work);
+}
+EXPORT_SYMBOL_GPL(i3c_master_queue_hotjoin);
+
static ssize_t hotjoin_show(struct device *dev, struct device_attribute *da, char *buf)
{
struct i3c_bus *i3cbus = dev_to_i3cbus(dev);
@@ -3084,6 +3103,7 @@ int i3c_master_register(struct i3c_master_controller *master,
ret = -ENOMEM;
goto err_put_dev;
}
+ INIT_WORK(&master->hj_work, i3c_master_hj_work_fn);
ret = i3c_master_bus_init(master);
if (ret)
@@ -3146,6 +3166,7 @@ EXPORT_SYMBOL_GPL(i3c_master_register);
void i3c_master_unregister(struct i3c_master_controller *master)
{
i3c_bus_notify(&master->bus, I3C_NOTIFY_BUS_REMOVE);
+ cancel_work_sync(&master->hj_work);
if (master->ops->set_dev_nack_retry)
device_remove_file(&master->dev, &dev_attr_dev_nack_retry_count);
diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
index 655693a2187e..eb9a13a73684 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -1445,7 +1445,7 @@ static void dw_i3c_master_irq_handle_ibis(struct dw_i3c_master *master)
if (IBI_TYPE_SIRQ(reg)) {
dw_i3c_master_handle_ibi_sir(master, reg);
} else if (IBI_TYPE_HJ(reg)) {
- queue_work(master->base.wq, &master->hj_work);
+ i3c_master_queue_hotjoin(&master->base);
} else {
len = IBI_QUEUE_STATUS_DATA_LEN(reg);
dev_info(&master->base.dev,
@@ -1554,14 +1554,6 @@ static const struct dw_i3c_platform_ops dw_i3c_platform_ops_default = {
.set_dat_ibi = dw_i3c_platform_set_dat_ibi_nop,
};
-static void dw_i3c_hj_work(struct work_struct *work)
-{
- struct dw_i3c_master *master =
- container_of(work, typeof(*master), hj_work);
-
- i3c_master_do_daa(&master->base);
-}
-
int dw_i3c_common_probe(struct dw_i3c_master *master,
struct platform_device *pdev)
{
@@ -1636,8 +1628,6 @@ int dw_i3c_common_probe(struct dw_i3c_master *master,
if (master->quirks & DW_I3C_DISABLE_RUNTIME_PM_QUIRK)
pm_runtime_get_noresume(&pdev->dev);
- INIT_WORK(&master->hj_work, dw_i3c_hj_work);
-
device_set_of_node_from_dev(&master->base.i2c.dev, &pdev->dev);
ret = i3c_master_register(&master->base, &pdev->dev,
&dw_mipi_i3c_ops, false);
@@ -1659,7 +1649,6 @@ EXPORT_SYMBOL_GPL(dw_i3c_common_probe);
void dw_i3c_common_remove(struct dw_i3c_master *master)
{
- cancel_work_sync(&master->hj_work);
i3c_master_unregister(&master->base);
/* Balance pm_runtime_get_noresume() from probe() */
@@ -1804,7 +1793,7 @@ static void dw_i3c_shutdown(struct platform_device *pdev)
return;
}
- cancel_work_sync(&master->hj_work);
+ cancel_work_sync(&master->base.hj_work);
/* Disable interrupts */
writel((u32)~INTR_ALL, master->regs + INTR_STATUS_EN);
diff --git a/drivers/i3c/master/dw-i3c-master.h b/drivers/i3c/master/dw-i3c-master.h
index c5cb695c16ab..2f029bd36232 100644
--- a/drivers/i3c/master/dw-i3c-master.h
+++ b/drivers/i3c/master/dw-i3c-master.h
@@ -68,8 +68,6 @@ struct dw_i3c_master {
/* platform-specific data */
const struct dw_i3c_platform_ops *platform_ops;
-
- struct work_struct hj_work;
};
struct dw_i3c_platform_ops {
diff --git a/drivers/i3c/master/i3c-master-cdns.c b/drivers/i3c/master/i3c-master-cdns.c
index 5cfec6761494..6d221596ea35 100644
--- a/drivers/i3c/master/i3c-master-cdns.c
+++ b/drivers/i3c/master/i3c-master-cdns.c
@@ -398,7 +398,6 @@ struct cdns_i3c_data {
};
struct cdns_i3c_master {
- struct work_struct hj_work;
struct i3c_master_controller base;
u32 free_rr_slots;
unsigned int maxdevs;
@@ -1357,7 +1356,7 @@ static void cnds_i3c_master_demux_ibis(struct cdns_i3c_master *master)
case IBIR_TYPE_HJ:
WARN_ON(IBIR_XFER_BYTES(ibir) || (ibir & IBIR_ERROR));
- queue_work(master->base.wq, &master->hj_work);
+ i3c_master_queue_hotjoin(&master->base);
break;
case IBIR_TYPE_MR:
@@ -1528,15 +1527,6 @@ static const struct i3c_master_controller_ops cdns_i3c_master_ops = {
.recycle_ibi_slot = cdns_i3c_master_recycle_ibi_slot,
};
-static void cdns_i3c_master_hj(struct work_struct *work)
-{
- struct cdns_i3c_master *master = container_of(work,
- struct cdns_i3c_master,
- hj_work);
-
- i3c_master_do_daa(&master->base);
-}
-
static struct cdns_i3c_data cdns_i3c_devdata = {
.thd_delay_ns = 10,
};
@@ -1584,7 +1574,6 @@ static int cdns_i3c_master_probe(struct platform_device *pdev)
spin_lock_init(&master->xferqueue.lock);
INIT_LIST_HEAD(&master->xferqueue.list);
- INIT_WORK(&master->hj_work, cdns_i3c_master_hj);
writel(0xffffffff, master->regs + MST_IDR);
writel(0xffffffff, master->regs + SLV_IDR);
ret = devm_request_irq(&pdev->dev, irq, cdns_i3c_master_interrupt, 0,
@@ -1627,7 +1616,6 @@ static void cdns_i3c_master_remove(struct platform_device *pdev)
{
struct cdns_i3c_master *master = platform_get_drvdata(pdev);
- cancel_work_sync(&master->hj_work);
i3c_master_unregister(&master->base);
}
diff --git a/drivers/i3c/master/svc-i3c-master.c b/drivers/i3c/master/svc-i3c-master.c
index e2d99a3ac07d..62e5666798c8 100644
--- a/drivers/i3c/master/svc-i3c-master.c
+++ b/drivers/i3c/master/svc-i3c-master.c
@@ -208,7 +208,6 @@ struct svc_i3c_drvdata {
* @free_slots: Bit array of available slots
* @addrs: Array containing the dynamic addresses of each attached device
* @descs: Array of descriptors, one per attached device
- * @hj_work: Hot-join work
* @irq: Main interrupt
* @num_clks: I3C clock number
* @fclk: Fast clock (bus)
@@ -235,7 +234,6 @@ struct svc_i3c_master {
u32 free_slots;
u8 addrs[SVC_I3C_MAX_DEVS];
struct i3c_dev_desc *descs[SVC_I3C_MAX_DEVS];
- struct work_struct hj_work;
int irq;
int num_clks;
struct clk *fclk;
@@ -366,14 +364,6 @@ to_svc_i3c_master(struct i3c_master_controller *master)
return container_of(master, struct svc_i3c_master, base);
}
-static void svc_i3c_master_hj_work(struct work_struct *work)
-{
- struct svc_i3c_master *master;
-
- master = container_of(work, struct svc_i3c_master, hj_work);
- i3c_master_do_daa(&master->base);
-}
-
static struct i3c_dev_desc *
svc_i3c_master_dev_from_addr(struct svc_i3c_master *master,
unsigned int ibiaddr)
@@ -651,7 +641,7 @@ static void svc_i3c_master_ibi_isr(struct svc_i3c_master *master)
case SVC_I3C_MSTATUS_IBITYPE_HOT_JOIN:
svc_i3c_master_emit_stop(master);
if (is_events_enabled(master, SVC_I3C_EVENT_HOTJOIN))
- queue_work(master->base.wq, &master->hj_work);
+ i3c_master_queue_hotjoin(&master->base);
break;
case SVC_I3C_MSTATUS_IBITYPE_MASTER_REQUEST:
svc_i3c_master_emit_stop(master);
@@ -2022,7 +2012,6 @@ static int svc_i3c_master_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "can't enable I3C clocks\n");
- INIT_WORK(&master->hj_work, svc_i3c_master_hj_work);
mutex_init(&master->lock);
ret = devm_request_irq(dev, master->irq, svc_i3c_master_irq_handler,
@@ -2081,7 +2070,6 @@ static void svc_i3c_master_remove(struct platform_device *pdev)
{
struct svc_i3c_master *master = platform_get_drvdata(pdev);
- cancel_work_sync(&master->hj_work);
i3c_master_unregister(&master->base);
pm_runtime_dont_use_autosuspend(&pdev->dev);
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index e6112e5f6608..eb5c51608bd7 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -520,6 +520,8 @@ struct i3c_master_controller_ops {
* in a thread context. Typical examples are Hot Join processing which
* requires taking the bus lock in maintenance, which in turn, can only
* be done from a sleep-able context
+ * @hj_work: work item used to run DAA after a Hot-Join event is detected.
+ * Queued to @wq by i3c_master_queue_hotjoin()
* @dev_nack_retry_count: retry count when slave device nack
*
* A &struct i3c_master_controller has to be registered to the I3C subsystem
@@ -543,6 +545,7 @@ struct i3c_master_controller {
} boardinfo;
struct i3c_bus bus;
struct workqueue_struct *wq;
+ struct work_struct hj_work;
unsigned int dev_nack_retry_count;
};
@@ -623,6 +626,7 @@ int i3c_master_register(struct i3c_master_controller *master,
void i3c_master_unregister(struct i3c_master_controller *master);
int i3c_master_enable_hotjoin(struct i3c_master_controller *master);
int i3c_master_disable_hotjoin(struct i3c_master_controller *master);
+void i3c_master_queue_hotjoin(struct i3c_master_controller *master);
/**
* i3c_dev_get_master_data() - get master private data attached to an I3C
--
2.51.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH 3/8] i3c: master: Consolidate Hot-Join DAA work in the core
2026-05-12 12:17 ` [PATCH 3/8] i3c: master: Consolidate Hot-Join DAA work in the core Adrian Hunter
@ 2026-05-12 16:16 ` Frank Li
0 siblings, 0 replies; 24+ messages in thread
From: Frank Li @ 2026-05-12 16:16 UTC (permalink / raw)
To: Adrian Hunter; +Cc: alexandre.belloni, linux-i3c, linux-kernel
On Tue, May 12, 2026 at 03:17:27PM +0300, Adrian Hunter wrote:
> Three master drivers (dw-i3c-master, i3c-master-cdns, svc-i3c-master)
> each carry an essentially identical Hot-Join handler: a struct
> work_struct embedded in their private state, a work function that just
> calls i3c_master_do_daa() on the embedded i3c_master_controller, plus
> matching INIT_WORK()/cancel_work_sync() boilerplate in probe/remove (and
> shutdown for dw-i3c). The IBI/ISR paths then queue that work onto
> master->wq, which already lives in the core.
>
> Move this pattern into the I3C core:
>
> - Add struct work_struct hj_work to struct i3c_master_controller and
> initialise it in i3c_master_register() with a core-provided handler
> i3c_master_hj_work_fn() that performs i3c_master_do_daa().
> - Cancel the work in i3c_master_unregister() so all controllers get
> correct teardown ordering against the workqueue for free.
> - Export i3c_master_queue_hotjoin() as the single entry point drivers
> call from their Hot-Join IBI handler.
>
> Convert the three existing users to the new API: drop their private
> hj_work fields, work functions, INIT_WORK() and cancel_work_sync()
> calls, and replace the queue_work(master->wq, &drv->hj_work) call sites
> with i3c_master_queue_hotjoin(&drv->base). The dw-i3c shutdown path
> still needs to flush pending Hot-Join work before tearing down the
> hardware, so it is updated to cancel master->base.hj_work directly.
>
> No functional change intended: the work is still queued on the same
> master->wq, runs the same i3c_master_do_daa(), and is cancelled at
> controller teardown. Future Hot-Join improvements now only need to
> be made in one place.
>
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
Thank you for do such consolidate work.
Reviewed-by: Frank Li <Frank.Li@nxp.com>
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 4/8] i3c: master: Ensure Hot-Join operations are stopped on shutdown
2026-05-12 12:17 [PATCH 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
` (2 preceding siblings ...)
2026-05-12 12:17 ` [PATCH 3/8] i3c: master: Consolidate Hot-Join DAA work in the core Adrian Hunter
@ 2026-05-12 12:17 ` Adrian Hunter
2026-05-12 16:27 ` Frank Li
2026-05-12 12:17 ` [PATCH 5/8] i3c: dw: Drop redundant Hot-Join cancel_work_sync() in shutdown Adrian Hunter
` (3 subsequent siblings)
7 siblings, 1 reply; 24+ messages in thread
From: Adrian Hunter @ 2026-05-12 12:17 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, linux-i3c, linux-kernel
System shutdown invokes each device's bus shutdown callback to quiesce
hardware, but the I3C bus type does not currently implement one. As a
result, on shutdown the controller's Hot-Join work and any in-flight
i3c_master_do_daa() can keep running (or be newly triggered) while the
rest of the system is being torn down.
A similar window exists at i3c_master_unregister() time: cancel_work_sync()
on hj_work prevents queued work from completing, but does not stop a
fresh Hot-Join IBI from re-queueing the worker, nor a concurrent sysfs
writer from toggling Hot-Join via i3c_set_hotjoin().
Introduce a single "shutting down" gate in the I3C core, set under the
bus maintenance lock so it is observed by any in-progress DAA path
before pending work is cancelled. Install an i3c_bus_type shutdown
callback that engages this gate for master devices during system
shutdown, and use the same gate in i3c_master_unregister() so both
paths get identical guarantees.
Once the gate is engaged, the Hot-Join worker, i3c_master_do_daa_ext()
and i3c_set_hotjoin() all bail out cleanly, so Hot-Join IBIs that race
with shutdown become no-ops, direct DAA callers see -ENODEV, and sysfs
writers can no longer re-enable Hot-Join through ops->enable_hotjoin()
while the controller is going away.
No functional change for the steady-state runtime path; the new checks
only take effect once the controller has been marked as shutting down.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
drivers/i3c/master.c | 52 +++++++++++++++++++++++++++-----------
include/linux/i3c/master.h | 2 ++
2 files changed, 39 insertions(+), 15 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index cdb5cb2aa65d..a59c4b744b36 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -368,14 +368,6 @@ static void i3c_device_remove(struct device *dev)
driver->remove(i3cdev);
}
-const struct bus_type i3c_bus_type = {
- .name = "i3c",
- .match = i3c_device_match,
- .probe = i3c_device_probe,
- .remove = i3c_device_remove,
-};
-EXPORT_SYMBOL_GPL(i3c_bus_type);
-
static enum i3c_addr_slot_status
i3c_bus_get_addr_slot_status_mask(struct i3c_bus *bus, u16 addr, u32 mask)
{
@@ -637,7 +629,8 @@ static void i3c_master_hj_work_fn(struct work_struct *work)
{
struct i3c_master_controller *master = container_of(work, typeof(*master), hj_work);
- i3c_master_do_daa(master);
+ if (!master->shutting_down)
+ i3c_master_do_daa(master);
}
static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
@@ -658,7 +651,9 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
i3c_bus_maintenance_lock(&master->bus);
- if (enable)
+ if (master->shutting_down)
+ ret = -ENODEV;
+ else if (enable)
ret = master->ops->enable_hotjoin(master);
else
ret = master->ops->disable_hotjoin(master);
@@ -837,6 +832,30 @@ static const struct device_type i3c_masterdev_type = {
.groups = i3c_masterdev_groups,
};
+static void i3c_master_shutdown(struct i3c_master_controller *master)
+{
+ i3c_bus_maintenance_lock(&master->bus);
+ master->shutting_down = true;
+ i3c_bus_maintenance_unlock(&master->bus);
+
+ cancel_work_sync(&master->hj_work);
+}
+
+static void i3c_device_shutdown(struct device *dev)
+{
+ if (dev->type == &i3c_masterdev_type)
+ i3c_master_shutdown(dev_to_i3cmaster(dev));
+}
+
+const struct bus_type i3c_bus_type = {
+ .name = "i3c",
+ .match = i3c_device_match,
+ .probe = i3c_device_probe,
+ .remove = i3c_device_remove,
+ .shutdown = i3c_device_shutdown,
+};
+EXPORT_SYMBOL_GPL(i3c_bus_type);
+
static int i3c_bus_set_mode(struct i3c_bus *i3cbus, enum i3c_bus_mode mode,
unsigned long max_i2c_scl_rate)
{
@@ -1846,10 +1865,13 @@ int i3c_master_do_daa_ext(struct i3c_master_controller *master, bool rstdaa)
i3c_bus_maintenance_lock(&master->bus);
- if (rstdaa)
- rstret = i3c_master_rstdaa_locked(master, I3C_BROADCAST_ADDR);
-
- ret = master->ops->do_daa(master);
+ if (master->shutting_down) {
+ ret = -ENODEV;
+ } else {
+ if (rstdaa)
+ rstret = i3c_master_rstdaa_locked(master, I3C_BROADCAST_ADDR);
+ ret = master->ops->do_daa(master);
+ }
i3c_bus_maintenance_unlock(&master->bus);
@@ -3166,7 +3188,7 @@ EXPORT_SYMBOL_GPL(i3c_master_register);
void i3c_master_unregister(struct i3c_master_controller *master)
{
i3c_bus_notify(&master->bus, I3C_NOTIFY_BUS_REMOVE);
- cancel_work_sync(&master->hj_work);
+ i3c_master_shutdown(master);
if (master->ops->set_dev_nack_retry)
device_remove_file(&master->dev, &dev_attr_dev_nack_retry_count);
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index eb5c51608bd7..77e63082b06e 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -511,6 +511,7 @@ struct i3c_master_controller_ops {
* @hotjoin: true if the master support hotjoin
* @rpm_allowed: true if Runtime PM allowed
* @rpm_ibi_allowed: true if IBI and Hot-Join allowed while runtime suspended
+ * @shutting_down: set to true when master begins shutdown or unregister
* @boardinfo.i3c: list of I3C boardinfo objects
* @boardinfo.i2c: list of I2C boardinfo objects
* @boardinfo: board-level information attached to devices connected on the bus
@@ -539,6 +540,7 @@ struct i3c_master_controller {
unsigned int hotjoin: 1;
unsigned int rpm_allowed: 1;
unsigned int rpm_ibi_allowed: 1;
+ bool shutting_down;
struct {
struct list_head i3c;
struct list_head i2c;
--
2.51.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH 4/8] i3c: master: Ensure Hot-Join operations are stopped on shutdown
2026-05-12 12:17 ` [PATCH 4/8] i3c: master: Ensure Hot-Join operations are stopped on shutdown Adrian Hunter
@ 2026-05-12 16:27 ` Frank Li
2026-05-13 5:31 ` Adrian Hunter
0 siblings, 1 reply; 24+ messages in thread
From: Frank Li @ 2026-05-12 16:27 UTC (permalink / raw)
To: Adrian Hunter; +Cc: alexandre.belloni, linux-i3c, linux-kernel
On Tue, May 12, 2026 at 03:17:28PM +0300, Adrian Hunter wrote:
> System shutdown invokes each device's bus shutdown callback to quiesce
> hardware, but the I3C bus type does not currently implement one. As a
> result, on shutdown the controller's Hot-Join work and any in-flight
> i3c_master_do_daa() can keep running (or be newly triggered) while the
> rest of the system is being torn down.
>
> A similar window exists at i3c_master_unregister() time: cancel_work_sync()
> on hj_work prevents queued work from completing, but does not stop a
> fresh Hot-Join IBI from re-queueing the worker, nor a concurrent sysfs
> writer from toggling Hot-Join via i3c_set_hotjoin().
>
> Introduce a single "shutting down" gate in the I3C core, set under the
> bus maintenance lock so it is observed by any in-progress DAA path
> before pending work is cancelled. Install an i3c_bus_type shutdown
> callback that engages this gate for master devices during system
> shutdown, and use the same gate in i3c_master_unregister() so both
> paths get identical guarantees.
>
> Once the gate is engaged, the Hot-Join worker, i3c_master_do_daa_ext()
> and i3c_set_hotjoin() all bail out cleanly, so Hot-Join IBIs that race
> with shutdown become no-ops, direct DAA callers see -ENODEV, and sysfs
> writers can no longer re-enable Hot-Join through ops->enable_hotjoin()
> while the controller is going away.
>
> No functional change for the steady-state runtime path; the new checks
> only take effect once the controller has been marked as shutting down.
>
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
> drivers/i3c/master.c | 52 +++++++++++++++++++++++++++-----------
> include/linux/i3c/master.h | 2 ++
> 2 files changed, 39 insertions(+), 15 deletions(-)
>
> diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
> index cdb5cb2aa65d..a59c4b744b36 100644
> --- a/drivers/i3c/master.c
> +++ b/drivers/i3c/master.c
> @@ -368,14 +368,6 @@ static void i3c_device_remove(struct device *dev)
> driver->remove(i3cdev);
> }
>
> -const struct bus_type i3c_bus_type = {
> - .name = "i3c",
> - .match = i3c_device_match,
> - .probe = i3c_device_probe,
> - .remove = i3c_device_remove,
> -};
> -EXPORT_SYMBOL_GPL(i3c_bus_type);
> -
why need move this tunk?
> static enum i3c_addr_slot_status
> i3c_bus_get_addr_slot_status_mask(struct i3c_bus *bus, u16 addr, u32 mask)
> {
> @@ -637,7 +629,8 @@ static void i3c_master_hj_work_fn(struct work_struct *work)
> {
> struct i3c_master_controller *master = container_of(work, typeof(*master), hj_work);
>
> - i3c_master_do_daa(master);
> + if (!master->shutting_down)
> + i3c_master_do_daa(master);
> }
>
> static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
> @@ -658,7 +651,9 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
>
> i3c_bus_maintenance_lock(&master->bus);
later, consider change to use cleanup, so
if (master->shutting_down)
return -ENODEV
and avoid use else if branch.
but this change is okay for now.
Frank
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH 4/8] i3c: master: Ensure Hot-Join operations are stopped on shutdown
2026-05-12 16:27 ` Frank Li
@ 2026-05-13 5:31 ` Adrian Hunter
0 siblings, 0 replies; 24+ messages in thread
From: Adrian Hunter @ 2026-05-13 5:31 UTC (permalink / raw)
To: Frank Li; +Cc: alexandre.belloni, linux-i3c, linux-kernel
On 12/05/2026 19:27, Frank Li wrote:
> On Tue, May 12, 2026 at 03:17:28PM +0300, Adrian Hunter wrote:
>> System shutdown invokes each device's bus shutdown callback to quiesce
>> hardware, but the I3C bus type does not currently implement one. As a
>> result, on shutdown the controller's Hot-Join work and any in-flight
>> i3c_master_do_daa() can keep running (or be newly triggered) while the
>> rest of the system is being torn down.
>>
>> A similar window exists at i3c_master_unregister() time: cancel_work_sync()
>> on hj_work prevents queued work from completing, but does not stop a
>> fresh Hot-Join IBI from re-queueing the worker, nor a concurrent sysfs
>> writer from toggling Hot-Join via i3c_set_hotjoin().
>>
>> Introduce a single "shutting down" gate in the I3C core, set under the
>> bus maintenance lock so it is observed by any in-progress DAA path
>> before pending work is cancelled. Install an i3c_bus_type shutdown
>> callback that engages this gate for master devices during system
>> shutdown, and use the same gate in i3c_master_unregister() so both
>> paths get identical guarantees.
>>
>> Once the gate is engaged, the Hot-Join worker, i3c_master_do_daa_ext()
>> and i3c_set_hotjoin() all bail out cleanly, so Hot-Join IBIs that race
>> with shutdown become no-ops, direct DAA callers see -ENODEV, and sysfs
>> writers can no longer re-enable Hot-Join through ops->enable_hotjoin()
>> while the controller is going away.
>>
>> No functional change for the steady-state runtime path; the new checks
>> only take effect once the controller has been marked as shutting down.
>>
>> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
>> ---
>> drivers/i3c/master.c | 52 +++++++++++++++++++++++++++-----------
>> include/linux/i3c/master.h | 2 ++
>> 2 files changed, 39 insertions(+), 15 deletions(-)
>>
>> diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
>> index cdb5cb2aa65d..a59c4b744b36 100644
>> --- a/drivers/i3c/master.c
>> +++ b/drivers/i3c/master.c
>> @@ -368,14 +368,6 @@ static void i3c_device_remove(struct device *dev)
>> driver->remove(i3cdev);
>> }
>>
>> -const struct bus_type i3c_bus_type = {
>> - .name = "i3c",
>> - .match = i3c_device_match,
>> - .probe = i3c_device_probe,
>> - .remove = i3c_device_remove,
>> -};
>> -EXPORT_SYMBOL_GPL(i3c_bus_type);
>> -
>
> why need move this tunk?
To avoid forward declarations.
i3c_device_shutdown() references i3c_masterdev_type so it needs to be
after i3c_masterdev_type definition. i3c_device_shutdown() is being
added to struct i3c_bus_type, so i3c_bus_type needs to be after
i3c_device_shutdown().
>
>> static enum i3c_addr_slot_status
>> i3c_bus_get_addr_slot_status_mask(struct i3c_bus *bus, u16 addr, u32 mask)
>> {
>> @@ -637,7 +629,8 @@ static void i3c_master_hj_work_fn(struct work_struct *work)
>> {
>> struct i3c_master_controller *master = container_of(work, typeof(*master), hj_work);
>>
>> - i3c_master_do_daa(master);
>> + if (!master->shutting_down)
>> + i3c_master_do_daa(master);
>> }
>>
>> static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
>> @@ -658,7 +651,9 @@ static int i3c_set_hotjoin(struct i3c_master_controller *master, bool enable)
>>
>> i3c_bus_maintenance_lock(&master->bus);
>
> later, consider change to use cleanup, so
> if (master->shutting_down)
> return -ENODEV
>
> and avoid use else if branch.
>
> but this change is okay for now.
>
> Frank
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 5/8] i3c: dw: Drop redundant Hot-Join cancel_work_sync() in shutdown
2026-05-12 12:17 [PATCH 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
` (3 preceding siblings ...)
2026-05-12 12:17 ` [PATCH 4/8] i3c: master: Ensure Hot-Join operations are stopped on shutdown Adrian Hunter
@ 2026-05-12 12:17 ` Adrian Hunter
2026-05-12 16:30 ` Frank Li
2026-05-12 12:17 ` [PATCH 6/8] i3c: master: Defer new-device registration out of DAA caller context Adrian Hunter
` (2 subsequent siblings)
7 siblings, 1 reply; 24+ messages in thread
From: Adrian Hunter @ 2026-05-12 12:17 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, linux-i3c, linux-kernel
The I3C core now installs an i3c_bus_type shutdown callback that
flushes master->hj_work (via i3c_master_shutdown()) before any driver's
platform shutdown hook runs. The explicit cancel_work_sync() in
dw_i3c_shutdown() is therefore redundant: by the time it executes, the
Hot-Join worker has already been cancelled, and the shutting_down gate
makes a new worker a no-op.
Remove the now-unneeded call. No functional change.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
drivers/i3c/master/dw-i3c-master.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
index eb9a13a73684..c7030d0cd8a6 100644
--- a/drivers/i3c/master/dw-i3c-master.c
+++ b/drivers/i3c/master/dw-i3c-master.c
@@ -1793,8 +1793,6 @@ static void dw_i3c_shutdown(struct platform_device *pdev)
return;
}
- cancel_work_sync(&master->base.hj_work);
-
/* Disable interrupts */
writel((u32)~INTR_ALL, master->regs + INTR_STATUS_EN);
writel((u32)~INTR_ALL, master->regs + INTR_SIGNAL_EN);
--
2.51.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH 5/8] i3c: dw: Drop redundant Hot-Join cancel_work_sync() in shutdown
2026-05-12 12:17 ` [PATCH 5/8] i3c: dw: Drop redundant Hot-Join cancel_work_sync() in shutdown Adrian Hunter
@ 2026-05-12 16:30 ` Frank Li
0 siblings, 0 replies; 24+ messages in thread
From: Frank Li @ 2026-05-12 16:30 UTC (permalink / raw)
To: Adrian Hunter; +Cc: alexandre.belloni, linux-i3c, linux-kernel
On Tue, May 12, 2026 at 03:17:29PM +0300, Adrian Hunter wrote:
> The I3C core now installs an i3c_bus_type shutdown callback that
> flushes master->hj_work (via i3c_master_shutdown()) before any driver's
> platform shutdown hook runs. The explicit cancel_work_sync() in
> dw_i3c_shutdown() is therefore redundant: by the time it executes, the
> Hot-Join worker has already been cancelled, and the shutting_down gate
> makes a new worker a no-op.
>
> Remove the now-unneeded call. No functional change.
>
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/i3c/master/dw-i3c-master.c | 2 --
> 1 file changed, 2 deletions(-)
>
> diff --git a/drivers/i3c/master/dw-i3c-master.c b/drivers/i3c/master/dw-i3c-master.c
> index eb9a13a73684..c7030d0cd8a6 100644
> --- a/drivers/i3c/master/dw-i3c-master.c
> +++ b/drivers/i3c/master/dw-i3c-master.c
> @@ -1793,8 +1793,6 @@ static void dw_i3c_shutdown(struct platform_device *pdev)
> return;
> }
>
> - cancel_work_sync(&master->base.hj_work);
> -
> /* Disable interrupts */
> writel((u32)~INTR_ALL, master->regs + INTR_STATUS_EN);
> writel((u32)~INTR_ALL, master->regs + INTR_SIGNAL_EN);
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 6/8] i3c: master: Defer new-device registration out of DAA caller context
2026-05-12 12:17 [PATCH 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
` (4 preceding siblings ...)
2026-05-12 12:17 ` [PATCH 5/8] i3c: dw: Drop redundant Hot-Join cancel_work_sync() in shutdown Adrian Hunter
@ 2026-05-12 12:17 ` Adrian Hunter
2026-05-12 16:39 ` Frank Li
2026-05-12 12:17 ` [PATCH 7/8] i3c: master: Export i3c_master_enec_disec_locked() Adrian Hunter
2026-05-12 12:17 ` [PATCH 8/8] i3c: mipi-i3c-hci: Add Hot-Join support Adrian Hunter
7 siblings, 1 reply; 24+ messages in thread
From: Adrian Hunter @ 2026-05-12 12:17 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, linux-i3c, linux-kernel
Master drivers may invoke i3c_master_do_daa_ext() during resume to
re-run Dynamic Address Assignment. As well as assigning addresses to
any newly arrived devices, this restores the dynamic address of devices
that lost it across system suspend, so it has to run as part of the
controller's resume path.
A side effect of i3c_master_do_daa_ext() today is that it also
registers any newly discovered I3C devices with the driver model
inline, via i3c_master_register_new_i3c_devs(). Doing that from the
resume path is problematic: a hot-join-capable device may join the bus
during this same DAA, and registering it immediately would push driver
model work (probing, sysfs, etc.) into the controller's resume context,
where the rest of the system is not yet fully resumed and the
controller driver is still partway through its own resume sequence.
Decouple discovery from registration: add a reg_work work item to
struct i3c_master_controller and have i3c_master_do_daa_ext() queue it
on master->wq (the freezable workqueue) instead of calling
i3c_master_register_new_i3c_devs() directly. The worker performs the
registration only when the controller is not shutting_down, and is
cancelled alongside hj_work in i3c_master_shutdown(). Because wq is
freezable, any newly observed devices end up being registered after
the system has finished resuming.
i3c_master_register() also routes its initial post-bus-init registration
through reg_work, using flush_work() to keep probe-time behavior
synchronous. This keeps a single registration code path and ensures the
worker is the only writer of desc->dev.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
drivers/i3c/master.c | 21 +++++++++++++++------
include/linux/i3c/master.h | 6 ++++++
2 files changed, 21 insertions(+), 6 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index a59c4b744b36..1eb157c8091d 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -839,6 +839,7 @@ static void i3c_master_shutdown(struct i3c_master_controller *master)
i3c_bus_maintenance_unlock(&master->bus);
cancel_work_sync(&master->hj_work);
+ cancel_work_sync(&master->reg_work);
}
static void i3c_device_shutdown(struct device *dev)
@@ -1838,6 +1839,16 @@ i3c_master_register_new_i3c_devs(struct i3c_master_controller *master)
}
}
+static void i3c_master_reg_work_fn(struct work_struct *work)
+{
+ struct i3c_master_controller *master = container_of(work, typeof(*master), reg_work);
+
+ i3c_bus_normaluse_lock(&master->bus);
+ if (!master->shutting_down)
+ i3c_master_register_new_i3c_devs(master);
+ i3c_bus_normaluse_unlock(&master->bus);
+}
+
/**
* i3c_master_do_daa_ext() - Dynamic Address Assignment (extended version)
* @master: controller
@@ -1878,9 +1889,7 @@ int i3c_master_do_daa_ext(struct i3c_master_controller *master, bool rstdaa)
if (ret)
goto out;
- i3c_bus_normaluse_lock(&master->bus);
- i3c_master_register_new_i3c_devs(master);
- i3c_bus_normaluse_unlock(&master->bus);
+ queue_work(master->wq, &master->reg_work);
out:
i3c_master_rpm_put(master);
@@ -3126,6 +3135,7 @@ int i3c_master_register(struct i3c_master_controller *master,
goto err_put_dev;
}
INIT_WORK(&master->hj_work, i3c_master_hj_work_fn);
+ INIT_WORK(&master->reg_work, i3c_master_reg_work_fn);
ret = i3c_master_bus_init(master);
if (ret)
@@ -3154,9 +3164,8 @@ int i3c_master_register(struct i3c_master_controller *master,
* register I3C devices discovered during the initial DAA.
*/
master->init_done = true;
- i3c_bus_normaluse_lock(&master->bus);
- i3c_master_register_new_i3c_devs(master);
- i3c_bus_normaluse_unlock(&master->bus);
+ queue_work(master->wq, &master->reg_work);
+ flush_work(&master->reg_work);
if (master->ops->set_dev_nack_retry)
device_create_file(&master->dev, &dev_attr_dev_nack_retry_count);
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index 77e63082b06e..8cdd7be505d3 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -523,6 +523,11 @@ struct i3c_master_controller_ops {
* be done from a sleep-able context
* @hj_work: work item used to run DAA after a Hot-Join event is detected.
* Queued to @wq by i3c_master_queue_hotjoin()
+ * @reg_work: work item used to register newly discovered I3C devices with
+ * the driver model. Queued to @wq by i3c_master_do_daa_ext() so
+ * that device registration is deferred out of the DAA caller's
+ * context (notably the resume path), and is skipped if the
+ * controller is shutting down
* @dev_nack_retry_count: retry count when slave device nack
*
* A &struct i3c_master_controller has to be registered to the I3C subsystem
@@ -548,6 +553,7 @@ struct i3c_master_controller {
struct i3c_bus bus;
struct workqueue_struct *wq;
struct work_struct hj_work;
+ struct work_struct reg_work;
unsigned int dev_nack_retry_count;
};
--
2.51.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH 6/8] i3c: master: Defer new-device registration out of DAA caller context
2026-05-12 12:17 ` [PATCH 6/8] i3c: master: Defer new-device registration out of DAA caller context Adrian Hunter
@ 2026-05-12 16:39 ` Frank Li
2026-05-13 5:45 ` Adrian Hunter
0 siblings, 1 reply; 24+ messages in thread
From: Frank Li @ 2026-05-12 16:39 UTC (permalink / raw)
To: Adrian Hunter; +Cc: alexandre.belloni, linux-i3c, linux-kernel
On Tue, May 12, 2026 at 03:17:30PM +0300, Adrian Hunter wrote:
> Master drivers may invoke i3c_master_do_daa_ext() during resume to
> re-run Dynamic Address Assignment. As well as assigning addresses to
> any newly arrived devices, this restores the dynamic address of devices
> that lost it across system suspend, so it has to run as part of the
> controller's resume path.
>
> A side effect of i3c_master_do_daa_ext() today is that it also
> registers any newly discovered I3C devices with the driver model
> inline, via i3c_master_register_new_i3c_devs(). Doing that from the
> resume path is problematic: a hot-join-capable device may join the bus
> during this same DAA, and registering it immediately would push driver
> model work (probing, sysfs, etc.) into the controller's resume context,
> where the rest of the system is not yet fully resumed and the
> controller driver is still partway through its own resume sequence.
>
> Decouple discovery from registration: add a reg_work work item to
> struct i3c_master_controller and have i3c_master_do_daa_ext() queue it
> on master->wq (the freezable workqueue) instead of calling
> i3c_master_register_new_i3c_devs() directly. The worker performs the
> registration only when the controller is not shutting_down, and is
> cancelled alongside hj_work in i3c_master_shutdown(). Because wq is
> freezable, any newly observed devices end up being registered after
> the system has finished resuming.
>
> i3c_master_register() also routes its initial post-bus-init registration
> through reg_work, using flush_work() to keep probe-time behavior
> synchronous. This keeps a single registration code path and ensures the
> worker is the only writer of desc->dev.
why not direct use hj_work?
Frank
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 6/8] i3c: master: Defer new-device registration out of DAA caller context
2026-05-12 16:39 ` Frank Li
@ 2026-05-13 5:45 ` Adrian Hunter
2026-05-13 10:20 ` David Nyström
0 siblings, 1 reply; 24+ messages in thread
From: Adrian Hunter @ 2026-05-13 5:45 UTC (permalink / raw)
To: Frank Li; +Cc: alexandre.belloni, linux-i3c, linux-kernel
On 12/05/2026 19:39, Frank Li wrote:
> On Tue, May 12, 2026 at 03:17:30PM +0300, Adrian Hunter wrote:
>> Master drivers may invoke i3c_master_do_daa_ext() during resume to
>> re-run Dynamic Address Assignment. As well as assigning addresses to
>> any newly arrived devices, this restores the dynamic address of devices
>> that lost it across system suspend, so it has to run as part of the
>> controller's resume path.
>>
>> A side effect of i3c_master_do_daa_ext() today is that it also
>> registers any newly discovered I3C devices with the driver model
>> inline, via i3c_master_register_new_i3c_devs(). Doing that from the
>> resume path is problematic: a hot-join-capable device may join the bus
>> during this same DAA, and registering it immediately would push driver
>> model work (probing, sysfs, etc.) into the controller's resume context,
>> where the rest of the system is not yet fully resumed and the
>> controller driver is still partway through its own resume sequence.
>>
>> Decouple discovery from registration: add a reg_work work item to
>> struct i3c_master_controller and have i3c_master_do_daa_ext() queue it
>> on master->wq (the freezable workqueue) instead of calling
>> i3c_master_register_new_i3c_devs() directly. The worker performs the
>> registration only when the controller is not shutting_down, and is
>> cancelled alongside hj_work in i3c_master_shutdown(). Because wq is
>> freezable, any newly observed devices end up being registered after
>> the system has finished resuming.
>>
>> i3c_master_register() also routes its initial post-bus-init registration
>> through reg_work, using flush_work() to keep probe-time behavior
>> synchronous. This keeps a single registration code path and ensures the
>> worker is the only writer of desc->dev.
>
> why not direct use hj_work?
i3c_master_register_new_i3c_devs() use of desc->dev is racy, so
i3c_master_register_new_i3c_devs() must not be allowed to race
with itself. Having it only ever run via reg_work achieves that.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 6/8] i3c: master: Defer new-device registration out of DAA caller context
2026-05-13 5:45 ` Adrian Hunter
@ 2026-05-13 10:20 ` David Nyström
0 siblings, 0 replies; 24+ messages in thread
From: David Nyström @ 2026-05-13 10:20 UTC (permalink / raw)
To: Adrian Hunter; +Cc: Frank Li, alexandre.belloni, linux-i3c, linux-kernel
On Wed, 13 May 2026, Adrian Hunter wrote:
> On 12/05/2026 19:39, Frank Li wrote:
>> On Tue, May 12, 2026 at 03:17:30PM +0300, Adrian Hunter wrote:
>>> Master drivers may invoke i3c_master_do_daa_ext() during resume to
>>> re-run Dynamic Address Assignment. As well as assigning addresses to
>>> any newly arrived devices, this restores the dynamic address of devices
>>> that lost it across system suspend, so it has to run as part of the
>>> controller's resume path.
>>>
>>> A side effect of i3c_master_do_daa_ext() today is that it also
>>> registers any newly discovered I3C devices with the driver model
>>> inline, via i3c_master_register_new_i3c_devs(). Doing that from the
>>> resume path is problematic: a hot-join-capable device may join the bus
>>> during this same DAA, and registering it immediately would push driver
>>> model work (probing, sysfs, etc.) into the controller's resume context,
>>> where the rest of the system is not yet fully resumed and the
>>> controller driver is still partway through its own resume sequence.
>>>
>>> Decouple discovery from registration: add a reg_work work item to
>>> struct i3c_master_controller and have i3c_master_do_daa_ext() queue it
>>> on master->wq (the freezable workqueue) instead of calling
>>> i3c_master_register_new_i3c_devs() directly. The worker performs the
>>> registration only when the controller is not shutting_down, and is
>>> cancelled alongside hj_work in i3c_master_shutdown(). Because wq is
>>> freezable, any newly observed devices end up being registered after
>>> the system has finished resuming.
>>>
>>> i3c_master_register() also routes its initial post-bus-init registration
>>> through reg_work, using flush_work() to keep probe-time behavior
>>> synchronous. This keeps a single registration code path and ensures the
>>> worker is the only writer of desc->dev.
>>
>> why not direct use hj_work?
>
> i3c_master_register_new_i3c_devs() use of desc->dev is racy, so
> i3c_master_register_new_i3c_devs() must not be allowed to race
> with itself. Having it only ever run via reg_work achieves that.
This race was introduced in
3a379bbcea0a ("i3c: Add core I3C infrastructure")
But since this path was exposed via sysfs in latest 7.1-rc via:
8ea0b60bc00d ("i3c: master: Add sysfs option to rescan bus via entdaa")
I would argue that we should revert the sysfs addition from 7.1-rc until
this fix is in place.
>
> --
> linux-i3c mailing list
> linux-i3c@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-i3c
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 7/8] i3c: master: Export i3c_master_enec_disec_locked()
2026-05-12 12:17 [PATCH 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
` (5 preceding siblings ...)
2026-05-12 12:17 ` [PATCH 6/8] i3c: master: Defer new-device registration out of DAA caller context Adrian Hunter
@ 2026-05-12 12:17 ` Adrian Hunter
2026-05-12 16:31 ` Frank Li
2026-05-12 12:17 ` [PATCH 8/8] i3c: mipi-i3c-hci: Add Hot-Join support Adrian Hunter
7 siblings, 1 reply; 24+ messages in thread
From: Adrian Hunter @ 2026-05-12 12:17 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, linux-i3c, linux-kernel
The existing i3c_master_enec_locked() wrapper always treats a NACKed
ENEC CCC as a failure (M2 error). However, broadcasting ENEC to enable
Hot-Join is legitimately useful even when no I3C devices are currently
present on the bus, in which case the broadcast will be NACKed and
should not be reported as an error.
The underlying helper i3c_master_enec_disec_locked() already accepts a
suppress_m2 flag that lets callers ignore such NACKs. Expose it so that
a subsequent patch enabling Hot-Join events can issue ENEC with M2
suppression.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
drivers/i3c/master.c | 27 ++++++++++++++++++++++++---
include/linux/i3c/master.h | 2 ++
2 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
index 1eb157c8091d..db46cc12c7db 100644
--- a/drivers/i3c/master.c
+++ b/drivers/i3c/master.c
@@ -1121,9 +1121,29 @@ int i3c_master_entdaa_locked(struct i3c_master_controller *master)
}
EXPORT_SYMBOL_GPL(i3c_master_entdaa_locked);
-static int i3c_master_enec_disec_locked(struct i3c_master_controller *master,
- u8 addr, bool enable, u8 evts,
- bool suppress_m2)
+/**
+ * i3c_master_enec_disec_locked() - send an ENEC or DISEC CCC command
+ * @master: master used to send frames on the bus
+ * @addr: a valid I3C slave address or %I3C_BROADCAST_ADDR
+ * @enable: true to send ENEC, false to send DISEC
+ * @evts: events to enable or disable
+ * @suppress_m2: if true, treat an M2 (NACK) error from the CCC as success
+ *
+ * Send an ENEC or DISEC CCC command to enable or disable some or all events
+ * coming from a specific slave, or all devices if @addr is
+ * %I3C_BROADCAST_ADDR.
+ *
+ * When @suppress_m2 is true, a NACK of the broadcast (which can happen when
+ * no devices are present on the bus) is not reported as an error. This is
+ * useful for callers that want to configure event reporting unconditionally,
+ * regardless of whether any devices are currently on the bus.
+ *
+ * This function must be called with the bus lock held in write mode.
+ *
+ * Return: 0 in case of success, or a negative error code otherwise.
+ */
+int i3c_master_enec_disec_locked(struct i3c_master_controller *master, u8 addr,
+ bool enable, u8 evts, bool suppress_m2)
{
struct i3c_ccc_events *events;
struct i3c_ccc_cmd_dest dest;
@@ -1148,6 +1168,7 @@ static int i3c_master_enec_disec_locked(struct i3c_master_controller *master,
return ret;
}
+EXPORT_SYMBOL_GPL(i3c_master_enec_disec_locked);
/**
* i3c_master_disec_locked() - send a DISEC CCC command
diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
index 8cdd7be505d3..e2c831fb5354 100644
--- a/include/linux/i3c/master.h
+++ b/include/linux/i3c/master.h
@@ -607,6 +607,8 @@ int i3c_master_disec_locked(struct i3c_master_controller *master, u8 addr,
u8 evts);
int i3c_master_enec_locked(struct i3c_master_controller *master, u8 addr,
u8 evts);
+int i3c_master_enec_disec_locked(struct i3c_master_controller *master, u8 addr,
+ bool enable, u8 evts, bool suppress_m2);
int i3c_master_entdaa_locked(struct i3c_master_controller *master);
int i3c_master_defslvs_locked(struct i3c_master_controller *master);
--
2.51.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH 7/8] i3c: master: Export i3c_master_enec_disec_locked()
2026-05-12 12:17 ` [PATCH 7/8] i3c: master: Export i3c_master_enec_disec_locked() Adrian Hunter
@ 2026-05-12 16:31 ` Frank Li
0 siblings, 0 replies; 24+ messages in thread
From: Frank Li @ 2026-05-12 16:31 UTC (permalink / raw)
To: Adrian Hunter; +Cc: alexandre.belloni, linux-i3c, linux-kernel
On Tue, May 12, 2026 at 03:17:31PM +0300, Adrian Hunter wrote:
> The existing i3c_master_enec_locked() wrapper always treats a NACKed
> ENEC CCC as a failure (M2 error). However, broadcasting ENEC to enable
> Hot-Join is legitimately useful even when no I3C devices are currently
> present on the bus, in which case the broadcast will be NACKed and
> should not be reported as an error.
>
> The underlying helper i3c_master_enec_disec_locked() already accepts a
> suppress_m2 flag that lets callers ignore such NACKs. Expose it so that
> a subsequent patch enabling Hot-Join events can issue ENEC with M2
> suppression.
>
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/i3c/master.c | 27 ++++++++++++++++++++++++---
> include/linux/i3c/master.h | 2 ++
> 2 files changed, 26 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/i3c/master.c b/drivers/i3c/master.c
> index 1eb157c8091d..db46cc12c7db 100644
> --- a/drivers/i3c/master.c
> +++ b/drivers/i3c/master.c
> @@ -1121,9 +1121,29 @@ int i3c_master_entdaa_locked(struct i3c_master_controller *master)
> }
> EXPORT_SYMBOL_GPL(i3c_master_entdaa_locked);
>
> -static int i3c_master_enec_disec_locked(struct i3c_master_controller *master,
> - u8 addr, bool enable, u8 evts,
> - bool suppress_m2)
> +/**
> + * i3c_master_enec_disec_locked() - send an ENEC or DISEC CCC command
> + * @master: master used to send frames on the bus
> + * @addr: a valid I3C slave address or %I3C_BROADCAST_ADDR
> + * @enable: true to send ENEC, false to send DISEC
> + * @evts: events to enable or disable
> + * @suppress_m2: if true, treat an M2 (NACK) error from the CCC as success
> + *
> + * Send an ENEC or DISEC CCC command to enable or disable some or all events
> + * coming from a specific slave, or all devices if @addr is
> + * %I3C_BROADCAST_ADDR.
> + *
> + * When @suppress_m2 is true, a NACK of the broadcast (which can happen when
> + * no devices are present on the bus) is not reported as an error. This is
> + * useful for callers that want to configure event reporting unconditionally,
> + * regardless of whether any devices are currently on the bus.
> + *
> + * This function must be called with the bus lock held in write mode.
> + *
> + * Return: 0 in case of success, or a negative error code otherwise.
> + */
> +int i3c_master_enec_disec_locked(struct i3c_master_controller *master, u8 addr,
> + bool enable, u8 evts, bool suppress_m2)
> {
> struct i3c_ccc_events *events;
> struct i3c_ccc_cmd_dest dest;
> @@ -1148,6 +1168,7 @@ static int i3c_master_enec_disec_locked(struct i3c_master_controller *master,
>
> return ret;
> }
> +EXPORT_SYMBOL_GPL(i3c_master_enec_disec_locked);
>
> /**
> * i3c_master_disec_locked() - send a DISEC CCC command
> diff --git a/include/linux/i3c/master.h b/include/linux/i3c/master.h
> index 8cdd7be505d3..e2c831fb5354 100644
> --- a/include/linux/i3c/master.h
> +++ b/include/linux/i3c/master.h
> @@ -607,6 +607,8 @@ int i3c_master_disec_locked(struct i3c_master_controller *master, u8 addr,
> u8 evts);
> int i3c_master_enec_locked(struct i3c_master_controller *master, u8 addr,
> u8 evts);
> +int i3c_master_enec_disec_locked(struct i3c_master_controller *master, u8 addr,
> + bool enable, u8 evts, bool suppress_m2);
> int i3c_master_entdaa_locked(struct i3c_master_controller *master);
> int i3c_master_defslvs_locked(struct i3c_master_controller *master);
>
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 8/8] i3c: mipi-i3c-hci: Add Hot-Join support
2026-05-12 12:17 [PATCH 0/8] i3c: Hot-Join improvements and MIPI HCI Hot-Join support Adrian Hunter
` (6 preceding siblings ...)
2026-05-12 12:17 ` [PATCH 7/8] i3c: master: Export i3c_master_enec_disec_locked() Adrian Hunter
@ 2026-05-12 12:17 ` Adrian Hunter
2026-05-12 16:34 ` Frank Li
7 siblings, 1 reply; 24+ messages in thread
From: Adrian Hunter @ 2026-05-12 12:17 UTC (permalink / raw)
To: alexandre.belloni; +Cc: Frank.Li, linux-i3c, linux-kernel
Wire the MIPI I3C HCI driver into the I3C core Hot-Join framework to
allow targets to dynamically join the bus after initial DAA.
HCI hardware ACKs or NACKs Hot-Join requests based on
HC_CONTROL.HOT_JOIN_CTRL. This was previously left in the
NACK-and-DISEC state, effectively preventing Hot-Join. Implement
the ->enable_hotjoin() and ->disable_hotjoin() master operations
so the core and user space can control this policy at runtime.
Also issue broadcast ENEC HJ when enabling Hot-Join. This is required
because the controller may have previously DISEC'ed the Hot-Join
event, causing targets that were NACKed once to never retry.
Acknowledged Hot-Join requests are delivered as IBIs on the reserved
address 0x02. Update both the DMA and PIO IBI paths to recognise this
address and forward the event to i3c_master_queue_hotjoin().
To make Hot-Join usable by default, enable it once after the initial
DAA. This is gated by rpm_ibi_allowed, since otherwise keeping Hot-Join
enabled prevents runtime suspend. A new hj_init_done flag ensures this
one-time enablement is not repeated on subsequent DAAs.
Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
---
drivers/i3c/master/mipi-i3c-hci/core.c | 50 ++++++++++++++++++++++++--
drivers/i3c/master/mipi-i3c-hci/dma.c | 5 +++
drivers/i3c/master/mipi-i3c-hci/hci.h | 1 +
drivers/i3c/master/mipi-i3c-hci/pio.c | 5 +++
4 files changed, 58 insertions(+), 3 deletions(-)
diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
index 2866d599612a..f8b399d16598 100644
--- a/drivers/i3c/master/mipi-i3c-hci/core.c
+++ b/drivers/i3c/master/mipi-i3c-hci/core.c
@@ -392,11 +392,52 @@ static int i3c_hci_send_ccc_cmd(struct i3c_master_controller *m,
return ret;
}
+static int i3c_hci_enable_hotjoin(struct i3c_master_controller *m)
+{
+ struct i3c_hci *hci = to_i3c_hci(m);
+ int ret;
+
+ reg_clear(HC_CONTROL, HC_CONTROL_HOT_JOIN_CTRL);
+
+ /*
+ * Broadcast Hot_join enable, so that an I3C device that has previously
+ * had its Hot-Join request NACK'ed knows to try again.
+ */
+ ret = i3c_master_enec_disec_locked(m, I3C_BROADCAST_ADDR, true, I3C_CCC_EVENT_HJ, true);
+ if (ret) {
+ reg_set(HC_CONTROL, HC_CONTROL_HOT_JOIN_CTRL);
+ dev_err(&hci->master.dev, "Hot-Join ENEC CCC failed\n");
+ }
+
+ return ret;
+}
+
+static int i3c_hci_disable_hotjoin(struct i3c_master_controller *m)
+{
+ struct i3c_hci *hci = to_i3c_hci(m);
+
+ reg_set(HC_CONTROL, HC_CONTROL_HOT_JOIN_CTRL);
+ return 0;
+}
+
static int i3c_hci_daa(struct i3c_master_controller *m)
{
struct i3c_hci *hci = to_i3c_hci(m);
+ int ret;
- return hci->cmd->perform_daa(hci);
+ ret = hci->cmd->perform_daa(hci);
+
+ if (!hci->hj_init_done) {
+ hci->hj_init_done = true;
+ /*
+ * Enable Hot-Join by default after initial DAA if it does not
+ * prevent runtime suspend.
+ */
+ if (m->rpm_ibi_allowed && !ret)
+ m->hotjoin = !i3c_hci_enable_hotjoin(m);
+ }
+
+ return ret;
}
static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev,
@@ -652,6 +693,8 @@ static const struct i3c_master_controller_ops i3c_hci_ops = {
.enable_ibi = i3c_hci_enable_ibi,
.disable_ibi = i3c_hci_disable_ibi,
.recycle_ibi_slot = i3c_hci_recycle_ibi_slot,
+ .enable_hotjoin = i3c_hci_enable_hotjoin,
+ .disable_hotjoin = i3c_hci_disable_hotjoin,
};
static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
@@ -833,8 +876,9 @@ static int i3c_hci_do_reset_and_restore(struct i3c_hci *hci)
scoped_guard(spinlock_irqsave, &hci->lock)
hci->irq_inactive = false;
- /* Enable bus with Hot-Join disabled */
- reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE | HC_CONTROL_HOT_JOIN_CTRL);
+ /* Enable bus, restoring hot-join state */
+ reg_set(HC_CONTROL,
+ HC_CONTROL_BUS_ENABLE | (hci->master.hotjoin ? 0 : HC_CONTROL_HOT_JOIN_CTRL));
return 0;
}
diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c
index e5deeba0aa4e..34129ac039dc 100644
--- a/drivers/i3c/master/mipi-i3c-hci/dma.c
+++ b/drivers/i3c/master/mipi-i3c-hci/dma.c
@@ -971,6 +971,11 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh)
}
/* determine who this is for */
+ if (ibi_addr == I3C_HOT_JOIN_ADDR) {
+ i3c_master_queue_hotjoin(&hci->master);
+ goto done;
+ }
+
dev = i3c_hci_addr_to_dev(hci, ibi_addr);
if (!dev) {
dev_err(&hci->master.dev,
diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h
index 243d7a67f6f6..591eea040b01 100644
--- a/drivers/i3c/master/mipi-i3c-hci/hci.h
+++ b/drivers/i3c/master/mipi-i3c-hci/hci.h
@@ -57,6 +57,7 @@ struct i3c_hci {
bool irq_inactive;
bool enqueue_blocked;
bool recovery_needed;
+ bool hj_init_done;
wait_queue_head_t enqueue_wait_queue;
u32 caps;
unsigned int quirks;
diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c
index 6b8cc5f2b4d2..b5ae1cfaa9e0 100644
--- a/drivers/i3c/master/mipi-i3c-hci/pio.c
+++ b/drivers/i3c/master/mipi-i3c-hci/pio.c
@@ -862,6 +862,11 @@ static bool hci_pio_prep_new_ibi(struct i3c_hci *hci, struct hci_pio_data *pio)
ibi->seg_len = FIELD_GET(IBI_DATA_LENGTH, ibi_status);
ibi->seg_cnt = ibi->seg_len;
+ if (ibi->addr == I3C_HOT_JOIN_ADDR) {
+ i3c_master_queue_hotjoin(&hci->master);
+ return true;
+ }
+
dev = i3c_hci_addr_to_dev(hci, ibi->addr);
if (!dev) {
dev_err(&hci->master.dev,
--
2.51.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH 8/8] i3c: mipi-i3c-hci: Add Hot-Join support
2026-05-12 12:17 ` [PATCH 8/8] i3c: mipi-i3c-hci: Add Hot-Join support Adrian Hunter
@ 2026-05-12 16:34 ` Frank Li
0 siblings, 0 replies; 24+ messages in thread
From: Frank Li @ 2026-05-12 16:34 UTC (permalink / raw)
To: Adrian Hunter; +Cc: alexandre.belloni, linux-i3c, linux-kernel
On Tue, May 12, 2026 at 03:17:32PM +0300, Adrian Hunter wrote:
> Wire the MIPI I3C HCI driver into the I3C core Hot-Join framework to
> allow targets to dynamically join the bus after initial DAA.
>
> HCI hardware ACKs or NACKs Hot-Join requests based on
> HC_CONTROL.HOT_JOIN_CTRL. This was previously left in the
> NACK-and-DISEC state, effectively preventing Hot-Join. Implement
> the ->enable_hotjoin() and ->disable_hotjoin() master operations
> so the core and user space can control this policy at runtime.
>
> Also issue broadcast ENEC HJ when enabling Hot-Join. This is required
> because the controller may have previously DISEC'ed the Hot-Join
> event, causing targets that were NACKed once to never retry.
>
> Acknowledged Hot-Join requests are delivered as IBIs on the reserved
> address 0x02. Update both the DMA and PIO IBI paths to recognise this
> address and forward the event to i3c_master_queue_hotjoin().
>
> To make Hot-Join usable by default, enable it once after the initial
> DAA. This is gated by rpm_ibi_allowed, since otherwise keeping Hot-Join
> enabled prevents runtime suspend. A new hj_init_done flag ensures this
> one-time enablement is not repeated on subsequent DAAs.
>
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
Reviewed-by: Frank Li <Frank.Li@nxp.com>
> drivers/i3c/master/mipi-i3c-hci/core.c | 50 ++++++++++++++++++++++++--
> drivers/i3c/master/mipi-i3c-hci/dma.c | 5 +++
> drivers/i3c/master/mipi-i3c-hci/hci.h | 1 +
> drivers/i3c/master/mipi-i3c-hci/pio.c | 5 +++
> 4 files changed, 58 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/i3c/master/mipi-i3c-hci/core.c b/drivers/i3c/master/mipi-i3c-hci/core.c
> index 2866d599612a..f8b399d16598 100644
> --- a/drivers/i3c/master/mipi-i3c-hci/core.c
> +++ b/drivers/i3c/master/mipi-i3c-hci/core.c
> @@ -392,11 +392,52 @@ static int i3c_hci_send_ccc_cmd(struct i3c_master_controller *m,
> return ret;
> }
>
> +static int i3c_hci_enable_hotjoin(struct i3c_master_controller *m)
> +{
> + struct i3c_hci *hci = to_i3c_hci(m);
> + int ret;
> +
> + reg_clear(HC_CONTROL, HC_CONTROL_HOT_JOIN_CTRL);
> +
> + /*
> + * Broadcast Hot_join enable, so that an I3C device that has previously
> + * had its Hot-Join request NACK'ed knows to try again.
> + */
> + ret = i3c_master_enec_disec_locked(m, I3C_BROADCAST_ADDR, true, I3C_CCC_EVENT_HJ, true);
> + if (ret) {
> + reg_set(HC_CONTROL, HC_CONTROL_HOT_JOIN_CTRL);
> + dev_err(&hci->master.dev, "Hot-Join ENEC CCC failed\n");
> + }
> +
> + return ret;
> +}
> +
> +static int i3c_hci_disable_hotjoin(struct i3c_master_controller *m)
> +{
> + struct i3c_hci *hci = to_i3c_hci(m);
> +
> + reg_set(HC_CONTROL, HC_CONTROL_HOT_JOIN_CTRL);
> + return 0;
> +}
> +
> static int i3c_hci_daa(struct i3c_master_controller *m)
> {
> struct i3c_hci *hci = to_i3c_hci(m);
> + int ret;
>
> - return hci->cmd->perform_daa(hci);
> + ret = hci->cmd->perform_daa(hci);
> +
> + if (!hci->hj_init_done) {
> + hci->hj_init_done = true;
> + /*
> + * Enable Hot-Join by default after initial DAA if it does not
> + * prevent runtime suspend.
> + */
> + if (m->rpm_ibi_allowed && !ret)
> + m->hotjoin = !i3c_hci_enable_hotjoin(m);
> + }
> +
> + return ret;
> }
>
> static int i3c_hci_i3c_xfers(struct i3c_dev_desc *dev,
> @@ -652,6 +693,8 @@ static const struct i3c_master_controller_ops i3c_hci_ops = {
> .enable_ibi = i3c_hci_enable_ibi,
> .disable_ibi = i3c_hci_disable_ibi,
> .recycle_ibi_slot = i3c_hci_recycle_ibi_slot,
> + .enable_hotjoin = i3c_hci_enable_hotjoin,
> + .disable_hotjoin = i3c_hci_disable_hotjoin,
> };
>
> static irqreturn_t i3c_hci_irq_handler(int irq, void *dev_id)
> @@ -833,8 +876,9 @@ static int i3c_hci_do_reset_and_restore(struct i3c_hci *hci)
> scoped_guard(spinlock_irqsave, &hci->lock)
> hci->irq_inactive = false;
>
> - /* Enable bus with Hot-Join disabled */
> - reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE | HC_CONTROL_HOT_JOIN_CTRL);
> + /* Enable bus, restoring hot-join state */
> + reg_set(HC_CONTROL,
> + HC_CONTROL_BUS_ENABLE | (hci->master.hotjoin ? 0 : HC_CONTROL_HOT_JOIN_CTRL));
>
> return 0;
> }
> diff --git a/drivers/i3c/master/mipi-i3c-hci/dma.c b/drivers/i3c/master/mipi-i3c-hci/dma.c
> index e5deeba0aa4e..34129ac039dc 100644
> --- a/drivers/i3c/master/mipi-i3c-hci/dma.c
> +++ b/drivers/i3c/master/mipi-i3c-hci/dma.c
> @@ -971,6 +971,11 @@ static void hci_dma_process_ibi(struct i3c_hci *hci, struct hci_rh_data *rh)
> }
>
> /* determine who this is for */
> + if (ibi_addr == I3C_HOT_JOIN_ADDR) {
> + i3c_master_queue_hotjoin(&hci->master);
> + goto done;
> + }
> +
> dev = i3c_hci_addr_to_dev(hci, ibi_addr);
> if (!dev) {
> dev_err(&hci->master.dev,
> diff --git a/drivers/i3c/master/mipi-i3c-hci/hci.h b/drivers/i3c/master/mipi-i3c-hci/hci.h
> index 243d7a67f6f6..591eea040b01 100644
> --- a/drivers/i3c/master/mipi-i3c-hci/hci.h
> +++ b/drivers/i3c/master/mipi-i3c-hci/hci.h
> @@ -57,6 +57,7 @@ struct i3c_hci {
> bool irq_inactive;
> bool enqueue_blocked;
> bool recovery_needed;
> + bool hj_init_done;
> wait_queue_head_t enqueue_wait_queue;
> u32 caps;
> unsigned int quirks;
> diff --git a/drivers/i3c/master/mipi-i3c-hci/pio.c b/drivers/i3c/master/mipi-i3c-hci/pio.c
> index 6b8cc5f2b4d2..b5ae1cfaa9e0 100644
> --- a/drivers/i3c/master/mipi-i3c-hci/pio.c
> +++ b/drivers/i3c/master/mipi-i3c-hci/pio.c
> @@ -862,6 +862,11 @@ static bool hci_pio_prep_new_ibi(struct i3c_hci *hci, struct hci_pio_data *pio)
> ibi->seg_len = FIELD_GET(IBI_DATA_LENGTH, ibi_status);
> ibi->seg_cnt = ibi->seg_len;
>
> + if (ibi->addr == I3C_HOT_JOIN_ADDR) {
> + i3c_master_queue_hotjoin(&hci->master);
> + return true;
> + }
> +
> dev = i3c_hci_addr_to_dev(hci, ibi->addr);
> if (!dev) {
> dev_err(&hci->master.dev,
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 24+ messages in thread