Commit cfcb4465 authored by Dario Binacchi's avatar Dario Binacchi Committed by Marc Kleine-Budde

can: slcan: remove legacy infrastructure

Taking inspiration from the drivers/net/can/can327.c driver and at the
suggestion of its author Max Staudt, I removed legacy stuff like
`SLCAN_MAGIC' and `slcan_devs' resulting in simplification of the code
and its maintainability.

The use of slcan_devs is derived from a very old kernel, since slip.c
is about 30 years old, so today's kernel allows us to remove it.

The .hangup() ldisc function, which only called the ldisc .close(), has
been removed since the ldisc layer calls .close() in a good place
anyway.

The old slcanX name has been dropped in order to use the standard canX
interface naming. The ioctl SIOCGIFNAME can be used to query the name of
the created interface. Furthermore, there are several ways to get stable
interfaces names in user space, e.g. udev or systemd-networkd.

The `maxdev' module parameter has also been removed.

CC: Max Staudt <max@enpas.org>
Signed-off-by: default avatarDario Binacchi <dario.binacchi@amarulasolutions.com>
Reviewed-by: default avatarMax Staudt <max@enpas.org>
Link: https://lore.kernel.org/all/20220728070254.267974-4-dario.binacchi@amarulasolutions.comSigned-off-by: default avatarMarc Kleine-Budde <mkl@pengutronix.de>
parent 7a1fc3ee
/* /*
* slcan.c - serial line CAN interface driver (using tty line discipline) * slcan.c - serial line CAN interface driver (using tty line discipline)
* *
* This file is derived from linux/drivers/net/slip/slip.c * This file is derived from linux/drivers/net/slip/slip.c and got
* inspiration from linux/drivers/net/can/can327.c for the rework made
* on the line discipline code.
* *
* slip.c Authors : Laurence Culhane <loz@holmes.demon.co.uk> * slip.c Authors : Laurence Culhane <loz@holmes.demon.co.uk>
* Fred N. van Kempen <waltje@uwalt.nl.mugnet.org> * Fred N. van Kempen <waltje@uwalt.nl.mugnet.org>
* slcan.c Author : Oliver Hartkopp <socketcan@hartkopp.net> * slcan.c Author : Oliver Hartkopp <socketcan@hartkopp.net>
* can327.c Author : Max Staudt <max-linux@enpas.org>
* *
* This program is free software; you can redistribute it and/or modify it * This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the * under the terms of the GNU General Public License as published by the
...@@ -38,7 +41,6 @@ ...@@ -38,7 +41,6 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h> #include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <linux/bitops.h> #include <linux/bitops.h>
...@@ -48,7 +50,6 @@ ...@@ -48,7 +50,6 @@
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/skbuff.h> #include <linux/skbuff.h>
#include <linux/rtnetlink.h> #include <linux/rtnetlink.h>
#include <linux/delay.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/workqueue.h> #include <linux/workqueue.h>
...@@ -63,15 +64,6 @@ MODULE_DESCRIPTION("serial line CAN interface"); ...@@ -63,15 +64,6 @@ MODULE_DESCRIPTION("serial line CAN interface");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_AUTHOR("Oliver Hartkopp <socketcan@hartkopp.net>"); MODULE_AUTHOR("Oliver Hartkopp <socketcan@hartkopp.net>");
#define SLCAN_MAGIC 0x53CA
static int maxdev = 10; /* MAX number of SLCAN channels;
* This can be overridden with
* insmod slcan.ko maxdev=nnn
*/
module_param(maxdev, int, 0);
MODULE_PARM_DESC(maxdev, "Maximum number of slcan interfaces");
/* maximum rx buffer len: extended CAN frame with timestamp */ /* maximum rx buffer len: extended CAN frame with timestamp */
#define SLC_MTU (sizeof("T1111222281122334455667788EA5F\r") + 1) #define SLC_MTU (sizeof("T1111222281122334455667788EA5F\r") + 1)
...@@ -85,7 +77,6 @@ MODULE_PARM_DESC(maxdev, "Maximum number of slcan interfaces"); ...@@ -85,7 +77,6 @@ MODULE_PARM_DESC(maxdev, "Maximum number of slcan interfaces");
SLC_STATE_BE_TXCNT_LEN) SLC_STATE_BE_TXCNT_LEN)
struct slcan { struct slcan {
struct can_priv can; struct can_priv can;
int magic;
/* Various fields. */ /* Various fields. */
struct tty_struct *tty; /* ptr to TTY structure */ struct tty_struct *tty; /* ptr to TTY structure */
...@@ -101,17 +92,14 @@ struct slcan { ...@@ -101,17 +92,14 @@ struct slcan {
int xleft; /* bytes left in XMIT queue */ int xleft; /* bytes left in XMIT queue */
unsigned long flags; /* Flag values/ mode etc */ unsigned long flags; /* Flag values/ mode etc */
#define SLF_INUSE 0 /* Channel in use */ #define SLF_ERROR 0 /* Parity, etc. error */
#define SLF_ERROR 1 /* Parity, etc. error */ #define SLF_XCMD 1 /* Command transmission */
#define SLF_XCMD 2 /* Command transmission */
unsigned long cmd_flags; /* Command flags */ unsigned long cmd_flags; /* Command flags */
#define CF_ERR_RST 0 /* Reset errors on open */ #define CF_ERR_RST 0 /* Reset errors on open */
wait_queue_head_t xcmd_wait; /* Wait queue for commands */ wait_queue_head_t xcmd_wait; /* Wait queue for commands */
/* transmission */ /* transmission */
}; };
static struct net_device **slcan_devs;
static const u32 slcan_bitrate_const[] = { static const u32 slcan_bitrate_const[] = {
10000, 20000, 50000, 100000, 125000, 10000, 20000, 50000, 100000, 125000,
250000, 500000, 800000, 1000000 250000, 500000, 800000, 1000000
...@@ -556,9 +544,8 @@ static void slcan_transmit(struct work_struct *work) ...@@ -556,9 +544,8 @@ static void slcan_transmit(struct work_struct *work)
spin_lock_bh(&sl->lock); spin_lock_bh(&sl->lock);
/* First make sure we're connected. */ /* First make sure we're connected. */
if (!sl->tty || sl->magic != SLCAN_MAGIC || if (unlikely(!netif_running(sl->dev)) &&
(unlikely(!netif_running(sl->dev)) && likely(!test_bit(SLF_XCMD, &sl->flags))) {
likely(!test_bit(SLF_XCMD, &sl->flags)))) {
spin_unlock_bh(&sl->lock); spin_unlock_bh(&sl->lock);
return; return;
} }
...@@ -593,13 +580,9 @@ static void slcan_transmit(struct work_struct *work) ...@@ -593,13 +580,9 @@ static void slcan_transmit(struct work_struct *work)
*/ */
static void slcan_write_wakeup(struct tty_struct *tty) static void slcan_write_wakeup(struct tty_struct *tty)
{ {
struct slcan *sl; struct slcan *sl = (struct slcan *)tty->disc_data;
rcu_read_lock(); schedule_work(&sl->tx_work);
sl = rcu_dereference(tty->disc_data);
if (sl)
schedule_work(&sl->tx_work);
rcu_read_unlock();
} }
/* Send a can_frame to a TTY queue. */ /* Send a can_frame to a TTY queue. */
...@@ -670,25 +653,21 @@ static int slc_close(struct net_device *dev) ...@@ -670,25 +653,21 @@ static int slc_close(struct net_device *dev)
struct slcan *sl = netdev_priv(dev); struct slcan *sl = netdev_priv(dev);
int err; int err;
spin_lock_bh(&sl->lock); if (sl->can.bittiming.bitrate &&
if (sl->tty) { sl->can.bittiming.bitrate != CAN_BITRATE_UNKNOWN) {
if (sl->can.bittiming.bitrate && err = slcan_transmit_cmd(sl, "C\r");
sl->can.bittiming.bitrate != CAN_BITRATE_UNKNOWN) { if (err)
spin_unlock_bh(&sl->lock); netdev_warn(dev,
err = slcan_transmit_cmd(sl, "C\r"); "failed to send close command 'C\\r'\n");
spin_lock_bh(&sl->lock);
if (err)
netdev_warn(dev,
"failed to send close command 'C\\r'\n");
}
/* TTY discipline is running. */
clear_bit(TTY_DO_WRITE_WAKEUP, &sl->tty->flags);
} }
/* TTY discipline is running. */
clear_bit(TTY_DO_WRITE_WAKEUP, &sl->tty->flags);
flush_work(&sl->tx_work);
netif_stop_queue(dev); netif_stop_queue(dev);
sl->rcount = 0; sl->rcount = 0;
sl->xleft = 0; sl->xleft = 0;
spin_unlock_bh(&sl->lock);
close_candev(dev); close_candev(dev);
sl->can.state = CAN_STATE_STOPPED; sl->can.state = CAN_STATE_STOPPED;
if (sl->can.bittiming.bitrate == CAN_BITRATE_UNKNOWN) if (sl->can.bittiming.bitrate == CAN_BITRATE_UNKNOWN)
...@@ -704,9 +683,6 @@ static int slc_open(struct net_device *dev) ...@@ -704,9 +683,6 @@ static int slc_open(struct net_device *dev)
unsigned char cmd[SLC_MTU]; unsigned char cmd[SLC_MTU];
int err, s; int err, s;
if (!sl->tty)
return -ENODEV;
/* The baud rate is not set with the command /* The baud rate is not set with the command
* `ip link set <iface> type can bitrate <baud>' and therefore * `ip link set <iface> type can bitrate <baud>' and therefore
* can.bittiming.bitrate is CAN_BITRATE_UNSET (0), causing * can.bittiming.bitrate is CAN_BITRATE_UNSET (0), causing
...@@ -721,8 +697,6 @@ static int slc_open(struct net_device *dev) ...@@ -721,8 +697,6 @@ static int slc_open(struct net_device *dev)
return err; return err;
} }
sl->flags &= BIT(SLF_INUSE);
if (sl->can.bittiming.bitrate != CAN_BITRATE_UNKNOWN) { if (sl->can.bittiming.bitrate != CAN_BITRATE_UNKNOWN) {
for (s = 0; s < ARRAY_SIZE(slcan_bitrate_const); s++) { for (s = 0; s < ARRAY_SIZE(slcan_bitrate_const); s++) {
if (sl->can.bittiming.bitrate == slcan_bitrate_const[s]) if (sl->can.bittiming.bitrate == slcan_bitrate_const[s])
...@@ -766,14 +740,6 @@ static int slc_open(struct net_device *dev) ...@@ -766,14 +740,6 @@ static int slc_open(struct net_device *dev)
return err; return err;
} }
static void slc_dealloc(struct slcan *sl)
{
int i = sl->dev->base_addr;
free_candev(sl->dev);
slcan_devs[i] = NULL;
}
static int slcan_change_mtu(struct net_device *dev, int new_mtu) static int slcan_change_mtu(struct net_device *dev, int new_mtu)
{ {
return -EINVAL; return -EINVAL;
...@@ -803,7 +769,7 @@ static void slcan_receive_buf(struct tty_struct *tty, ...@@ -803,7 +769,7 @@ static void slcan_receive_buf(struct tty_struct *tty,
{ {
struct slcan *sl = (struct slcan *)tty->disc_data; struct slcan *sl = (struct slcan *)tty->disc_data;
if (!sl || sl->magic != SLCAN_MAGIC || !netif_running(sl->dev)) if (!netif_running(sl->dev))
return; return;
/* Read the characters out of the buffer */ /* Read the characters out of the buffer */
...@@ -818,80 +784,15 @@ static void slcan_receive_buf(struct tty_struct *tty, ...@@ -818,80 +784,15 @@ static void slcan_receive_buf(struct tty_struct *tty,
} }
} }
/************************************
* slcan_open helper routines.
************************************/
/* Collect hanged up channels */
static void slc_sync(void)
{
int i;
struct net_device *dev;
struct slcan *sl;
for (i = 0; i < maxdev; i++) {
dev = slcan_devs[i];
if (!dev)
break;
sl = netdev_priv(dev);
if (sl->tty)
continue;
if (dev->flags & IFF_UP)
dev_close(dev);
}
}
/* Find a free SLCAN channel, and link in this `tty' line. */
static struct slcan *slc_alloc(void)
{
int i;
struct net_device *dev = NULL;
struct slcan *sl;
for (i = 0; i < maxdev; i++) {
dev = slcan_devs[i];
if (!dev)
break;
}
/* Sorry, too many, all slots in use */
if (i >= maxdev)
return NULL;
dev = alloc_candev(sizeof(*sl), 1);
if (!dev)
return NULL;
snprintf(dev->name, sizeof(dev->name), KBUILD_MODNAME "%d", i);
dev->netdev_ops = &slc_netdev_ops;
dev->ethtool_ops = &slcan_ethtool_ops;
dev->base_addr = i;
sl = netdev_priv(dev);
/* Initialize channel control data */
sl->magic = SLCAN_MAGIC;
sl->dev = dev;
sl->can.bitrate_const = slcan_bitrate_const;
sl->can.bitrate_const_cnt = ARRAY_SIZE(slcan_bitrate_const);
spin_lock_init(&sl->lock);
INIT_WORK(&sl->tx_work, slcan_transmit);
init_waitqueue_head(&sl->xcmd_wait);
slcan_devs[i] = dev;
return sl;
}
/* Open the high-level part of the SLCAN channel. /* Open the high-level part of the SLCAN channel.
* This function is called by the TTY module when the * This function is called by the TTY module when the
* SLCAN line discipline is called for. Because we are * SLCAN line discipline is called for.
* sure the tty line exists, we only have to link it to
* a free SLCAN channel...
* *
* Called in process context serialized from other ldisc calls. * Called in process context serialized from other ldisc calls.
*/ */
static int slcan_open(struct tty_struct *tty) static int slcan_open(struct tty_struct *tty)
{ {
struct net_device *dev;
struct slcan *sl; struct slcan *sl;
int err; int err;
...@@ -901,72 +802,49 @@ static int slcan_open(struct tty_struct *tty) ...@@ -901,72 +802,49 @@ static int slcan_open(struct tty_struct *tty)
if (!tty->ops->write) if (!tty->ops->write)
return -EOPNOTSUPP; return -EOPNOTSUPP;
/* RTnetlink lock is misused here to serialize concurrent dev = alloc_candev(sizeof(*sl), 1);
* opens of slcan channels. There are better ways, but it is if (!dev)
* the simplest one. return -ENFILE;
*/
rtnl_lock();
/* Collect hanged up channels. */ sl = netdev_priv(dev);
slc_sync();
sl = tty->disc_data; /* Configure TTY interface */
tty->receive_room = 65536; /* We don't flow control */
sl->rcount = 0;
sl->xleft = 0;
spin_lock_init(&sl->lock);
INIT_WORK(&sl->tx_work, slcan_transmit);
init_waitqueue_head(&sl->xcmd_wait);
err = -EEXIST; /* Configure CAN metadata */
/* First make sure we're not already connected. */ sl->can.bitrate_const = slcan_bitrate_const;
if (sl && sl->magic == SLCAN_MAGIC) sl->can.bitrate_const_cnt = ARRAY_SIZE(slcan_bitrate_const);
goto err_exit;
/* OK. Find a free SLCAN channel to use. */ /* Configure netdev interface */
err = -ENFILE; sl->dev = dev;
sl = slc_alloc(); dev->netdev_ops = &slc_netdev_ops;
if (!sl) dev->ethtool_ops = &slcan_ethtool_ops;
goto err_exit;
/* Mark ldisc channel as alive */
sl->tty = tty; sl->tty = tty;
tty->disc_data = sl; tty->disc_data = sl;
if (!test_bit(SLF_INUSE, &sl->flags)) { err = register_candev(dev);
/* Perform the low-level SLCAN initialization. */ if (err) {
sl->rcount = 0; free_candev(dev);
sl->xleft = 0; pr_err("can't register candev\n");
return err;
set_bit(SLF_INUSE, &sl->flags);
rtnl_unlock();
err = register_candev(sl->dev);
if (err) {
pr_err("can't register candev\n");
goto err_free_chan;
}
} else {
rtnl_unlock();
} }
tty->receive_room = 65536; /* We don't flow control */ netdev_info(dev, "slcan on %s.\n", tty->name);
/* TTY layer expects 0 on success */ /* TTY layer expects 0 on success */
return 0; return 0;
err_free_chan:
rtnl_lock();
sl->tty = NULL;
tty->disc_data = NULL;
clear_bit(SLF_INUSE, &sl->flags);
slc_dealloc(sl);
rtnl_unlock();
return err;
err_exit:
rtnl_unlock();
/* Count references from TTY module */
return err;
} }
/* Close down a SLCAN channel. /* Close down a SLCAN channel.
* This means flushing out any pending queues, and then returning. This * This means flushing out any pending queues, and then returning. This
* call is serialized against other ldisc functions. * call is serialized against other ldisc functions.
* Once this is called, no other ldisc function of ours is entered.
* *
* We also use this method for a hangup event. * We also use this method for a hangup event.
*/ */
...@@ -974,28 +852,20 @@ static void slcan_close(struct tty_struct *tty) ...@@ -974,28 +852,20 @@ static void slcan_close(struct tty_struct *tty)
{ {
struct slcan *sl = (struct slcan *)tty->disc_data; struct slcan *sl = (struct slcan *)tty->disc_data;
/* First make sure we're connected. */ /* unregister_netdev() calls .ndo_stop() so we don't have to.
if (!sl || sl->magic != SLCAN_MAGIC || sl->tty != tty) * Our .ndo_stop() also flushes the TTY write wakeup handler,
return; * so we can safely set sl->tty = NULL after this.
*/
unregister_candev(sl->dev);
/* Mark channel as dead */
spin_lock_bh(&sl->lock); spin_lock_bh(&sl->lock);
rcu_assign_pointer(tty->disc_data, NULL); tty->disc_data = NULL;
sl->tty = NULL; sl->tty = NULL;
spin_unlock_bh(&sl->lock); spin_unlock_bh(&sl->lock);
synchronize_rcu(); netdev_info(sl->dev, "slcan off %s.\n", tty->name);
flush_work(&sl->tx_work); free_candev(sl->dev);
slc_close(sl->dev);
unregister_candev(sl->dev);
rtnl_lock();
slc_dealloc(sl);
rtnl_unlock();
}
static void slcan_hangup(struct tty_struct *tty)
{
slcan_close(tty);
} }
/* Perform I/O control on an active SLCAN channel. */ /* Perform I/O control on an active SLCAN channel. */
...@@ -1005,10 +875,6 @@ static int slcan_ioctl(struct tty_struct *tty, unsigned int cmd, ...@@ -1005,10 +875,6 @@ static int slcan_ioctl(struct tty_struct *tty, unsigned int cmd,
struct slcan *sl = (struct slcan *)tty->disc_data; struct slcan *sl = (struct slcan *)tty->disc_data;
unsigned int tmp; unsigned int tmp;
/* First make sure we're connected. */
if (!sl || sl->magic != SLCAN_MAGIC)
return -EINVAL;
switch (cmd) { switch (cmd) {
case SIOCGIFNAME: case SIOCGIFNAME:
tmp = strlen(sl->dev->name) + 1; tmp = strlen(sl->dev->name) + 1;
...@@ -1030,7 +896,6 @@ static struct tty_ldisc_ops slc_ldisc = { ...@@ -1030,7 +896,6 @@ static struct tty_ldisc_ops slc_ldisc = {
.name = KBUILD_MODNAME, .name = KBUILD_MODNAME,
.open = slcan_open, .open = slcan_open,
.close = slcan_close, .close = slcan_close,
.hangup = slcan_hangup,
.ioctl = slcan_ioctl, .ioctl = slcan_ioctl,
.receive_buf = slcan_receive_buf, .receive_buf = slcan_receive_buf,
.write_wakeup = slcan_write_wakeup, .write_wakeup = slcan_write_wakeup,
...@@ -1040,78 +905,21 @@ static int __init slcan_init(void) ...@@ -1040,78 +905,21 @@ static int __init slcan_init(void)
{ {
int status; int status;
if (maxdev < 4)
maxdev = 4; /* Sanity */
pr_info("serial line CAN interface driver\n"); pr_info("serial line CAN interface driver\n");
pr_info("%d dynamic interface channels.\n", maxdev);
slcan_devs = kcalloc(maxdev, sizeof(struct net_device *), GFP_KERNEL);
if (!slcan_devs)
return -ENOMEM;
/* Fill in our line protocol discipline, and register it */ /* Fill in our line protocol discipline, and register it */
status = tty_register_ldisc(&slc_ldisc); status = tty_register_ldisc(&slc_ldisc);
if (status) { if (status)
pr_err("can't register line discipline\n"); pr_err("can't register line discipline\n");
kfree(slcan_devs);
}
return status; return status;
} }
static void __exit slcan_exit(void) static void __exit slcan_exit(void)
{ {
int i; /* This will only be called when all channels have been closed by
struct net_device *dev; * userspace - tty_ldisc.c takes care of the module's refcount.
struct slcan *sl;
unsigned long timeout = jiffies + HZ;
int busy = 0;
if (!slcan_devs)
return;
/* First of all: check for active disciplines and hangup them.
*/
do {
if (busy)
msleep_interruptible(100);
busy = 0;
for (i = 0; i < maxdev; i++) {
dev = slcan_devs[i];
if (!dev)
continue;
sl = netdev_priv(dev);
spin_lock_bh(&sl->lock);
if (sl->tty) {
busy++;
tty_hangup(sl->tty);
}
spin_unlock_bh(&sl->lock);
}
} while (busy && time_before(jiffies, timeout));
/* FIXME: hangup is async so we should wait when doing this second
* phase
*/ */
for (i = 0; i < maxdev; i++) {
dev = slcan_devs[i];
if (!dev)
continue;
sl = netdev_priv(dev);
if (sl->tty)
netdev_err(dev, "tty discipline still running\n");
slc_close(dev);
unregister_candev(dev);
slc_dealloc(sl);
}
kfree(slcan_devs);
slcan_devs = NULL;
tty_unregister_ldisc(&slc_ldisc); tty_unregister_ldisc(&slc_ldisc);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment