[lvc-project] [PATCH v3 1/2] can: j1939: make hrtimer callback lockless and free ECUs asyncronously
Dmitry Antipov
dmantipov at yandex.ru
Fri Aug 28 09:46:20 MSK 2026
In j1939_ecu_map_locked(), it is not needed to lock 'struct j1939_priv'
just to claim ECU slot. This may be accomplished by using cmpxchg() on
ECU slot pointer. This way, convert j1939_ecu_map_locked() to
j1939_ecu_map_atomic() and make j1939_ecu_timer_handler() lockless.
Now, if hrtimer handler is lockless, the following calls from multiple
handlers may trigger concurrent modifications of ECU list:
-> j1939_ecu_timer_handler()
-> j1939_ecu_put()
-> __j1939_ecu_release()
-> list_del()
To avoid this, offload actual ECU removal to schedule_work(). Since
the work handler runs in process context, this is a way better place
to perform an operation which requires 'struct j1939_priv' locking
(and suits PREEMPT_RT better as well).
Signed-off-by: Dmitry Antipov <dmantipov at yandex.ru>
---
v3: initial version to join the series
---
net/can/j1939/bus.c | 27 ++++++++++++++++-----------
net/can/j1939/j1939-priv.h | 1 +
2 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/net/can/j1939/bus.c b/net/can/j1939/bus.c
index cdc3c0a71937..a347594b8c1c 100644
--- a/net/can/j1939/bus.c
+++ b/net/can/j1939/bus.c
@@ -14,17 +14,27 @@
#include "j1939-priv.h"
-static void __j1939_ecu_release(struct kref *kref)
+static void __j1939_ecu_work(struct work_struct *work)
{
- struct j1939_ecu *ecu = container_of(kref, struct j1939_ecu, kref);
+ struct j1939_ecu *ecu = container_of(work, struct j1939_ecu, work);
struct j1939_priv *priv = ecu->priv;
+ write_lock(&priv->lock);
list_del(&ecu->list);
netdev_put(priv->ndev, &ecu->priv_dev_tracker);
kfree(ecu);
+ write_unlock(&priv->lock);
+
j1939_priv_put(priv);
}
+static void __j1939_ecu_release(struct kref *kref)
+{
+ struct j1939_ecu *ecu = container_of(kref, struct j1939_ecu, kref);
+
+ schedule_work(&ecu->work);
+}
+
void j1939_ecu_put(struct j1939_ecu *ecu)
{
kref_put(&ecu->kref, __j1939_ecu_release);
@@ -46,26 +56,23 @@ static bool j1939_ecu_is_mapped_locked(struct j1939_ecu *ecu)
/* ECU device interface */
/* map ECU to a bus address space */
-static void j1939_ecu_map_locked(struct j1939_ecu *ecu)
+static void j1939_ecu_map_atomic(struct j1939_ecu *ecu)
{
struct j1939_priv *priv = ecu->priv;
struct j1939_addr_ent *ent;
- lockdep_assert_held(&priv->lock);
-
if (!j1939_address_is_unicast(ecu->addr))
return;
ent = &priv->ents[ecu->addr];
- if (ent->ecu) {
+ if (cmpxchg(&ent->ecu, NULL, ecu)) {
netdev_warn(priv->ndev, "Trying to map already mapped ECU, addr: 0x%02x, name: 0x%016llx. Skip it.\n",
ecu->addr, ecu->name);
return;
}
j1939_ecu_get(ecu);
- ent->ecu = ecu;
ent->nusers += ecu->nusers;
}
@@ -129,19 +136,16 @@ static enum hrtimer_restart j1939_ecu_timer_handler(struct hrtimer *hrtimer)
{
struct j1939_ecu *ecu =
container_of(hrtimer, struct j1939_ecu, ac_timer);
- struct j1939_priv *priv = ecu->priv;
- write_lock_bh(&priv->lock);
/* TODO: can we test if ecu->addr is unicast before starting
* the timer?
*/
- j1939_ecu_map_locked(ecu);
+ j1939_ecu_map_atomic(ecu);
/* The corresponding j1939_ecu_get() is in
* j1939_ecu_timer_start().
*/
j1939_ecu_put(ecu);
- write_unlock_bh(&priv->lock);
return HRTIMER_NORESTART;
}
@@ -156,6 +160,7 @@ struct j1939_ecu *j1939_ecu_create_locked(struct j1939_priv *priv, name_t name)
if (!ecu)
return ERR_PTR(-ENOMEM);
kref_init(&ecu->kref);
+ INIT_WORK(&ecu->work, __j1939_ecu_work);
netdev_hold(priv->ndev, &ecu->priv_dev_tracker, gfp_any());
ecu->addr = J1939_IDLE_ADDR;
ecu->name = name;
diff --git a/net/can/j1939/j1939-priv.h b/net/can/j1939/j1939-priv.h
index cf26352d1d8c..080d5b4c8c23 100644
--- a/net/can/j1939/j1939-priv.h
+++ b/net/can/j1939/j1939-priv.h
@@ -37,6 +37,7 @@ struct j1939_ecu {
/* indicates that this ecu successfully claimed @sa as its address */
struct hrtimer ac_timer;
struct kref kref;
+ struct work_struct work;
struct j1939_priv *priv;
netdevice_tracker priv_dev_tracker;
--
2.55.0
More information about the lvc-project
mailing list