Skip to contentSkip to conversation

can: bcm: use call_rcu() instead of costly synchronize_rcu()

RFC PATCH In review Oliver Hartkopp 1 participant 2 messages May 20, 2022 – Jul 4, 2022

Subsystems (2)
lore
Share & tools
lore

Oliver Hartkopp

author maintainer
#0 lore raw

RFC PATCH 1/1 can: bcm: use call_rcu() instead of costly synchronize_rcu()

In commit d5f9023fa61e ("can: bcm: delay release of struct bcm_op after synchronize_rcu()") Thadeu Lima de Souza Cascardo introduced two synchronize_rcu() calls in bcm_release() (only once at socket close) and in bcm_delete_rx_op() (called on removal of each single bcm_op).

Unfortunately this slow removal of the bcm_op's affects user space applications like cansniffer where the modification of a filter removes 2048 bcm_op's which blocks the cansniffer application for 40(!) seconds.

In commit 181d4447905d ("can: gw: use call_rcu() instead of costly synchronize_rcu()") Eric Dumazet replaced the synchronize_rcu() calls with several call_rcu()'s to safely remove the data structures after the removal of CAN ID subscriptions with can_rx_unregister() calls.

This patch adopts Erics approach for the can-bcm which should be applicable since the removal of tasklet_kill() in bcm_remove_op() and the introduction of the HRTIMER_MODE_SOFT timer handling in Linux 5.4.

Fixes d5f9023fa61e ("can: bcm: delay release of struct bcm_op after synchronize_rcu()") # >= 5.4
Cc Eric Dumazet <[email protected]>
Cc Norbert Slusarek <[email protected]>
Cc Thadeu Lima de Souza Cascardo <[email protected]>
---
 net/can/bcm.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)
net/can/bcm.c +14 -4
index 65ee1b784a30..e60161bec850 100644
@@ -98,10 +98,11 @@ static inline u64 get_u64(const struct canfd_frame *cp, int offset)
 	return *(u64 *)(cp->data + offset);
 }
 
 struct bcm_op {
 	struct list_head list;
+	struct rcu_head rcu;
 	int ifindex;
 	canid_t can_id;
 	u32 flags;
 	unsigned long frames_abs, frames_filtered;
 	struct bcm_timeval ival1, ival2;
@@ -716,24 +717,31 @@ static struct bcm_op *bcm_find_op(struct list_head *ops,
 	}
 
 	return NULL;
 }
 
-static void bcm_remove_op(struct bcm_op *op)
+static void bcm_free_op_rcu(struct rcu_head *rcu_head)
 {
-	hrtimer_cancel(&op->timer);
-	hrtimer_cancel(&op->thrtimer);
+	struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu);
 
 	if ((op->frames) && (op->frames != &op->sframe))
 		kfree(op->frames);
 
 	if ((op->last_frames) && (op->last_frames != &op->last_sframe))
 		kfree(op->last_frames);
 
 	kfree(op);
 }
 
+static void bcm_remove_op(struct bcm_op *op)
+{
+	hrtimer_cancel(&op->timer);
+	hrtimer_cancel(&op->thrtimer);
+
+	call_rcu(&op->rcu, bcm_free_op_rcu);
+}
+
 static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op)
 {
 	if (op->rx_reg_dev == dev) {
 		can_rx_unregister(dev_net(dev), dev, op->can_id,
 				  REGMASK(op->can_id), bcm_rx_handler, op);
@@ -755,10 +763,13 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh,
 
 	list_for_each_entry_safe(op, n, ops, list) {
 		if ((op->can_id == mh->can_id) && (op->ifindex == ifindex) &&
 		    (op->flags & CAN_FD_FRAME) == (mh->flags & CAN_FD_FRAME)) {
 
+			/* disable automatic timer on frame reception */
+			op->flags |= RX_NO_AUTOTIMER;
+
 			/*
 			 * Don't care if we're bound or not (due to netdev
 			 * problems) can_rx_unregister() is always a save
 			 * thing to do here.
 			 */
@@ -783,11 +794,10 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh,
 						  op->can_id,
 						  REGMASK(op->can_id),
 						  bcm_rx_handler, op);
 
 			list_del(&op->list);
-			synchronize_rcu();
 			bcm_remove_op(op);
 			return 1; /* done */
 		}
 	}
 
Signature · 1 line
2.30.2
author maintainer
#1 lore raw

Hello Marc,

unfortunately I did not get any feedback/review on this patch from the original authors.

But I think this fix should go into Linux 5.4+

Best regards,
Oliver

Quoted 104 lines · Oliver Hartkopp

In commit d5f9023fa61e ("can: bcm: delay release of struct bcm_op after synchronize_rcu()") Thadeu Lima de Souza Cascardo introduced two synchronize_rcu() calls in bcm_release() (only once at socket close) and in bcm_delete_rx_op() (called on removal of each single bcm_op).

Unfortunately this slow removal of the bcm_op's affects user space applications like cansniffer where the modification of a filter removes 2048 bcm_op's which blocks the cansniffer application for 40(!) seconds.

In commit 181d4447905d ("can: gw: use call_rcu() instead of costly synchronize_rcu()") Eric Dumazet replaced the synchronize_rcu() calls with several call_rcu()'s to safely remove the data structures after the removal of CAN ID subscriptions with can_rx_unregister() calls.

This patch adopts Erics approach for the can-bcm which should be applicable since the removal of tasklet_kill() in bcm_remove_op() and the introduction of the HRTIMER_MODE_SOFT timer handling in Linux 5.4.

Fixes d5f9023fa61e ("can: bcm: delay release of struct bcm_op after synchronize_rcu()") # >= 5.4
Cc Eric Dumazet <[email protected]>
Cc Norbert Slusarek <[email protected]>
Cc Thadeu Lima de Souza Cascardo <[email protected]>
---
  net/can/bcm.c | 18 ++++++++++++++----
  1 file changed, 14 insertions(+), 4 deletions(-)
net/can/bcm.c +14 -4
index 65ee1b784a30..e60161bec850 100644
@@ -98,10 +98,11 @@ static inline u64 get_u64(const struct canfd_frame *cp, int offset)
  	return *(u64 *)(cp->data + offset);
  }
 
  struct bcm_op {
  	struct list_head list;
+	struct rcu_head rcu;
  	int ifindex;
  	canid_t can_id;
  	u32 flags;
  	unsigned long frames_abs, frames_filtered;
  	struct bcm_timeval ival1, ival2;
@@ -716,24 +717,31 @@ static struct bcm_op *bcm_find_op(struct list_head *ops,
  	}
 
  	return NULL;
  }
 
-static void bcm_remove_op(struct bcm_op *op)
+static void bcm_free_op_rcu(struct rcu_head *rcu_head)
  {
-	hrtimer_cancel(&op->timer);
-	hrtimer_cancel(&op->thrtimer);
+	struct bcm_op *op = container_of(rcu_head, struct bcm_op, rcu);
 
  	if ((op->frames) && (op->frames != &op->sframe))
  		kfree(op->frames);
 
  	if ((op->last_frames) && (op->last_frames != &op->last_sframe))
  		kfree(op->last_frames);
 
  	kfree(op);
  }
 
+static void bcm_remove_op(struct bcm_op *op)
+{
+	hrtimer_cancel(&op->timer);
+	hrtimer_cancel(&op->thrtimer);
+
+	call_rcu(&op->rcu, bcm_free_op_rcu);
+}
+
  static void bcm_rx_unreg(struct net_device *dev, struct bcm_op *op)
  {
  	if (op->rx_reg_dev == dev) {
  		can_rx_unregister(dev_net(dev), dev, op->can_id,
  				  REGMASK(op->can_id), bcm_rx_handler, op);
@@ -755,10 +763,13 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh,
 
  	list_for_each_entry_safe(op, n, ops, list) {
  		if ((op->can_id == mh->can_id) && (op->ifindex == ifindex) &&
  		    (op->flags & CAN_FD_FRAME) == (mh->flags & CAN_FD_FRAME)) {
 
+			/* disable automatic timer on frame reception */
+			op->flags |= RX_NO_AUTOTIMER;
+
  			/*
  			 * Don't care if we're bound or not (due to netdev
  			 * problems) can_rx_unregister() is always a save
  			 * thing to do here.
  			 */
@@ -783,11 +794,10 @@ static int bcm_delete_rx_op(struct list_head *ops, struct bcm_msg_head *mh,
  						  op->can_id,
  						  REGMASK(op->can_id),
  						  bcm_rx_handler, op);
 
  			list_del(&op->list);
-			synchronize_rcu();
  			bcm_remove_op(op);
  			return 1; /* done */
  		}
  	}