From fa9d5491b53f5d759b93be6d2303147367f3791c Mon Sep 17 00:00:00 2001 From: Gabor Juhos Date: Sat, 31 Dec 2011 15:02:30 +0000 Subject: [PATCH] kernel: swconfig: introduce a generic switch LED trigger git-svn-id: svn://svn.openwrt.org/openwrt/trunk@29627 3c298f89-4303-0410-b956-a3cf2f4a3e73 --- target/linux/generic/config-2.6.39 | 1 + target/linux/generic/config-3.0 | 1 + target/linux/generic/config-3.1 | 1 + .../generic/files/drivers/net/phy/swconfig.c | 8 + .../files/drivers/net/phy/swconfig_leds.c | 354 ++++++++++++++++++ .../generic/files/include/linux/switch.h | 5 + .../generic/patches-2.6.39/700-swconfig.patch | 6 +- .../generic/patches-3.0/700-swconfig.patch | 6 +- .../generic/patches-3.1/700-swconfig.patch | 6 +- 9 files changed, 385 insertions(+), 3 deletions(-) create mode 100644 target/linux/generic/files/drivers/net/phy/swconfig_leds.c diff --git a/target/linux/generic/config-2.6.39 b/target/linux/generic/config-2.6.39 index 22dc9a0ea4..3f9f180964 100644 --- a/target/linux/generic/config-2.6.39 +++ b/target/linux/generic/config-2.6.39 @@ -2577,6 +2577,7 @@ CONFIG_STRIP_ASM_SYMS=y # CONFIG_SUSPEND is not set CONFIG_SWAP=y # CONFIG_SWCONFIG is not set +# CONFIG_SWCONFIG_LEDS is not set # CONFIG_SYNCLINK_CS is not set CONFIG_SYN_COOKIES=y CONFIG_SYSCTL=y diff --git a/target/linux/generic/config-3.0 b/target/linux/generic/config-3.0 index 5a92d9ff7e..f872fb7c2d 100644 --- a/target/linux/generic/config-3.0 +++ b/target/linux/generic/config-3.0 @@ -2562,6 +2562,7 @@ CONFIG_STRIP_ASM_SYMS=y # CONFIG_SUSPEND is not set CONFIG_SWAP=y # CONFIG_SWCONFIG is not set +# CONFIG_SWCONFIG_LEDS is not set # CONFIG_SYNCLINK_CS is not set CONFIG_SYN_COOKIES=y CONFIG_SYSCTL=y diff --git a/target/linux/generic/config-3.1 b/target/linux/generic/config-3.1 index 6573f4ac46..a3a0fa1000 100644 --- a/target/linux/generic/config-3.1 +++ b/target/linux/generic/config-3.1 @@ -2583,6 +2583,7 @@ CONFIG_STRIP_ASM_SYMS=y # CONFIG_SUSPEND is not set CONFIG_SWAP=y # CONFIG_SWCONFIG is not set +# CONFIG_SWCONFIG_LEDS is not set # CONFIG_SYNCLINK_CS is not set CONFIG_SYN_COOKIES=y CONFIG_SYSCTL=y diff --git a/target/linux/generic/files/drivers/net/phy/swconfig.c b/target/linux/generic/files/drivers/net/phy/swconfig.c index 2038330ba2..1f4491ac51 100644 --- a/target/linux/generic/files/drivers/net/phy/swconfig.c +++ b/target/linux/generic/files/drivers/net/phy/swconfig.c @@ -33,6 +33,8 @@ #define SWCONFIG_DEVNAME "switch%d" +#include "swconfig_leds.c" + MODULE_AUTHOR("Felix Fietkau "); MODULE_LICENSE("GPL"); @@ -863,6 +865,7 @@ register_switch(struct switch_dev *dev, struct net_device *netdev) struct switch_dev *sdev; const int max_switches = 8 * sizeof(unsigned long); unsigned long in_use = 0; + int err; int i; INIT_LIST_HEAD(&dev->dev_list); @@ -905,6 +908,10 @@ register_switch(struct switch_dev *dev, struct net_device *netdev) list_add(&dev->dev_list, &swdevs); swconfig_unlock(); + err = swconfig_create_led_trigger(dev); + if (err) + return err; + return 0; } EXPORT_SYMBOL_GPL(register_switch); @@ -912,6 +919,7 @@ EXPORT_SYMBOL_GPL(register_switch); void unregister_switch(struct switch_dev *dev) { + swconfig_destroy_led_trigger(dev); kfree(dev->portbuf); spin_lock(&dev->lock); swconfig_lock(); diff --git a/target/linux/generic/files/drivers/net/phy/swconfig_leds.c b/target/linux/generic/files/drivers/net/phy/swconfig_leds.c new file mode 100644 index 0000000000..6f54cc15b7 --- /dev/null +++ b/target/linux/generic/files/drivers/net/phy/swconfig_leds.c @@ -0,0 +1,354 @@ +/* + * swconfig_led.c: LED trigger support for the switch configuration API + * + * Copyright (C) 2011 Gabor Juhos + * + * 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 Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + */ + +#ifdef CONFIG_SWCONFIG_LEDS + +#include +#include +#include +#include + +#define SWCONFIG_LED_TIMER_INTERVAL (HZ / 10) +#define SWCONFIG_LED_NUM_PORTS 32 + +struct switch_led_trigger { + struct led_trigger trig; + struct switch_dev *swdev; + + struct delayed_work sw_led_work; + u32 port_mask; + u32 port_link; + unsigned long port_traffic[SWCONFIG_LED_NUM_PORTS]; +}; + +struct swconfig_trig_data { + struct led_classdev *led_cdev; + struct switch_dev *swdev; + + rwlock_t lock; + u32 port_mask; + + bool prev_link; + unsigned long prev_traffic; + enum led_brightness prev_brightness; +}; + +static void +swconfig_trig_set_brightness(struct swconfig_trig_data *trig_data, + enum led_brightness brightness) +{ + led_brightness_set(trig_data->led_cdev, brightness); + trig_data->prev_brightness = brightness; +} + +static void +swconfig_trig_update_port_mask(struct led_trigger *trigger) +{ + struct list_head *entry; + struct switch_led_trigger *sw_trig; + u32 port_mask; + + if (!trigger) + return; + + sw_trig = (void *) trigger; + + port_mask = 0; + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + struct swconfig_trig_data *trig_data; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + trig_data = led_cdev->trigger_data; + if (trig_data) { + read_lock(&trig_data->lock); + port_mask |= trig_data->port_mask; + read_unlock(&trig_data->lock); + } + } + read_unlock(&trigger->leddev_list_lock); + + sw_trig->port_mask = port_mask; + + if (port_mask) + schedule_delayed_work(&sw_trig->sw_led_work, + SWCONFIG_LED_TIMER_INTERVAL); + else + cancel_delayed_work_sync(&sw_trig->sw_led_work); +} + +static ssize_t +swconfig_trig_port_mask_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + unsigned long port_mask; + ssize_t ret = -EINVAL; + char *after; + size_t count; + + port_mask = simple_strtoul(buf, &after, 16); + count = after - buf; + + if (*after && isspace(*after)) + count++; + + if (count == size) { + bool changed; + + write_lock(&trig_data->lock); + + changed = (trig_data->port_mask != port_mask); + if (changed) { + trig_data->port_mask = port_mask; + if (port_mask == 0) + swconfig_trig_set_brightness(trig_data, LED_OFF); + } + + write_unlock(&trig_data->lock); + + if (changed) + swconfig_trig_update_port_mask(led_cdev->trigger); + + ret = count; + } + + return ret; +} + +static ssize_t +swconfig_trig_port_mask_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct swconfig_trig_data *trig_data = led_cdev->trigger_data; + + read_lock(&trig_data->lock); + sprintf(buf, "%#x\n", trig_data->port_mask); + read_unlock(&trig_data->lock); + + return strlen(buf) + 1; +} + +static DEVICE_ATTR(port_mask, 0644, swconfig_trig_port_mask_show, + swconfig_trig_port_mask_store); + +static void +swconfig_trig_activate(struct led_classdev *led_cdev) +{ + struct switch_led_trigger *sw_trig; + struct swconfig_trig_data *trig_data; + int err; + + if (led_cdev->trigger->activate != swconfig_trig_activate) + return; + + trig_data = kzalloc(sizeof(struct swconfig_trig_data), GFP_KERNEL); + if (!trig_data) + return; + + sw_trig = (void *) led_cdev->trigger; + + rwlock_init(&trig_data->lock); + trig_data->led_cdev = led_cdev; + trig_data->swdev = sw_trig->swdev; + led_cdev->trigger_data = trig_data; + + err = device_create_file(led_cdev->dev, &dev_attr_port_mask); + if (err) + goto err_free; + + return; + +err_free: + led_cdev->trigger_data = NULL; + kfree(trig_data); +} + +static void +swconfig_trig_deactivate(struct led_classdev *led_cdev) +{ + struct swconfig_trig_data *trig_data; + + swconfig_trig_update_port_mask(led_cdev->trigger); + + trig_data = (void *) led_cdev->trigger_data; + if (trig_data) { + device_remove_file(led_cdev->dev, &dev_attr_port_mask); + kfree(trig_data); + } +} + +static void +swconfig_trig_led_event(struct switch_led_trigger *sw_trig, + struct led_classdev *led_cdev) +{ + struct swconfig_trig_data *trig_data; + u32 port_mask; + bool link; + + trig_data = led_cdev->trigger_data; + if (!trig_data) + return; + + read_lock(&trig_data->lock); + port_mask = trig_data->port_mask; + read_unlock(&trig_data->lock); + + link = !!(sw_trig->port_link & port_mask); + if (!link) { + if (link != trig_data->prev_link) + led_brightness_set(trig_data->led_cdev, LED_OFF); + } else { + unsigned long traffic; + int i; + + traffic = 0; + for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { + if (port_mask & (1 << i)) + traffic += sw_trig->port_traffic[i]; + } + + if (trig_data->prev_brightness != LED_FULL) + swconfig_trig_set_brightness(trig_data, LED_FULL); + else if (traffic != trig_data->prev_traffic) + swconfig_trig_set_brightness(trig_data, LED_OFF); + + trig_data->prev_traffic = traffic; + } + + trig_data->prev_link = link; +} + +static void +swconfig_trig_update_leds(struct switch_led_trigger *sw_trig) +{ + struct list_head *entry; + struct led_trigger *trigger; + + trigger = &sw_trig->trig; + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + swconfig_trig_led_event(sw_trig, led_cdev); + } + read_unlock(&trigger->leddev_list_lock); +} + +static void +swconfig_led_work_func(struct work_struct *work) +{ + struct switch_led_trigger *sw_trig; + struct switch_dev *swdev; + u32 port_mask; + u32 link; + int i; + + sw_trig = container_of(work, struct switch_led_trigger, + sw_led_work.work); + + port_mask = sw_trig->port_mask; + swdev = sw_trig->swdev; + + link = 0; + for (i = 0; i < SWCONFIG_LED_NUM_PORTS; i++) { + u32 port_bit; + + port_bit = BIT(i); + if ((port_mask & port_bit) == 0) + continue; + + if (swdev->ops->get_port_link) { + struct switch_port_link port_link; + + memset(&port_link, '\0', sizeof(port_link)); + swdev->ops->get_port_link(swdev, i, &port_link); + + if (port_link.link) + link |= port_bit; + } + + if (swdev->ops->get_port_stats) { + struct switch_port_stats port_stats; + + memset(&port_stats, '\0', sizeof(port_stats)); + swdev->ops->get_port_stats(swdev, i, &port_stats); + sw_trig->port_traffic[i] = port_stats.tx_bytes + + port_stats.rx_bytes; + } + } + + sw_trig->port_link = link; + + swconfig_trig_update_leds(sw_trig); + + schedule_delayed_work(&sw_trig->sw_led_work, + SWCONFIG_LED_TIMER_INTERVAL); +} + +static int +swconfig_create_led_trigger(struct switch_dev *swdev) +{ + struct switch_led_trigger *sw_trig; + int err; + + if (!swdev->ops->get_port_link) + return 0; + + sw_trig = kzalloc(sizeof(struct switch_led_trigger), GFP_KERNEL); + if (!sw_trig) + return -ENOMEM; + + sw_trig->swdev = swdev; + sw_trig->trig.name = swdev->devname; + sw_trig->trig.activate = swconfig_trig_activate; + sw_trig->trig.deactivate = swconfig_trig_deactivate; + + INIT_DELAYED_WORK(&sw_trig->sw_led_work, swconfig_led_work_func); + + err = led_trigger_register(&sw_trig->trig); + if (err) + goto err_free; + + swdev->led_trigger = sw_trig; + + return 0; + +err_free: + kfree(sw_trig); + return err; +} + +static void +swconfig_destroy_led_trigger(struct switch_dev *swdev) +{ + struct switch_led_trigger *sw_trig; + + sw_trig = swdev->led_trigger; + if (sw_trig) { + cancel_delayed_work_sync(&sw_trig->sw_led_work); + led_trigger_unregister(&sw_trig->trig); + kfree(sw_trig); + } +} + +#else /* SWCONFIG_LEDS */ +static inline int +swconfig_create_led_trigger(struct switch_dev *swdev) { return 0; } + +static inline void +swconfig_destroy_led_trigger(struct switch_dev *swdev) { } +#endif /* CONFIG_SWCONFIG_LEDS */ diff --git a/target/linux/generic/files/include/linux/switch.h b/target/linux/generic/files/include/linux/switch.h index 04371ae741..ba1de9b18b 100644 --- a/target/linux/generic/files/include/linux/switch.h +++ b/target/linux/generic/files/include/linux/switch.h @@ -99,6 +99,7 @@ struct switch_op; struct switch_val; struct switch_attr; struct switch_attrlist; +struct switch_led_trigger; int register_switch(struct switch_dev *dev, struct net_device *netdev); void unregister_switch(struct switch_dev *dev); @@ -192,6 +193,10 @@ struct switch_dev { spinlock_t lock; struct switch_port *portbuf; + +#ifdef CONFIG_SWCONFIG_LEDS + struct switch_led_trigger *led_trigger; +#endif }; struct switch_port { diff --git a/target/linux/generic/patches-2.6.39/700-swconfig.patch b/target/linux/generic/patches-2.6.39/700-swconfig.patch index 6825037ce9..48cb64372e 100644 --- a/target/linux/generic/patches-2.6.39/700-swconfig.patch +++ b/target/linux/generic/patches-2.6.39/700-swconfig.patch @@ -1,6 +1,6 @@ --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig -@@ -13,6 +13,12 @@ menuconfig PHYLIB +@@ -13,6 +13,16 @@ menuconfig PHYLIB if PHYLIB @@ -9,6 +9,10 @@ + ---help--- + Switch configuration API using netlink. This allows + you to configure the VLAN features of certain switches. ++ ++config SWCONFIG_LEDS ++ bool "Switch LED trigger support" ++ depends on (SWCONFIG && LEDS_TRIGGERS) + comment "MII PHY device drivers" diff --git a/target/linux/generic/patches-3.0/700-swconfig.patch b/target/linux/generic/patches-3.0/700-swconfig.patch index 6825037ce9..48cb64372e 100644 --- a/target/linux/generic/patches-3.0/700-swconfig.patch +++ b/target/linux/generic/patches-3.0/700-swconfig.patch @@ -1,6 +1,6 @@ --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig -@@ -13,6 +13,12 @@ menuconfig PHYLIB +@@ -13,6 +13,16 @@ menuconfig PHYLIB if PHYLIB @@ -9,6 +9,10 @@ + ---help--- + Switch configuration API using netlink. This allows + you to configure the VLAN features of certain switches. ++ ++config SWCONFIG_LEDS ++ bool "Switch LED trigger support" ++ depends on (SWCONFIG && LEDS_TRIGGERS) + comment "MII PHY device drivers" diff --git a/target/linux/generic/patches-3.1/700-swconfig.patch b/target/linux/generic/patches-3.1/700-swconfig.patch index 6825037ce9..48cb64372e 100644 --- a/target/linux/generic/patches-3.1/700-swconfig.patch +++ b/target/linux/generic/patches-3.1/700-swconfig.patch @@ -1,6 +1,6 @@ --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig -@@ -13,6 +13,12 @@ menuconfig PHYLIB +@@ -13,6 +13,16 @@ menuconfig PHYLIB if PHYLIB @@ -9,6 +9,10 @@ + ---help--- + Switch configuration API using netlink. This allows + you to configure the VLAN features of certain switches. ++ ++config SWCONFIG_LEDS ++ bool "Switch LED trigger support" ++ depends on (SWCONFIG && LEDS_TRIGGERS) + comment "MII PHY device drivers"