From 620f803f4e7031e1f94aa8a23e4f410b14aff4dd Mon Sep 17 00:00:00 2001 From: Koki Nakazawa Date: Tue, 11 Aug 2026 07:48:30 +0900 Subject: [PATCH] Add listen-only mode support via ip link (issue #25) Advertise CAN_CTRLMODE_LISTENONLY when a v3.2+ usermode daemon is connected, and report the control mode to the daemon alongside the bitrate. Because can_changelink() stores ctrlmode without calling into the driver (and requires the interface to be down), the accumulated configuration is reported from ndo_open. The report is skipped when the bitrate is still zero or when nothing changed since the last report, so repeated ifup/ifdown cycles do not re-apply unchanged settings to the hardware. Baudrates seeded by the daemon via SIOCSBAUDRATE count as already reported for the same reason. The listen-only flag is carried in a spare high bit of the existing intrepid_pending_tx_info.count field; bitrate_const caps arbitration bitrates at 1 Mbit/s so the flag cannot collide with a bitrate value. The record layout and the read() protocol are unchanged, and nothing new is sent to daemons that do not announce v3.2+. This encoding is a proposal and pairs with the corresponding icsscand change, which masks the flag before using the bitrate. The bitrate_changed test-then-clear in check_bitrate_change() is now an xchg(), closing the pre-existing window in which a set landing between the test and the clear could be lost, with WRITE_ONCE/READ_ONCE at the setters and the poll() reader. Signed-off-by: Koki Nakazawa --- intrepid.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/intrepid.c b/intrepid.c index 6c5726b..330e2da 100644 --- a/intrepid.c +++ b/intrepid.c @@ -99,6 +99,14 @@ MODULE_VERSION(KO_VERSION); #define SIOCGCLIENTVEROK 0x3009 #define SIOCSBAUDRATE 0x300A +/* Flag bit encoded into intrepid_pending_tx_info.count when reporting a + * settings change to the usermode daemon (tx_box_index < 0). The low bits + * carry the arbitration bitrate, which bitrate_const caps at 1 Mbit/s, so + * the flag cannot collide with a real bitrate value. Only sent to daemons + * that announce v3.2+ via SIOCGCLIENTVEROK. + */ +#define INTREPID_BITRATE_FLAG_LISTENONLY BIT(30) + /* This is true until we have Ethernet support * It is used to stop the netif queues before we have to return NETDEV_TX_BUSY */ @@ -128,6 +136,8 @@ struct intrepid_netdevice { unsigned char *from_user; uint8_t tx_idx; int bitrate_changed; + u32 last_reported_bitrate; + int last_reported_listenonly; struct sk_buff *tx_skbs[MAX_TX]; }; @@ -369,6 +379,26 @@ static int intrepid_netdevice_stop(struct net_device *dev) static int intrepid_netdevice_open(struct net_device *dev) { + /* can_changelink() stores ctrlmode changes (e.g. listen-only) without + * calling into the driver, and requires the interface to be down, so + * ndo_open is the point where the accumulated CAN configuration is + * final. Report it to the usermode daemon, but only when it differs + * from what the daemon was already told, so repeated ifup/ifdown + * cycles do not re-apply unchanged settings to the hardware. + */ + if (dev->type == ARPHRD_CAN && VER_MIN_FROM_INT(client_version) > 1) { + struct intrepid_netdevice *ics = netdev_priv(dev); + u32 bitrate = ics->can.bittiming.bitrate; + int listenonly = (ics->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) ? 1 : 0; + + if (bitrate != 0 && + (bitrate != ics->last_reported_bitrate || + listenonly != ics->last_reported_listenonly)) { + WRITE_ONCE(ics->bitrate_changed, 1); + wake_up_interruptible(&tx_wait); + } + } + netif_start_queue(dev); netif_carrier_on(dev); return 0; @@ -433,7 +463,7 @@ static int intrepid_set_bittiming(struct net_device *netdev) dev_dbg(&netdev->dev, "bitrate %d sample_point %d tq %d sjw %d phase1 %d phase2 %d prop %d brp %d", bt->bitrate, bt->sample_point, bt->tq, bt->sjw, bt->phase_seg1, bt->phase_seg2, bt->prop_seg, bt->brp); - dev->bitrate_changed = 1; + WRITE_ONCE(dev->bitrate_changed, 1); wake_up_interruptible(&tx_wait); return 0; } @@ -450,7 +480,7 @@ static int intrepid_set_data_bittiming(struct net_device *netdev) dev_dbg(&netdev->dev, "bitrate %d sample_point %d tq %d sjw %d phase1 %d phase2 %d prop %d brp %d", bt->bitrate, bt->sample_point, bt->tq, bt->sjw, bt->phase_seg1, bt->phase_seg2, bt->prop_seg, bt->brp); - dev->bitrate_changed = 1; + WRITE_ONCE(dev->bitrate_changed, 1); wake_up_interruptible(&tx_wait); return 0; } @@ -562,6 +592,8 @@ static int intrepid_add_can_if(struct intrepid_netdevice **result, const char *r } ics->can.state = CAN_STATE_ERROR_ACTIVE; ics->can.ctrlmode_supported = CAN_CTRLMODE_FD; + if (VER_MIN_FROM_INT(client_version) > 1) + ics->can.ctrlmode_supported |= CAN_CTRLMODE_LISTENONLY; spin_lock_init(&ics->lock); @@ -1060,6 +1092,12 @@ static long intrepid_dev_ioctl(struct file *fp, unsigned int cmd, unsigned long #else ics->can.data_bittiming.bitrate = info.baudrates[1]; #endif + /* Values seeded by the daemon are already applied to the + * hardware; record them so the next ifup does not + * re-report an unchanged configuration. + */ + ics->last_reported_bitrate = info.baudrates[0]; + ics->last_reported_listenonly = 0; break; } case SIOCSADDETHIF: { @@ -1186,15 +1224,20 @@ static int check_bitrate_change(struct intrepid_pending_tx_info *info) continue; ics = netdev_priv(net_devices[i]); - if (ics->bitrate_changed) { + if (xchg(&ics->bitrate_changed, 0)) { info->tx_box_index = -(i + 1); info->count = ics->can.bittiming.bitrate; + ics->last_reported_bitrate = ics->can.bittiming.bitrate; + ics->last_reported_listenonly = 0; + if (ics->can.ctrlmode & CAN_CTRLMODE_LISTENONLY) { + info->count |= INTREPID_BITRATE_FLAG_LISTENONLY; + ics->last_reported_listenonly = 1; + } #if KERNEL_CAN_PRIV_FD info->bytes = ics->can.fd.data_bittiming.bitrate; #else info->bytes = ics->can.data_bittiming.bitrate; #endif - ics->bitrate_changed = 0; return 1; } } @@ -1263,7 +1306,7 @@ static unsigned int intrepid_dev_poll(struct file *fp, poll_table *wait) continue; struct intrepid_netdevice *ics = netdev_priv(net_devices[i]); - if (ics->bitrate_changed) + if (READ_ONCE(ics->bitrate_changed)) return POLLIN | POLLRDNORM; } }