summaryrefslogtreecommitdiff
path: root/drivers/leds/trigger/ledtrig-netdev.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds/trigger/ledtrig-netdev.c')
-rw-r--r--drivers/leds/trigger/ledtrig-netdev.c551
1 files changed, 427 insertions, 124 deletions
diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
index d5e774d83021..c15efe3e5078 100644
--- a/drivers/leds/trigger/ledtrig-netdev.c
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -13,17 +13,23 @@
#include <linux/atomic.h>
#include <linux/ctype.h>
#include <linux/device.h>
+#include <linux/ethtool.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/leds.h>
+#include <linux/linkmode.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/netdevice.h>
-#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/phy.h>
+#include <linux/rtnetlink.h>
#include <linux/timer.h>
#include "../leds.h"
+#define NETDEV_LED_DEFAULT_INTERVAL 50
+
/*
* Configurable sysfs attributes:
*
@@ -33,11 +39,23 @@
* (has carrier) or not
* tx - LED blinks on transmitted data
* rx - LED blinks on receive data
+ * tx_err - LED blinks on transmit error
+ * rx_err - LED blinks on receive error
*
+ * Note: If the user selects a mode that is not supported by hw, default
+ * behavior is to fall back to software control of the LED. However not every
+ * hw supports software control. LED callbacks brightness_set() and
+ * brightness_set_blocking() are NULL in this case. hw_control_is_supported()
+ * should use available means supported by hw to inform the user that selected
+ * mode isn't supported by hw. This could be switching off the LED or any
+ * hw blink mode. If software control fallback isn't possible, we return
+ * -EOPNOTSUPP to the user, but still store the selected mode. This is needed
+ * in case an intermediate unsupported mode is necessary to switch from one
+ * supported mode to another.
*/
struct led_netdev_data {
- spinlock_t lock;
+ struct mutex lock;
struct delayed_work work;
struct notifier_block notifier;
@@ -50,33 +68,75 @@ struct led_netdev_data {
unsigned int last_activity;
unsigned long mode;
-#define NETDEV_LED_LINK 0
-#define NETDEV_LED_TX 1
-#define NETDEV_LED_RX 2
-#define NETDEV_LED_MODE_LINKUP 3
-};
+ int link_speed;
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(supported_link_modes);
+ u8 duplex;
-enum netdev_led_attr {
- NETDEV_ATTR_LINK,
- NETDEV_ATTR_TX,
- NETDEV_ATTR_RX
+ bool carrier_link_up;
+ bool hw_control;
};
+static const struct attribute_group netdev_trig_link_speed_attrs_group;
+
static void set_baseline_state(struct led_netdev_data *trigger_data)
{
int current_brightness;
struct led_classdev *led_cdev = trigger_data->led_cdev;
+ /* Already validated, hw control is possible with the requested mode */
+ if (trigger_data->hw_control) {
+ led_cdev->hw_control_set(led_cdev, trigger_data->mode);
+
+ return;
+ }
+
current_brightness = led_cdev->brightness;
if (current_brightness)
led_cdev->blink_brightness = current_brightness;
if (!led_cdev->blink_brightness)
led_cdev->blink_brightness = led_cdev->max_brightness;
- if (!test_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode))
+ if (!trigger_data->carrier_link_up) {
led_set_brightness(led_cdev, LED_OFF);
- else {
- if (test_bit(NETDEV_LED_LINK, &trigger_data->mode))
+ } else {
+ bool blink_on = false;
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode))
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) &&
+ trigger_data->link_speed == SPEED_10)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) &&
+ trigger_data->link_speed == SPEED_100)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) &&
+ trigger_data->link_speed == SPEED_1000)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) &&
+ trigger_data->link_speed == SPEED_2500)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) &&
+ trigger_data->link_speed == SPEED_5000)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) &&
+ trigger_data->link_speed == SPEED_10000)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) &&
+ trigger_data->duplex == DUPLEX_HALF)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode) &&
+ trigger_data->duplex == DUPLEX_FULL)
+ blink_on = true;
+
+ if (blink_on)
led_set_brightness(led_cdev,
led_cdev->blink_brightness);
else
@@ -85,44 +145,138 @@ static void set_baseline_state(struct led_netdev_data *trigger_data)
/* If we are looking for RX/TX start periodically
* checking stats
*/
- if (test_bit(NETDEV_LED_TX, &trigger_data->mode) ||
- test_bit(NETDEV_LED_RX, &trigger_data->mode))
+ if (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode))
schedule_delayed_work(&trigger_data->work, 0);
}
}
+static bool supports_hw_control(struct led_classdev *led_cdev)
+{
+ if (!led_cdev->hw_control_get || !led_cdev->hw_control_set ||
+ !led_cdev->hw_control_is_supported)
+ return false;
+
+ return !strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name);
+}
+
+/*
+ * Validate the configured netdev is the same as the one associated with
+ * the LED driver in hw control.
+ */
+static bool validate_net_dev(struct led_classdev *led_cdev,
+ struct net_device *net_dev)
+{
+ struct device *dev = led_cdev->hw_control_get_device(led_cdev);
+ struct net_device *ndev;
+
+ if (!dev)
+ return false;
+
+ ndev = to_net_dev(dev);
+
+ return ndev == net_dev;
+}
+
+static bool can_hw_control(struct led_netdev_data *trigger_data)
+{
+ unsigned long default_interval = msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL);
+ unsigned int interval = atomic_read(&trigger_data->interval);
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
+ int ret;
+
+ if (!supports_hw_control(led_cdev))
+ return false;
+
+ /*
+ * Interval must be set to the default
+ * value. Any different value is rejected if in hw
+ * control.
+ */
+ if (interval != default_interval)
+ return false;
+
+ /*
+ * net_dev must be set with hw control, otherwise no
+ * blinking can be happening and there is nothing to
+ * offloaded. Additionally, for hw control to be
+ * valid, the configured netdev must be the same as
+ * netdev associated to the LED.
+ */
+ if (!validate_net_dev(led_cdev, trigger_data->net_dev))
+ return false;
+
+ /* Check if the requested mode is supported */
+ ret = led_cdev->hw_control_is_supported(led_cdev, trigger_data->mode);
+ /* Fall back to software blinking if not supported */
+ if (ret == -EOPNOTSUPP)
+ return false;
+ if (ret) {
+ dev_warn(led_cdev->dev,
+ "Current mode check failed with error %d\n", ret);
+ return false;
+ }
+
+ return true;
+}
+
+static void get_device_state(struct led_netdev_data *trigger_data)
+{
+ struct ethtool_link_ksettings cmd;
+
+ trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev);
+
+ if (__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd))
+ return;
+
+ if (trigger_data->carrier_link_up) {
+ trigger_data->link_speed = cmd.base.speed;
+ trigger_data->duplex = cmd.base.duplex;
+ }
+
+ /*
+ * Have a local copy of the link speed supported to avoid rtnl lock every time
+ * modes are refreshed on any change event
+ */
+ linkmode_copy(trigger_data->supported_link_modes, cmd.link_modes.supported);
+}
+
static ssize_t device_name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
ssize_t len;
- spin_lock_bh(&trigger_data->lock);
+ mutex_lock(&trigger_data->lock);
len = sprintf(buf, "%s\n", trigger_data->device_name);
- spin_unlock_bh(&trigger_data->lock);
+ mutex_unlock(&trigger_data->lock);
return len;
}
-static ssize_t device_name_store(struct device *dev,
- struct device_attribute *attr, const char *buf,
- size_t size)
+static int set_device_name(struct led_netdev_data *trigger_data,
+ const char *name, size_t size)
{
- struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
-
if (size >= IFNAMSIZ)
return -EINVAL;
cancel_delayed_work_sync(&trigger_data->work);
- spin_lock_bh(&trigger_data->lock);
+ /*
+ * Take RTNL lock before trigger_data lock to prevent potential
+ * deadlock with netdev notifier registration.
+ */
+ rtnl_lock();
+ mutex_lock(&trigger_data->lock);
if (trigger_data->net_dev) {
dev_put(trigger_data->net_dev);
trigger_data->net_dev = NULL;
}
- memcpy(trigger_data->device_name, buf, size);
+ memcpy(trigger_data->device_name, name, size);
trigger_data->device_name[size] = 0;
if (size > 0 && trigger_data->device_name[size - 1] == '\n')
trigger_data->device_name[size - 1] = 0;
@@ -131,15 +285,38 @@ static ssize_t device_name_store(struct device *dev,
trigger_data->net_dev =
dev_get_by_name(&init_net, trigger_data->device_name);
- clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
- if (trigger_data->net_dev != NULL)
- if (netif_carrier_ok(trigger_data->net_dev))
- set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
+ trigger_data->carrier_link_up = false;
+ trigger_data->link_speed = SPEED_UNKNOWN;
+ trigger_data->duplex = DUPLEX_UNKNOWN;
+ if (trigger_data->net_dev)
+ get_device_state(trigger_data);
trigger_data->last_activity = 0;
- set_baseline_state(trigger_data);
- spin_unlock_bh(&trigger_data->lock);
+ /* Skip if we're called from netdev_trig_activate() and hw_control is true */
+ if (!trigger_data->hw_control || led_get_trigger_data(trigger_data->led_cdev))
+ set_baseline_state(trigger_data);
+
+ mutex_unlock(&trigger_data->lock);
+ rtnl_unlock();
+
+ return 0;
+}
+
+static ssize_t device_name_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t size)
+{
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ int ret;
+
+ ret = set_device_name(trigger_data, buf, size);
+
+ if (ret < 0)
+ return ret;
+
+ /* Refresh link_speed visibility */
+ sysfs_update_group(&dev->kobj, &netdev_trig_link_speed_attrs_group);
return size;
}
@@ -147,20 +324,26 @@ static ssize_t device_name_store(struct device *dev,
static DEVICE_ATTR_RW(device_name);
static ssize_t netdev_led_attr_show(struct device *dev, char *buf,
- enum netdev_led_attr attr)
+ enum led_trigger_netdev_modes attr)
{
struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
int bit;
switch (attr) {
- case NETDEV_ATTR_LINK:
- bit = NETDEV_LED_LINK;
- break;
- case NETDEV_ATTR_TX:
- bit = NETDEV_LED_TX;
- break;
- case NETDEV_ATTR_RX:
- bit = NETDEV_LED_RX;
+ case TRIGGER_NETDEV_LINK:
+ case TRIGGER_NETDEV_LINK_10:
+ case TRIGGER_NETDEV_LINK_100:
+ case TRIGGER_NETDEV_LINK_1000:
+ case TRIGGER_NETDEV_LINK_2500:
+ case TRIGGER_NETDEV_LINK_5000:
+ case TRIGGER_NETDEV_LINK_10000:
+ case TRIGGER_NETDEV_HALF_DUPLEX:
+ case TRIGGER_NETDEV_FULL_DUPLEX:
+ case TRIGGER_NETDEV_TX:
+ case TRIGGER_NETDEV_RX:
+ case TRIGGER_NETDEV_TX_ERR:
+ case TRIGGER_NETDEV_RX_ERR:
+ bit = attr;
break;
default:
return -EINVAL;
@@ -170,10 +353,11 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf,
}
static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
- size_t size, enum netdev_led_attr attr)
+ size_t size, enum led_trigger_netdev_modes attr)
{
struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
- unsigned long state;
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
+ unsigned long state, mode = trigger_data->mode;
int ret;
int bit;
@@ -182,72 +366,79 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
return ret;
switch (attr) {
- case NETDEV_ATTR_LINK:
- bit = NETDEV_LED_LINK;
- break;
- case NETDEV_ATTR_TX:
- bit = NETDEV_LED_TX;
- break;
- case NETDEV_ATTR_RX:
- bit = NETDEV_LED_RX;
+ case TRIGGER_NETDEV_LINK:
+ case TRIGGER_NETDEV_LINK_10:
+ case TRIGGER_NETDEV_LINK_100:
+ case TRIGGER_NETDEV_LINK_1000:
+ case TRIGGER_NETDEV_LINK_2500:
+ case TRIGGER_NETDEV_LINK_5000:
+ case TRIGGER_NETDEV_LINK_10000:
+ case TRIGGER_NETDEV_HALF_DUPLEX:
+ case TRIGGER_NETDEV_FULL_DUPLEX:
+ case TRIGGER_NETDEV_TX:
+ case TRIGGER_NETDEV_RX:
+ case TRIGGER_NETDEV_TX_ERR:
+ case TRIGGER_NETDEV_RX_ERR:
+ bit = attr;
break;
default:
return -EINVAL;
}
- cancel_delayed_work_sync(&trigger_data->work);
-
if (state)
- set_bit(bit, &trigger_data->mode);
+ set_bit(bit, &mode);
else
- clear_bit(bit, &trigger_data->mode);
-
- set_baseline_state(trigger_data);
-
- return size;
-}
-
-static ssize_t link_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- return netdev_led_attr_show(dev, buf, NETDEV_ATTR_LINK);
-}
-
-static ssize_t link_store(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t size)
-{
- return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_LINK);
-}
-
-static DEVICE_ATTR_RW(link);
+ clear_bit(bit, &mode);
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &mode) &&
+ (test_bit(TRIGGER_NETDEV_LINK_10, &mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_100, &mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_1000, &mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_2500, &mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_5000, &mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_10000, &mode)))
+ return -EINVAL;
-static ssize_t tx_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- return netdev_led_attr_show(dev, buf, NETDEV_ATTR_TX);
-}
+ cancel_delayed_work_sync(&trigger_data->work);
-static ssize_t tx_store(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t size)
-{
- return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_TX);
-}
+ trigger_data->mode = mode;
+ trigger_data->hw_control = can_hw_control(trigger_data);
-static DEVICE_ATTR_RW(tx);
+ if (!led_cdev->brightness_set && !led_cdev->brightness_set_blocking &&
+ !trigger_data->hw_control)
+ return -EOPNOTSUPP;
-static ssize_t rx_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- return netdev_led_attr_show(dev, buf, NETDEV_ATTR_RX);
-}
+ set_baseline_state(trigger_data);
-static ssize_t rx_store(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t size)
-{
- return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_RX);
+ return size;
}
-static DEVICE_ATTR_RW(rx);
+#define DEFINE_NETDEV_TRIGGER(trigger_name, trigger) \
+ static ssize_t trigger_name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+ { \
+ return netdev_led_attr_show(dev, buf, trigger); \
+ } \
+ static ssize_t trigger_name##_store(struct device *dev, \
+ struct device_attribute *attr, const char *buf, size_t size) \
+ { \
+ return netdev_led_attr_store(dev, buf, size, trigger); \
+ } \
+ static DEVICE_ATTR_RW(trigger_name)
+
+DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK);
+DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10);
+DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100);
+DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000);
+DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500);
+DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000);
+DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000);
+DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX);
+DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX);
+DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
+DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX);
+DEFINE_NETDEV_TRIGGER(tx_err, TRIGGER_NETDEV_TX_ERR);
+DEFINE_NETDEV_TRIGGER(rx_err, TRIGGER_NETDEV_RX_ERR);
static ssize_t interval_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -266,6 +457,9 @@ static ssize_t interval_store(struct device *dev,
unsigned long value;
int ret;
+ if (trigger_data->hw_control)
+ return -EINVAL;
+
ret = kstrtoul(buf, 0, &value);
if (ret)
return ret;
@@ -283,15 +477,93 @@ static ssize_t interval_store(struct device *dev,
static DEVICE_ATTR_RW(interval);
+static ssize_t offloaded_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", trigger_data->hw_control);
+}
+
+static DEVICE_ATTR_RO(offloaded);
+
+#define CHECK_LINK_MODE_ATTR(link_speed) \
+ do { \
+ if (attr == &dev_attr_link_##link_speed.attr && \
+ link_ksettings.base.speed == SPEED_##link_speed) \
+ return attr->mode; \
+ } while (0)
+
+static umode_t netdev_trig_link_speed_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct led_netdev_data *trigger_data;
+ unsigned long *supported_link_modes;
+ u32 mode;
+
+ trigger_data = led_trigger_get_drvdata(dev);
+ supported_link_modes = trigger_data->supported_link_modes;
+
+ /*
+ * Search in the supported link mode mask a matching supported mode.
+ * Stop at the first matching entry as we care only to check if a particular
+ * speed is supported and not the kind.
+ */
+ for_each_set_bit(mode, supported_link_modes, __ETHTOOL_LINK_MODE_MASK_NBITS) {
+ struct ethtool_link_ksettings link_ksettings;
+
+ ethtool_params_from_link_mode(&link_ksettings, mode);
+
+ CHECK_LINK_MODE_ATTR(10);
+ CHECK_LINK_MODE_ATTR(100);
+ CHECK_LINK_MODE_ATTR(1000);
+ CHECK_LINK_MODE_ATTR(2500);
+ CHECK_LINK_MODE_ATTR(5000);
+ CHECK_LINK_MODE_ATTR(10000);
+ }
+
+ return 0;
+}
+
+static struct attribute *netdev_trig_link_speed_attrs[] = {
+ &dev_attr_link_10.attr,
+ &dev_attr_link_100.attr,
+ &dev_attr_link_1000.attr,
+ &dev_attr_link_2500.attr,
+ &dev_attr_link_5000.attr,
+ &dev_attr_link_10000.attr,
+ NULL
+};
+
+static const struct attribute_group netdev_trig_link_speed_attrs_group = {
+ .attrs = netdev_trig_link_speed_attrs,
+ .is_visible = netdev_trig_link_speed_visible,
+};
+
static struct attribute *netdev_trig_attrs[] = {
&dev_attr_device_name.attr,
&dev_attr_link.attr,
+ &dev_attr_full_duplex.attr,
+ &dev_attr_half_duplex.attr,
&dev_attr_rx.attr,
&dev_attr_tx.attr,
+ &dev_attr_rx_err.attr,
+ &dev_attr_tx_err.attr,
&dev_attr_interval.attr,
+ &dev_attr_offloaded.attr,
NULL
};
-ATTRIBUTE_GROUPS(netdev_trig);
+
+static const struct attribute_group netdev_trig_attrs_group = {
+ .attrs = netdev_trig_attrs,
+};
+
+static const struct attribute_group *netdev_trig_groups[] = {
+ &netdev_trig_attrs_group,
+ &netdev_trig_link_speed_attrs_group,
+ NULL,
+};
static int netdev_trig_notify(struct notifier_block *nb,
unsigned long evt, void *dv)
@@ -300,6 +572,7 @@ static int netdev_trig_notify(struct notifier_block *nb,
netdev_notifier_info_to_dev((struct netdev_notifier_info *)dv);
struct led_netdev_data *trigger_data =
container_of(nb, struct led_netdev_data, notifier);
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE
&& evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER
@@ -313,31 +586,39 @@ static int netdev_trig_notify(struct notifier_block *nb,
cancel_delayed_work_sync(&trigger_data->work);
- spin_lock_bh(&trigger_data->lock);
+ mutex_lock(&trigger_data->lock);
- clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
+ trigger_data->carrier_link_up = false;
+ trigger_data->link_speed = SPEED_UNKNOWN;
+ trigger_data->duplex = DUPLEX_UNKNOWN;
switch (evt) {
case NETDEV_CHANGENAME:
case NETDEV_REGISTER:
- if (trigger_data->net_dev)
- dev_put(trigger_data->net_dev);
+ dev_put(trigger_data->net_dev);
dev_hold(dev);
trigger_data->net_dev = dev;
+ if (evt == NETDEV_CHANGENAME)
+ get_device_state(trigger_data);
break;
case NETDEV_UNREGISTER:
dev_put(trigger_data->net_dev);
trigger_data->net_dev = NULL;
break;
case NETDEV_UP:
+ trigger_data->hw_control = can_hw_control(trigger_data);
+ fallthrough;
case NETDEV_CHANGE:
- if (netif_carrier_ok(dev))
- set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
+ get_device_state(trigger_data);
+ /* Refresh link_speed visibility */
+ if (evt == NETDEV_CHANGE)
+ sysfs_update_group(&led_cdev->dev->kobj,
+ &netdev_trig_link_speed_attrs_group);
break;
}
set_baseline_state(trigger_data);
- spin_unlock_bh(&trigger_data->lock);
+ mutex_unlock(&trigger_data->lock);
return NOTIFY_DONE;
}
@@ -360,21 +641,35 @@ static void netdev_trig_work(struct work_struct *work)
}
/* If we are not looking for RX/TX then return */
- if (!test_bit(NETDEV_LED_TX, &trigger_data->mode) &&
- !test_bit(NETDEV_LED_RX, &trigger_data->mode))
+ if (!test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) &&
+ !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) &&
+ !test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) &&
+ !test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode))
return;
dev_stats = dev_get_stats(trigger_data->net_dev, &temp);
new_activity =
- (test_bit(NETDEV_LED_TX, &trigger_data->mode) ?
+ (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ?
dev_stats->tx_packets : 0) +
- (test_bit(NETDEV_LED_RX, &trigger_data->mode) ?
- dev_stats->rx_packets : 0);
+ (test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ?
+ dev_stats->rx_packets : 0) +
+ (test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) ?
+ dev_stats->tx_errors : 0) +
+ (test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode) ?
+ dev_stats->rx_errors : 0);
if (trigger_data->last_activity != new_activity) {
led_stop_software_blink(trigger_data->led_cdev);
- invert = test_bit(NETDEV_LED_LINK, &trigger_data->mode);
+ invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode);
interval = jiffies_to_msecs(
atomic_read(&trigger_data->interval));
/* base state is ON (link present) */
@@ -392,13 +687,15 @@ static void netdev_trig_work(struct work_struct *work)
static int netdev_trig_activate(struct led_classdev *led_cdev)
{
struct led_netdev_data *trigger_data;
+ unsigned long mode = 0;
+ struct device *dev;
int rc;
trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL);
if (!trigger_data)
return -ENOMEM;
- spin_lock_init(&trigger_data->lock);
+ mutex_init(&trigger_data->lock);
trigger_data->notifier.notifier_call = netdev_trig_notify;
trigger_data->notifier.priority = 10;
@@ -410,9 +707,26 @@ static int netdev_trig_activate(struct led_classdev *led_cdev)
trigger_data->device_name[0] = 0;
trigger_data->mode = 0;
- atomic_set(&trigger_data->interval, msecs_to_jiffies(50));
+ atomic_set(&trigger_data->interval, msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL));
trigger_data->last_activity = 0;
+ /* Check if hw control is active by default on the LED.
+ * Init already enabled mode in hw control.
+ */
+ if (supports_hw_control(led_cdev)) {
+ dev = led_cdev->hw_control_get_device(led_cdev);
+ if (dev) {
+ const char *name = dev_name(dev);
+
+ trigger_data->hw_control = true;
+ set_device_name(trigger_data, name, strlen(name));
+
+ rc = led_cdev->hw_control_get(led_cdev, &mode);
+ if (!rc)
+ trigger_data->mode = mode;
+ }
+ }
+
led_set_trigger_data(led_cdev, trigger_data);
rc = register_netdevice_notifier(&trigger_data->notifier);
@@ -430,8 +744,7 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev)
cancel_delayed_work_sync(&trigger_data->work);
- if (trigger_data->net_dev)
- dev_put(trigger_data->net_dev);
+ dev_put(trigger_data->net_dev);
kfree(trigger_data);
}
@@ -443,20 +756,10 @@ static struct led_trigger netdev_led_trigger = {
.groups = netdev_trig_groups,
};
-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_led_trigger(netdev_led_trigger);
MODULE_AUTHOR("Ben Whitten <ben.whitten@gmail.com>");
MODULE_AUTHOR("Oliver Jowett <oliver@opencloud.com>");
MODULE_DESCRIPTION("Netdev LED trigger");
MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("ledtrig:netdev");