/* * LED Kernel Netdev Trigger * * Toggles the LED to reflect the link and traffic state of a named net device * * Copyright 2007 Oliver Jowett * * Derived from ledtrig-timer.c which is: * Copyright 2005-2006 Openedhand Ltd. * Author: Richard Purdie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * */ #include #include #include #include #include #include #include #include #include #include #include #include "leds.h" /* * Configurable sysfs attributes: * * device_name - network device name to monitor * * interval - duration of LED blink, in milliseconds * * mode - either "none" (LED is off) or a space separated list of one or more of: * link: LED's normal state reflects whether the link is up (has carrier) or not * tx: LED blinks on transmitted data * rx: LED blinks on receive data * * Some suggestions: * * Simple link status LED: * $ echo netdev >someled/trigger * $ echo eth0 >someled/device_name * $ echo link >someled/mode * * Ethernet-style link/activity LED: * $ echo netdev >someled/trigger * $ echo eth0 >someled/device_name * $ echo "link tx rx" >someled/mode * * Modem-style tx/rx LEDs: * $ echo netdev >led1/trigger * $ echo ppp0 >led1/device_name * $ echo tx >led1/mode * $ echo netdev >led2/trigger * $ echo ppp0 >led2/device_name * $ echo rx >led2/mode * */ #define MODE_LINK 1 #define MODE_TX 2 #define MODE_RX 4 struct led_netdev_data { spinlock_t lock; struct delayed_work work; struct notifier_block notifier; struct led_classdev *led_cdev; struct net_device *net_dev; char device_name[IFNAMSIZ]; unsigned interval; unsigned mode; unsigned link_up; unsigned last_activity; }; static void set_baseline_state(struct led_netdev_data *trigger_data) { if ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) led_set_brightness(trigger_data->led_cdev, LED_FULL); else led_set_brightness(trigger_data->led_cdev, LED_OFF); if ((trigger_data->mode & (MODE_TX | MODE_RX)) != 0 && trigger_data->link_up) schedule_delayed_work(&trigger_data->work, trigger_data->interval); } static ssize_t led_device_name_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_netdev_data *trigger_data = led_cdev->trigger_data; spin_lock_bh(&trigger_data->lock); sprintf(buf, "%s\n", trigger_data->device_name); spin_unlock_bh(&trigger_data->lock); return strlen(buf) + 1; } static ssize_t led_device_name_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_netdev_data *trigger_data = led_cdev->trigger_data; if (size >= IFNAMSIZ) return -EINVAL; cancel_delayed_work_sync(&trigger_data->work); spin_lock_bh(&trigger_data->lock); strcpy(trigger_data->device_name, buf); if (size > 0 && trigger_data->device_name[size-1] == '\n') trigger_data->device_name[size-1] = 0; trigger_data->link_up = 0; trigger_data->last_activity = 0; if (trigger_data->device_name[0] != 0) { /* check for existing device to update from */ trigger_data->net_dev = dev_get_by_name(&init_net, trigger_data->device_name); if (trigger_data->net_dev != NULL) trigger_data->link_up = (dev_get_flags(trigger_data->net_dev) & IFF_LOWER_UP) != 0; } set_baseline_state(trigger_data); spin_unlock_bh(&trigger_data->lock); return size; } static DEVICE_ATTR(device_name, 0644, led_device_name_show, led_device_name_store); static ssize_t led_mode_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_netdev_data *trigger_data = led_cdev->trigger_data; spin_lock_bh(&trigger_data->lock); if (trigger_data->mode == 0) { strcpy(buf, "none\n"); } else { if (trigger_data->mode & MODE_LINK) strcat(buf, "link "); if (trigger_data->mode & MODE_TX) strcat(buf, "tx "); if (trigger_data->mode & MODE_RX) strcat(buf, "rx "); strcat(buf, "\n"); } spin_unlock_bh(&trigger_data->lock); return strlen(buf)+1; } static ssize_t led_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_netdev_data *trigger_data = led_cdev->trigger_data; char copybuf[128]; int new_mode = -1; char *p, *token; /* take a copy since we don't want to trash the inbound buffer when using strsep */ strncpy(copybuf, buf, sizeof(copybuf)); copybuf[sizeof(copybuf) - 1] = 0; p = copybuf; while ((token = strsep(&p, " \t\n")) != NULL) { if (!*token) continue; if (new_mode == -1) new_mode = 0; if (!strcmp(token, "none")) new_mode = 0; else if (!strcmp(token, "tx")) new_mode |= MODE_TX; else if (!strcmp(token, "rx")) new_mode |= MODE_RX; else if (!strcmp(token, "link")) new_mode |= MODE_LINK; else return -EINVAL; } if (new_mode == -1) return -EINVAL; cancel_delayed_work_sync(&trigger_data->work); spin_lock_bh(&trigger_data->lock); trigger_data->mode = new_mode; set_baseline_state(trigger_data); spin_unlock_bh(&trigger_data->lock); return size; } static DEVICE_ATTR(mode, 0644, led_mode_show, led_mode_store); static ssize_t led_interval_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_netdev_data *trigger_data = led_cdev->trigger_data; spin_lock_bh(&trigger_data->lock); sprintf(buf, "%u\n", jiffies_to_msecs(trigger_data->interval)); spin_unlock_bh(&trigger_data->lock); return strlen(buf) + 1; } static ssize_t led_interval_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); struct led_netdev_data *trigger_data = led_cdev->trigger_data; int ret = -EINVAL; char *after; unsigned long value = simple_strtoul(buf, &after, 10); size_t count = after - buf; if (isspace(*after)) count++; /* impose some basic bounds on the timer interval */ if (count == size && value >= 5 && value <= 10000) { cancel_delayed_work_sync(&trigger_data->work); spin_lock_bh(&trigger_data->lock); trigger_data->interval = msecs_to_jiffies(value); set_baseline_state(trigger_data); /* resets timer */ spin_unlock_bh(&trigger_data->lock); ret = count; } return ret; } static DEVICE_ATTR(interval, 0644, led_interval_show, led_interval_store); static int netdev_trig_notify(struct notifier_block *nb, unsigned long evt, void *dv) { struct net_device *dev = netdev_notifier_info_to_dev((struct netdev_notifier_info *) dv); struct led_netdev_data *trigger_data = container_of(nb, struct led_netdev_data, notifier); if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER && evt != NETDEV_CHANGENAME) return NOTIFY_DONE; if (strcmp(dev->name, trigger_data->device_name)) return NOTIFY_DONE; cancel_delayed_work_sync(&trigger_data->work); spin_lock_bh(&trigger_data->lock); if (evt == NETDEV_REGISTER || evt == NETDEV_CHANGENAME) { if (trigger_data->net_dev != NULL) dev_put(trigger_data->net_dev); dev_hold(dev); trigger_data->net_dev = dev; trigger_data->link_up = 0; goto done; } if (evt == NETDEV_UNREGISTER && trigger_data->net_dev != NULL) { dev_put(trigger_data->net_dev); trigger_data->net_dev = NULL; goto done; } /* UP / DOWN / CHANGE */ trigger_data->link_up = (evt != NETDEV_DOWN && netif_carrier_ok(dev)); set_baseline_state(trigger_data); done: spin_unlock_bh(&trigger_data->lock); return NOTIFY_DONE; } /* here's the real work! */ static void netdev_trig_work(struct work_struct *work) { struct led_netdev_data *trigger_data = container_of(work, struct led_netdev_data, work.work); struct rtnl_link_stats64 *dev_stats; unsigned new_activity; struct rtnl_link_stats64 temp; if (!trigger_data->link_up || !trigger_data->net_dev || (trigger_data->mode & (MODE_TX | MODE_RX)) == 0) { /* we don't need to do timer work, just reflect link state. */ led_set_brightness(trigger_data->led_cdev, ((trigger_data->mode & MODE_LINK) != 0 && trigger_data->link_up) ? LED_FULL : LED_OFF); return; } dev_stats = dev_get_stats(trigger_data->net_dev, &temp); new_activity = ((trigger_data->mode & MODE_TX) ? dev_stats->tx_packets : 0) + ((trigger_data->mode & MODE_RX) ? dev_stats->rx_packets : 0); if (trigger_data->mode & MODE_LINK) { /* base state is ON (link present) */ /* if there's no link, we don't get this far and the LED is off */ /* OFF -> ON always */ /* ON -> OFF on activity */ if (trigger_data->led_cdev->brightness == LED_OFF) { led_set_brightness(trigger_data->led_cdev, LED_FULL); } else if (trigger_data->last_activity != new_activity) { led_set_brightness(trigger_data->led_cdev, LED_OFF); } } else { /* base state is OFF */ /* ON -> OFF always */ /* OFF -> ON on activity */ if (trigger_data->led_cdev->brightness == LED_FULL) { led_set_brightness(trigger_data->led_cdev, LED_OFF); } else if (trigger_data->last_activity != new_activity) { led_set_brightness(trigger_data->led_cdev, LED_FULL); } } trigger_data->last_activity = new_activity; schedule_delayed_work(&trigger_data->work, trigger_data->interval); } static void netdev_trig_activate(struct led_classdev *led_cdev) { struct led_netdev_data *trigger_data; int rc; trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL); if (!trigger_data) return; spin_lock_init(&trigger_data->lock); trigger_data->notifier.notifier_call = netdev_trig_notify; trigger_data->notifier.priority = 10; INIT_DELAYED_WORK(&trigger_data->work, netdev_trig_work); trigger_data->led_cdev = led_cdev; trigger_data->net_dev = NULL; trigger_data->device_name[0] = 0; trigger_data->mode = 0; trigger_data->interval = msecs_to_jiffies(50); trigger_data->link_up = 0; trigger_data->last_activity = 0; led_cdev->trigger_data = trigger_data; rc = device_create_file(led_cdev->dev, &dev_attr_device_name); if (rc) goto err_out; rc = device_create_file(led_cdev->dev, &dev_attr_mode); if (rc) goto err_out_device_name; rc = device_create_file(led_cdev->dev, &dev_attr_interval); if (rc) goto err_out_mode; register_netdevice_notifier(&trigger_data->notifier); return; err_out_mode: device_remove_file(led_cdev->dev, &dev_attr_mode); err_out_device_name: device_remove_file(led_cdev->dev, &dev_attr_device_name); err_out: led_cdev->trigger_data = NULL; kfree(trigger_data); } static void netdev_trig_deactivate(struct led_classdev *led_cdev) { struct led_netdev_data *trigger_data = led_cdev->trigger_data; if (trigger_data) { unregister_netdevice_notifier(&trigger_data->notifier); device_remove_file(led_cdev->dev, &dev_attr_device_name); device_remove_file(led_cdev->dev, &dev_attr_mode); device_remove_file(led_cdev->dev, &dev_attr_interval); cancel_delayed_work_sync(&trigger_data->work); spin_lock_bh(&trigger_data->lock); if (trigger_data->net_dev) { dev_put(trigger_data->net_dev); trigger_data->net_dev = NULL; } spin_unlock_bh(&trigger_data->lock); kfree(trigger_data); } } static struct led_trigger netdev_led_trigger = { .name = "netdev", .activate = netdev_trig_activate, .deactivate = netdev_trig_deactivate, }; static int __init netdev_trig_init(void) { return led_trigger_register(&netdev_led_trigger); } static void __exit netdev_trig_exit(void) { led_trigger_unregister(&netdev_led_trigger); } module_init(netdev_trig_init); module_exit(netdev_trig_exit); MODULE_AUTHOR("Oliver Jowett "); MODULE_DESCRIPTION("Netdev LED trigger"); MODULE_LICENSE("GPL"); r: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
# Basic Keycodes

The basic set of keycodes are based on the [HID Keyboard/Keypad Usage Page (0x07)](https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf) with the exception of `KC_NO`, `KC_TRNS` and keycodes in the `0xA5-DF` range. See below for more details.

## Letters and Numbers

|Key   |Description|
|------|-----------|
|`KC_A`|`a` and `A`|
|`KC_B`|`b` and `B`|
|`KC_C`|`c` and `C`|
|`KC_D`|`d` and `D`|
|`KC_E`|`e` and `E`|
|`KC_F`|`f` and `F`|
|`KC_G`|`g` and `G`|
|`KC_H`|`h` and `H`|
|`KC_I`|`i` and `I`|
|`KC_J`|`j` and `J`|
|`KC_K`|`k` and `K`|
|`KC_L`|`l` and `L`|
|`KC_M`|`m` and `M`|
|`KC_N`|`n` and `N`|
|`KC_O`|`o` and `O`|
|`KC_P`|`p` and `P`|
|`KC_Q`|`q` and `Q`|
|`KC_R`|`r` and `R`|
|`KC_S`|`s` and `S`|
|`KC_T`|`t` and `T`|
|`KC_U`|`u` and `U`|
|`KC_V`|`v` and `V`|
|`KC_W`|`w` and `W`|
|`KC_X`|`x` and `X`|
|`KC_Y`|`y` and `Y`|
|`KC_Z`|`z` and `Z`|
|`KC_1`|`1` and `!`|
|`KC_2`|`2` and `@`|
|`KC_3`|`3` and `#`|
|`KC_4`|`4` and `$`|
|`KC_5`|`5` and `%`|
|`KC_6`|`6` and `^`|
|`KC_7`|`7` and `&`|
|`KC_8`|`8` and `*`|
|`KC_9`|`9` and `(`|
|`KC_0`|`0` and `)`|

## F Keys

|Key     |Description|
|--------|-----------|
|`KC_F1` |F1         |
|`KC_F2` |F2         |
|`KC_F3` |F3         |
|`KC_F4` |F4         |
|`KC_F5` |F5         |
|`KC_F6` |F6         |
|`KC_F7` |F7         |
|`KC_F8` |F8         |
|`KC_F9` |F9         |
|`KC_F10`|F10        |
|`KC_F11`|F11        |
|`KC_F12`|F12        |
|`KC_F13`|F13        |
|`KC_F14`|F14        |
|`KC_F15`|F15        |
|`KC_F16`|F16        |
|`KC_F17`|F17        |
|`KC_F18`|F18        |
|`KC_F19`|F19        |
|`KC_F20`|F20        |
|`KC_F21`|F21        |
|`KC_F22`|F22        |
|`KC_F23`|F23        |
|`KC_F24`|F24        |

## Punctuation

|Key              |Aliases            |Description                                    |
|-----------------|-------------------|-----------------------------------------------|
|`KC_ENTER`       |`KC_ENT`           |Return (Enter)                                 |
|`KC_ESCAPE`      |`KC_ESC`           |Escape                                         |
|`KC_BSPACE`      |`KC_BSPC`          |Delete (Backspace)                             |
|`KC_TAB`         |                   |Tab                                            |
|`KC_SPACE`       |`KC_SPC`           |Spacebar                                       |
|`KC_MINUS`       |`KC_MINS`          |`-` and `_`                                    |
|`KC_EQUAL`       |`KC_EQL`           |`=` and `+`                                    |
|`KC_LBRACKET`    |`KC_LBRC`          |`[` and `{`                                    |
|`KC_RBRACKET`    |`KC_RBRC`          |`]` and `}`                                    |
|`KC_BSLASH`      |`KC_BSLS`          |`\` and <code>&#124;</code>                    |
|`KC_NONUS_HASH`  |`KC_NUHS`          |Non-US `#` and `~`                             |
|`KC_SCOLON`      |`KC_SCLN`          |`;` and `:`                                    |
|`KC_QUOTE`       |`KC_QUOT`          |`'` and `"`                                    |
|`KC_GRAVE`       |`KC_GRV`, `KC_ZKHK`|<code>&#96;</code> and `~`, JIS Zenkaku/Hankaku|
|`KC_COMMA`       |`KC_COMM`          |`,` and `<`                                    |
|`KC_DOT`         |                   |`.` and `>`                                    |
|`KC_SLASH`       |`KC_SLSH`          |`/` and `?`                                    |
|`KC_NONUS_BSLASH`|`KC_NUBS`          |Non-US `\` and <code>&#124;</code>             |

## Lock Keys

|Key                |Aliases             |Description              |
|-------------------|--------------------|-------------------------|
|`KC_CAPSLOCK`      |`KC_CLCK`, `KC_CAPS`|Caps Lock                |
|`KC_SCROLLLOCK`    |`KC_SLCK`           |Scroll Lock              |
|`KC_NUMLOCK`       |`KC_NLCK`           |Keypad Num Lock and Clear|
|`KC_LOCKING_CAPS`  |`KC_LCAP`           |Locking Caps Lock        |
|`KC_LOCKING_NUM`   |`KC_LNUM`           |Locking Num Lock         |
|`KC_LOCKING_SCROLL`|`KC_LSCR`           |Locking Scroll Lock      |

## Modifiers

|Key        |Aliases             |Description                         |
|-----------|--------------------|------------------------------------|
|`KC_LCTRL` |`KC_LCTL`           |Left Control                        |
|`KC_LSHIFT`|`KC_LSFT`           |Left Shift                          |
|`KC_LALT`  |                    |Left Alt                            |
|`KC_LGUI`  |`KC_LCMD`, `KC_LWIN`|Left GUI (Windows/Command/Meta key) |
|`KC_RCTRL` |`KC_RCTL`           |Right Control                       |
|`KC_RSHIFT`|`KC_RSFT`           |Right Shift                         |
|`KC_RALT`  |                    |Right Alt                           |
|`KC_RGUI`  |`KC_RCMD`, `KC_RWIN`|Right GUI (Windows/Command/Meta key)|

## International

|Key       |Aliases  |Description                    |
|----------|---------|-------------------------------|
|`KC_INT1` |`KC_RO`  |JIS `\` and `_`                |
|`KC_INT2` |`KC_KANA`|JIS Katakana/Hiragana          |
|`KC_INT3` |`KC_JYEN`|JIS `¥` and <code>&#124;</code>|
|`KC_INT4` |`KC_HENK`|JIS Henkan                     |
|`KC_INT5` |`KC_MHEN`|JIS Muhenkan                   |
|`KC_INT6` |         |JIS Numpad `,`                 |