/* elmcan.c - ELM327 based CAN interface driver
* (tty line discipline)
*
- * This file is derived from linux/drivers/net/can/slcan.c
+ * This driver started as a derivative of linux/drivers/net/can/slcan.c
+ * and my thanks go to the original authors for their inspiration.
*
* elmcan.c Author : Max Staudt <max-linux@enpas.org>
* slcan.c Author : Oliver Hartkopp <socketcan@hartkopp.net>
* slip.c Authors : Laurence Culhane <loz@holmes.demon.co.uk>
* Fred N. van Kempen <waltje@uwalt.nl.mugnet.org>
*
+ * This code barely bears any resemblance to slcan anymore, and whatever
+ * may be left is Linux specific boilerplate anyway, however I am leaving
+ * the GPL-2.0 identifier at the top just to be sure.
+ *
+ * Please feel free to use my own code, especially the ELM327 communication
+ * logic, in accordance with SPDX-License-Identifier BSD-3-Clause to port
+ * this driver to other systems.
+ * - Max
+ *
*/
#define pr_fmt(fmt) "[elmcan] " fmt
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/tty.h>
+#include <linux/tty_ldisc.h>
+#include <linux/version.h>
#include <linux/workqueue.h>
+#include <uapi/linux/tty.h>
+
#include <linux/can.h>
#include <linux/can/dev.h>
#include <linux/can/error.h>
#include <linux/can/led.h>
+#include <linux/can/rx-offload.h>
MODULE_ALIAS_LDISC(N_ELMCAN);
MODULE_DESCRIPTION("ELM327 based CAN interface");
#define N_ELMCAN 29
#endif
+#define ELM327_NAPI_WEIGHT 4
+
#define ELM327_SIZE_RXBUF 256
#define ELM327_SIZE_TXBUF 32
/* This must be the first member when using alloc_candev() */
struct can_priv can;
+ struct can_rx_offload offload;
+
/* TTY and netdev devices that we're bridging */
struct tty_struct *tty;
struct net_device *dev;
memcpy(cf, frame, sizeof(struct can_frame));
- elm->dev->stats.rx_packets++;
- elm->dev->stats.rx_bytes += frame->can_dlc;
- netif_rx_ni(skb);
+ /* Queue for NAPI pickup.
+ * rx-offload will update stats and LEDs for us.
+ */
+ if (can_rx_offload_queue_tail(&elm->offload, skb))
+ elm->dev->stats.rx_fifo_errors++;
- can_led_event(elm->dev, CAN_LED_EVENT_RX);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,15,0)
+ /* Wake NAPI */
+ can_rx_offload_irq_finish(&elm->offload);
+#endif
}
/***********************************************************************
* (takes elm->lock) *
***********************************************************************/
+/* Dummy needed to use can_rx_offload */
+static struct sk_buff *elmcan_mailbox_read(struct can_rx_offload *offload,
+ unsigned int n, u32 *timestamp,
+ bool drop)
+{
+ WARN_ON_ONCE(1); /* This function is a dummy, so don't call it! */
+
+ return ERR_PTR(-ENOBUFS);
+}
+
static int elmcan_netdev_open(struct net_device *dev)
{
struct elmcan *elm = netdev_priv(dev);
elm327_init(elm);
spin_unlock_bh(&elm->lock);
+ elm->offload.mailbox_read = elmcan_mailbox_read;
+ err = can_rx_offload_add_fifo(dev, &elm->offload, ELM327_NAPI_WEIGHT);
+ if (err) {
+ close_candev(dev);
+ return err;
+ }
+
+ can_rx_offload_enable(&elm->offload);
+
can_led_event(dev, CAN_LED_EVENT_OPEN);
elm->can.state = CAN_STATE_ERROR_ACTIVE;
netif_start_queue(dev);
{
struct elmcan *elm = netdev_priv(dev);
+ netif_stop_queue(dev);
+
spin_lock_bh(&elm->lock);
if (elm->tty) {
/* Interrupt whatever we're doing right now */
spin_unlock_bh(&elm->lock);
}
+ can_rx_offload_disable(&elm->offload);
elm->can.state = CAN_STATE_STOPPED;
- netif_stop_queue(dev);
+ can_rx_offload_del(&elm->offload);
close_candev(dev);
can_led_event(dev, CAN_LED_EVENT_STOP);
/* We shouldn't get here after a hardware fault:
* can_bus_off() calls netif_carrier_off()
*/
- WARN_ON(elm->hw_failure);
+ WARN_ON_ONCE(elm->hw_failure);
if (!elm->tty ||
elm->hw_failure ||
* This will not be re-entered while running, but other ldisc
* functions may be called in parallel.
*/
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
static void elmcan_ldisc_rx(struct tty_struct *tty,
const unsigned char *cp, char *fp, int count)
+#else
+static void elmcan_ldisc_rx(struct tty_struct *tty,
+ const unsigned char *cp, const char *fp, int count)
+#endif
{
struct elmcan *elm = get_elm(tty);
free_candev(elm->dev);
}
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,16,0)
static int elmcan_ldisc_hangup(struct tty_struct *tty)
+#else
+static void elmcan_ldisc_hangup(struct tty_struct *tty)
+#endif
{
elmcan_ldisc_close(tty);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,16,0)
return 0;
+#endif
}
-static int elmcan_ldisc_ioctl(struct tty_struct *tty, struct file *file,
+static int elmcan_ldisc_ioctl(struct tty_struct *tty,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,17,0)
+ struct file *file,
+#endif
unsigned int cmd, unsigned long arg)
{
struct elmcan *elm = get_elm(tty);
default:
put_elm(elm);
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,16,0)
return tty_mode_ioctl(tty, file, cmd, arg);
+#else
+ return tty_mode_ioctl(tty, cmd, arg);
+#endif
}
}
static struct tty_ldisc_ops elmcan_ldisc = {
.owner = THIS_MODULE,
.name = "elmcan",
+ .num = N_ELMCAN,
.receive_buf = elmcan_ldisc_rx,
.write_wakeup = elmcan_ldisc_tx_wakeup,
.open = elmcan_ldisc_open,
{
int status;
- pr_info("ELM327 based best-effort CAN interface driver\n");
+ pr_info("ELM327 based best effort CAN interface driver\n");
pr_info("This device is severely limited as a CAN interface, see documentation.\n");
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
status = tty_register_ldisc(N_ELMCAN, &elmcan_ldisc);
+#else
+ status = tty_register_ldisc(&elmcan_ldisc);
+#endif
if (status)
pr_err("Can't register line discipline\n");
/* This will only be called when all channels have been closed by
* userspace - tty_ldisc.c takes care of the module's refcount.
*/
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5,14,0)
int status;
status = tty_unregister_ldisc(N_ELMCAN);
if (status)
pr_err("Can't unregister line discipline (error: %d)\n",
status);
+#else
+ tty_unregister_ldisc(&elmcan_ldisc);
+#endif
}
module_init(elmcan_init);