X-Git-Url: http://rtime.felk.cvut.cz/gitweb/linux-lin.git/blobdiff_plain/e9e9ac668253066ba7abbf8f88a44ea292b017b1..2e243ee2db62f787a706895b5f25136e4b10dcc0:/sllin/sllin.c diff --git a/sllin/sllin.c b/sllin/sllin.c index 908cce9..ff9b0fe 100644 --- a/sllin/sllin.c +++ b/sllin/sllin.c @@ -42,6 +42,7 @@ */ //#define DEBUG 1 /* Enables pr_debug() printouts */ +//#define SLLIN_LED_TRIGGER /* Enables led triggers */ #include #include @@ -65,10 +66,31 @@ #include #include "linux/lin_bus.h" +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,11,0) +#include +#endif + /* Should be in include/linux/tty.h */ #define N_SLLIN 25 /* -------------------------------- */ +#ifdef SLLIN_LED_TRIGGER +#define SLLIN_LED_NAME_SZ (IFNAMSIZ + 6) +#include + +enum sllin_led_event { + SLLIN_LED_EVENT_OPEN, + SLLIN_LED_EVENT_STOP, + SLLIN_LED_EVENT_TX, + SLLIN_LED_EVENT_RX +}; + +static unsigned long led_delay = 50; +module_param(led_delay, ulong, 0644); +MODULE_PARM_DESC(led_delay, + "blink delay time for activity leds (msecs, default: 50)."); +#endif /* SLLIN_LED_TRIGGER */ + static __initdata const char banner[] = KERN_INFO "sllin: serial line LIN interface driver\n"; @@ -82,16 +104,23 @@ MODULE_AUTHOR("Pavel Pisa "); static bool master = true; static int baudrate; /* Use LIN_DEFAULT_BAUDRATE when not set */ +#ifndef BREAK_BY_BAUD +static bool break_by_baud = false; +#else /*BREAK_BY_BAUD*/ +static bool break_by_baud = true; +#endif /*BREAK_BY_BAUD*/ -module_param(master, bool, 0); +module_param(master, bool, 0444); MODULE_PARM_DESC(master, "LIN interface is Master device"); -module_param(baudrate, int, 0); +module_param(baudrate, int, 0444); MODULE_PARM_DESC(baudrate, "Baudrate of LIN interface"); +module_param(break_by_baud, bool, 0444); +MODULE_PARM_DESC(break_by_baud, "Break is sent by temporal baudrate switching"); static int maxdev = 10; /* MAX number of SLLIN channels; This can be overridden with insmod sllin.ko maxdev=nnn */ -module_param(maxdev, int, 0); +module_param(maxdev, int, 0444); MODULE_PARM_DESC(maxdev, "Maximum number of sllin interfaces"); /* maximum buffer len to store whole LIN message*/ @@ -142,6 +171,7 @@ struct sllin { int tx_lim; /* actual limit of bytes to Tx */ int tx_cnt; /* number of already Tx bytes */ char lin_master; /* node is a master node */ + bool lin_break_by_baud; /* send break character by baudrate switching */ int lin_baud; /* LIN baudrate */ int lin_state; /* state */ char id_to_send; /* there is ID to be sent */ @@ -173,6 +203,15 @@ struct sllin { /* List with configurations for each of 0 to LIN_ID_MAX LIN IDs */ struct sllin_conf_entry linfr_cache[LIN_ID_MAX + 1]; spinlock_t linfr_lock; /* frame cache and buffers lock */ + +#ifdef SLLIN_LED_TRIGGER + struct led_trigger *tx_led_trig; + char tx_led_trig_name[SLLIN_LED_NAME_SZ]; + struct led_trigger *rx_led_trig; + char rx_led_trig_name[SLLIN_LED_NAME_SZ]; + struct led_trigger *rxtx_led_trig; + char rxtx_led_trig_name[SLLIN_LED_NAME_SZ]; +#endif }; static struct net_device **sllin_devs; @@ -196,6 +235,124 @@ const unsigned char sllin_id_parity_table[] = { 0x40, 0x00, 0x80, 0xc0, 0x00, 0x40, 0xc0, 0x80 }; +#ifdef SLLIN_LED_TRIGGER +static void sllin_led_event(struct net_device *netdev, enum sllin_led_event event) +{ + struct sllin *sl = netdev_priv(netdev); + + switch (event) { + case SLLIN_LED_EVENT_OPEN: + led_trigger_event(sl->tx_led_trig, LED_FULL); + led_trigger_event(sl->rx_led_trig, LED_FULL); + led_trigger_event(sl->rxtx_led_trig, LED_FULL); + break; + case SLLIN_LED_EVENT_STOP: + led_trigger_event(sl->tx_led_trig, LED_OFF); + led_trigger_event(sl->rx_led_trig, LED_OFF); + led_trigger_event(sl->rxtx_led_trig, LED_OFF); + break; + case SLLIN_LED_EVENT_TX: + if (led_delay) { + led_trigger_blink_oneshot(sl->tx_led_trig, + &led_delay, &led_delay, 1); + led_trigger_blink_oneshot(sl->rxtx_led_trig, + &led_delay, &led_delay, 1); + } + break; + case SLLIN_LED_EVENT_RX: + if (led_delay) { + led_trigger_blink_oneshot(sl->rx_led_trig, + &led_delay, &led_delay, 1); + led_trigger_blink_oneshot(sl->rxtx_led_trig, + &led_delay, &led_delay, 1); + } + break; + } +} + +static void sllin_led_release(struct device *gendev, void *res) +{ + struct sllin *sl = netdev_priv(to_net_dev(gendev)); + + led_trigger_unregister_simple(sl->tx_led_trig); + led_trigger_unregister_simple(sl->rx_led_trig); + led_trigger_unregister_simple(sl->rxtx_led_trig); +} + +static void devm_sllin_led_init(struct net_device *netdev) +{ + struct sllin *sl = netdev_priv(netdev); + void *res; + + res = devres_alloc(sllin_led_release, 0, GFP_KERNEL); + if (!res) { + netdev_err(netdev, "cannot register LED triggers\n"); + return; + } + + snprintf(sl->tx_led_trig_name, sizeof(sl->tx_led_trig_name), + "%s-tx", netdev->name); + snprintf(sl->rx_led_trig_name, sizeof(sl->rx_led_trig_name), + "%s-rx", netdev->name); + snprintf(sl->rxtx_led_trig_name, sizeof(sl->rxtx_led_trig_name), + "%s-rxtx", netdev->name); + + led_trigger_register_simple(sl->tx_led_trig_name, + &sl->tx_led_trig); + led_trigger_register_simple(sl->rx_led_trig_name, + &sl->rx_led_trig); + led_trigger_register_simple(sl->rxtx_led_trig_name, + &sl->rxtx_led_trig); + + devres_add(&netdev->dev, res); +} + +static struct sllin *netdev_priv_safe(struct net_device *dev) +{ + int i; + + if (sllin_devs == NULL) + return NULL; + + for (i = 0; i < maxdev; ++i) + if (sllin_devs[i] == dev) + return netdev_priv(dev); + + return NULL; +} + +static int sllin_netdev_notifier_call(struct notifier_block *nb, unsigned long msg, + void *ptr) +{ + struct net_device *netdev = netdev_notifier_info_to_dev(ptr); + struct sllin *sl = netdev_priv_safe(netdev); + char name[SLLIN_LED_NAME_SZ]; + + if (!sl) + return NOTIFY_DONE; + + if (!sl->tx_led_trig || !sl->rx_led_trig || !sl->rxtx_led_trig) + return NOTIFY_DONE; + + if (msg == NETDEV_CHANGENAME) { + snprintf(name, sizeof(name), "%s-tx", netdev->name); + led_trigger_rename_static(name, sl->tx_led_trig); + + snprintf(name, sizeof(name), "%s-rx", netdev->name); + led_trigger_rename_static(name, sl->rx_led_trig); + + snprintf(name, sizeof(name), "%s-rxtx", netdev->name); + led_trigger_rename_static(name, sl->rxtx_led_trig); + } + + return NOTIFY_DONE; +} + +static struct notifier_block sllin_netdev_notifier __read_mostly = { + .notifier_call = sllin_netdev_notifier_call, +}; +#endif /* SLLIN_LED_TRIGGER */ + /** * sltty_change_speed() -- Change baudrate of Serial device belonging * to particular @tty @@ -276,6 +433,10 @@ static void sllin_send_canfr(struct sllin *sl, canid_t id, char *data, int len) sl->dev->stats.rx_packets++; sl->dev->stats.rx_bytes += cf.can_dlc; + +#ifdef SLLIN_LED_TRIGGER + sllin_led_event(sl->dev, SLLIN_LED_EVENT_RX); +#endif } /** @@ -425,6 +586,9 @@ static int sll_close(struct net_device *dev) sl->tx_lim = 0; spin_unlock_bh(&sl->lock); +#ifdef SLLIN_LED_TRIGGER + sllin_led_event(dev, SLLIN_LED_EVENT_STOP); +#endif return 0; } @@ -440,6 +604,10 @@ static int sll_open(struct net_device *dev) sl->flags &= (1 << SLF_INUSE); netif_start_queue(dev); + +#ifdef SLLIN_LED_TRIGGER + sllin_led_event(dev, SLLIN_LED_EVENT_OPEN); +#endif return 0; } @@ -460,7 +628,11 @@ static const struct net_device_ops sll_netdev_ops = { static void sll_setup(struct net_device *dev) { dev->netdev_ops = &sll_netdev_ops; +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) dev->destructor = sll_free_netdev; +#else /* Linux 4.12.0+ */ + dev->priv_destructor = sll_free_netdev; +#endif /* Linux 4.12.0+ */ dev->hard_header_len = 0; dev->addr_len = 0; @@ -497,13 +669,14 @@ static void sllin_master_receive_buf(struct tty_struct *tty, } } -#ifndef BREAK_BY_BAUD - /* We didn't receive Break character -- fake it! */ - if ((sl->rx_cnt == SLLIN_BUFF_BREAK) && (*cp == 0x55)) { - netdev_dbg(sl->dev, "LIN_RX[%d]: 0x00\n", sl->rx_cnt); - sl->rx_buff[sl->rx_cnt++] = 0x00; + if (!sl->lin_break_by_baud ) { + /* We didn't receive Break character -- fake it! */ + if ((sl->rx_cnt == SLLIN_BUFF_BREAK) && (*cp == 0x55)) { + netdev_dbg(sl->dev, "LIN_RX[%d]: 0x00\n", + sl->rx_cnt); + sl->rx_buff[sl->rx_cnt++] = 0x00; + } } -#endif /* BREAK_BY_BAUD */ if (sl->rx_cnt < SLLIN_BUFF_LEN) { netdev_dbg(sl->dev, "LIN_RX[%d]: 0x%02x\n", sl->rx_cnt, *cp); @@ -861,14 +1034,14 @@ static int sllin_send_tx_buff(struct sllin *sl) smp_mb__after_atomic(); #endif -#ifdef BREAK_BY_BAUD - if (sl->lin_state != SLSTATE_BREAK_SENT) + if (sl->lin_break_by_baud) { + if (sl->lin_state != SLSTATE_BREAK_SENT) + remains = sl->tx_lim - sl->tx_cnt; + else + remains = 1; + } else { remains = sl->tx_lim - sl->tx_cnt; - else - remains = 1; -#else - remains = sl->tx_lim - sl->tx_cnt; -#endif + } res = tty->ops->write(tty, sl->tx_buff + sl->tx_cnt, remains); if (res < 0) @@ -901,6 +1074,10 @@ static int sllin_send_tx_buff(struct sllin *sl) } while (unlikely(test_bit(SLF_TXBUFF_RQ, &sl->flags))); +#ifdef SLLIN_LED_TRIGGER + sllin_led_event(sl->dev, SLLIN_LED_EVENT_TX); +#endif + return 0; error_in_write: @@ -909,8 +1086,7 @@ error_in_write: } -#ifdef BREAK_BY_BAUD -static int sllin_send_break(struct sllin *sl) +static int sllin_send_break_by_baud(struct sllin *sl) { struct tty_struct *tty = sl->tty; unsigned long break_baud; @@ -919,7 +1095,11 @@ static int sllin_send_break(struct sllin *sl) break_baud = ((sl->lin_baud * 2) / 3); sltty_change_speed(tty, break_baud); - tty->ops->flush_buffer(tty); + if (tty->ops->flush_buffer != NULL) { + tty->ops->flush_buffer(tty); + } else { + netdev_dbg(sl->dev, "flush_buffer is not implemented.\n"); + } sl->rx_cnt = SLLIN_BUFF_BREAK; sl->rx_expect = SLLIN_BUFF_BREAK + 1; @@ -933,9 +1113,8 @@ static int sllin_send_break(struct sllin *sl) return 0; } -#else /* BREAK_BY_BAUD */ -static int sllin_send_break(struct sllin *sl) +static int sllin_send_break_by_usleep(struct sllin *sl) { struct tty_struct *tty = sl->tty; int retval; @@ -964,7 +1143,11 @@ static int sllin_send_break(struct sllin *sl) usleep_range_max = usleep_range_min + 30; usleep_range(usleep_range_min, usleep_range_max); - tty->ops->flush_buffer(tty); + if (tty != NULL && tty->ops != NULL && tty->ops->flush_buffer != NULL) { + tty->ops->flush_buffer(tty); + } else { + netdev_dbg(sl->dev, "flush_buffer is not implemented.\n"); + } sl->tx_cnt = SLLIN_BUFF_SYNC; @@ -974,8 +1157,14 @@ static int sllin_send_break(struct sllin *sl) return 0; } -#endif /* BREAK_BY_BAUD */ +static int sllin_send_break(struct sllin *sl) +{ + if (sl->lin_break_by_baud) + return sllin_send_break_by_baud(sl); + else + return sllin_send_break_by_usleep(sl); +} static enum hrtimer_restart sllin_rx_timeout_handler(struct hrtimer *hrtimer) { @@ -1092,8 +1281,6 @@ static int sllin_kwthread(void *ptr) /* SFF RTR CAN frame -> LIN header */ if (cf->can_id & CAN_RTR_FLAG) { - struct sllin_conf_entry *sce; - netdev_dbg(sl->dev, "%s: RTR SFF CAN frame, ID = %x\n", __func__, cf->can_id & LIN_ID_MASK); @@ -1106,6 +1293,9 @@ static int sllin_kwthread(void *ptr) if ((sce->frame_fl & LIN_CACHE_RESPONSE) && (sce->dlc > 0)) { + if (sce->frame_fl & LIN_SINGLE_RESPONSE) + sce->frame_fl &= ~LIN_CACHE_RESPONSE; + netdev_dbg(sl->dev, "Sending LIN response from linfr_cache\n"); lin_data = sce->data; @@ -1121,8 +1311,6 @@ static int sllin_kwthread(void *ptr) spin_unlock_irqrestore(&sl->linfr_lock, flags); } else { /* SFF NON-RTR CAN frame -> LIN header + LIN response */ - struct sllin_conf_entry *sce; - netdev_dbg(sl->dev, "%s: NON-RTR SFF CAN frame, ID = %x\n", __func__, (int)cf->can_id & LIN_ID_MASK); @@ -1156,12 +1344,12 @@ static int sllin_kwthread(void *ptr) break; case SLSTATE_BREAK_SENT: -#ifdef BREAK_BY_BAUD - if (sl->rx_cnt <= SLLIN_BUFF_BREAK) - continue; + if (sl->lin_break_by_baud) { + if (sl->rx_cnt <= SLLIN_BUFF_BREAK) + continue; - res = sltty_change_speed(tty, sl->lin_baud); -#endif + sltty_change_speed(tty, sl->lin_baud); + } sl->lin_state = SLSTATE_ID_SENT; sllin_send_tx_buff(sl); @@ -1256,6 +1444,9 @@ slstate_response_wait: if ((sce->frame_fl & LIN_CACHE_RESPONSE) && (sce->dlc > 0)) { + if (sce->frame_fl & LIN_SINGLE_RESPONSE) + sce->frame_fl &= ~LIN_CACHE_RESPONSE; + netdev_dbg(sl->dev, "Sending LIN response from linfr_cache\n"); lin_data = sce->data; @@ -1458,6 +1649,13 @@ static int sllin_open(struct tty_struct *tty) sl->lin_baud = (baudrate == 0) ? LIN_DEFAULT_BAUDRATE : baudrate; pr_debug("sllin: Baudrate set to %u\n", sl->lin_baud); + sl->lin_break_by_baud = break_by_baud; + + if (tty->ops->break_ctl == NULL && !sl->lin_break_by_baud) { + sl->lin_break_by_baud = true; + pr_notice ("sllin: break_ctl, switching to break_by_baud\n"); + } + sl->lin_state = SLSTATE_IDLE; hrtimer_init(&sl->rx_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); @@ -1477,6 +1675,10 @@ static int sllin_open(struct tty_struct *tty) err = register_netdevice(sl->dev); if (err) goto err_free_chan_and_thread; + +#ifdef SLLIN_LED_TRIGGER + devm_sllin_led_init(sl->dev); +#endif } /* Done. We have linked the TTY line to a channel. */ @@ -1577,6 +1779,12 @@ static int __init sllin_init(void) { int status; +#ifdef SLLIN_LED_TRIGGER + status = register_netdevice_notifier(&sllin_netdev_notifier); + if (status) + pr_err("sllin: can't register netdevice notifier\n"); +#endif + if (maxdev < 4) maxdev = 4; /* Sanity */ @@ -1596,11 +1804,10 @@ static int __init sllin_init(void) kfree(sllin_devs); } -#ifdef BREAK_BY_BAUD - pr_debug("sllin: Break is generated by baud-rate change."); -#else - pr_debug("sllin: Break is generated manually with tiny sleep."); -#endif + if (break_by_baud) + pr_debug("sllin: Break is generated by baud-rate change."); + else + pr_debug("sllin: Break is generated manually with tiny sleep."); return status; } @@ -1650,7 +1857,11 @@ static void __exit sllin_exit(void) if (sl->tty) { netdev_dbg(sl->dev, "tty discipline still running\n"); /* Intentionally leak the control block. */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(4,12,0) dev->destructor = NULL; +#else /* Linux 4.12.0+ */ + dev->priv_destructor = NULL; +#endif /* Linux 4.12.0+ */ } unregister_netdev(dev); @@ -1662,6 +1873,11 @@ static void __exit sllin_exit(void) i = tty_unregister_ldisc(N_SLLIN); if (i) pr_err("sllin: can't unregister ldisc (err %d)\n", i); + +#ifdef SLLIN_LED_TRIGGER + unregister_netdevice_notifier(&sllin_netdev_notifier); +#endif + } module_init(sllin_init);