From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=209.85.128.45 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793397579; b=qCIWW0eBDwijRaX1y6y9rZbMz8K3aNt096Oh2Y08XZuo5tP53PSgjjruuLRAPjieNoAq 63SvZI5g1T127b8f5yO6MPhgj4HGZRL8yjAPCKa1Ay31PxtB+aIjXFNyzr3YbU3vhibo6 smKuKiaHicrO3QIeo1Pla396U0kIKiCF4xO3HYDOsiJoWoqCqB4uy1smTKqvHK2Smux+E Eo+3VkejA9qL+mw4KXEN8qzmPsOqLMqVacGeXuwgN9QJCWlku6WU0untfkR8WBgDJW1c3 WU1y523rka5LZF3jmqERS+XZB+sk20FggW1AzXvaQ5pZLTC8/PQrUZLLHR19A1Md7MQ== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793397579; h=DMARC-Filter:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Forwarded-Encrypted:X-Gm-Message-State:X-Gm-Gg:X-Received:Received: From:To:Cc:Subject:Date:Message-ID:X-Mailer:MIME-Version: Content-Transfer-Encoding; bh=LaJ11rRvNM7F5mpfLmNq4MiUzrRFshUKgjdjahPHWq4=; b=bD5bJtzEC0M5xdc3g5LSOM4u1KzB4b746EPfDSvDquIIdOjmLLvP0NY6Vf+6CKcef11a kieSJznm1h0wZrK3BwsPthCuqbaEGXsLh1LY2JQTD3x7cq6e09fnHvrfQgqeAZR9nwqiW kvSN/+xe/GQtkAXNlZpyzQpNo26UsD9nEV5FkKssa+n0ULYLBG0VWTiiJGN/cHUTAU4bv 7zhAwkcA4UZFB9savlJSR4V2Usr7dEB6KAilXoX5oRSuC5AFw3WisDzs4FtS/Z5flfVrt pb8eAGLXou3WEKGOq8RpA+ra0cugFqkZbdO3pz5t6uDboAbLAP3oPXaCwLniDquuCWw== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=bVGU+BPq; arc=none smtp.remote-ip=209.85.128.45 Received: from mail-wm1-f45.google.com (mail-wm1-f45.google.com [209.85.128.45]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 79190606A31D for ; Fri, 30 Oct 2026 21:59:38 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org 79190606A31D Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=bVGU+BPq Received: by mail-wm1-f45.google.com with SMTP id 5b1f17b1804b1-4a5177d78c7so12565905e9.2 for ; Fri, 30 Oct 2026 14:59:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793397577; x=1794002377; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=LaJ11rRvNM7F5mpfLmNq4MiUzrRFshUKgjdjahPHWq4=; b=bVGU+BPqpqRoun2/Z+snr9OGRlOyab2c0bnbATOkzoOgLgkCnDqq0ZpmnYkWzsWdMt MdzbJxJA2agsIRYQTVVDST3BqvoGsWbTxnIM6M2opdA+kKbWqmrTfOb5c/lTV4IZKgrs X8kHySX07NX8/HqGnEECfIgAB/QwmIO38xtgqcN+ajFSPWfgX0q29vYeSMsVntZs4gGb 1JJWcVqjFoJ0OScRx0aOt3lFhs3RgMUOEGVqQf1XcJgbX0MFkeMOUh6mN/YKQuW4qjZI YmIGbp6FC/SQDU6zvIxcPwx+p0PzPM2sBkESXSNuWD9PUJS84Jr5yEJZhIsTBm9+VKXx wzZg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793397577; x=1794002377; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=LaJ11rRvNM7F5mpfLmNq4MiUzrRFshUKgjdjahPHWq4=; b=JHJCywhsFCxhfLHG2KRJ4xqBssHcCzVt5A4+gHbpnW5Kf86wVZa+ofGwcC1cn3VW1q qu5SITOa+iRB8P1Fd2hWucHkniPQydJGm+gRs8h0BOVHKB+l639f6gNiKZLyd8e+ztxN fT5ccNYP4lhutlwcFGfjRPZec4iI0d/XN75Ucn9zVOxUZyUP4dsncEVfFKKc/reqTK6c IqX5qu9JCxcsKGTftkHRsC6ZLivZCuET4KEXLyYE6UcXO/Ew83AFIEBjYDXqyNfgIXC8 KViyJibvUrUqH4mlcaHBcwG7xjg6n87NL4XGI/MtcEof2eMo/w1YdjO6/IoTP9Y5hd4y fs4Q== X-Forwarded-Encrypted: i=1; AKwUvBzDCgxskLVsnLCfBhmftaLwiOyaZfLUoCxFsJp3R27aIhNYR9fkmCRQ631bnqtsM92CapXwNGlmHSZRXwA=@vger.kernel.org X-Gm-Message-State: AFq9FYI6pMxMVmnwuNez3hP0+LG/xXdOIlkQzrxzKUT6qhQ2xmPxhyxr ED3gsXIDlLd4V+xiwEEe3K7dAwBtG7HrJ0KqIchSGhTqLxrYDs7SSerN5+/Rh30Z5NnnCQ== X-Gm-Gg: AYBFou1ps/NxOMIxr7gbFDfnZjawn1wlFflgNmVmRt+7bsNVEtInE4hac/QGHv+gfDe HaVmnJMkfB77nkhpKIjVLr99v1g/XMofaVuu+rfmlu2z4U+ayGZBPDk195U4MzgactOYoVdBN82 77GSBeHNqMnXRlbQjNc1+24iy5p0deOck6ndtEBBs0XMDNEuLlU90VbrMoa3hPNkAMNjILor6Sm C3WaaY1hsqieUXEPMnQKHfH7d/jGfR+ay0zPIh0Hr30Q48J+cH3K1IpWbgR8nqOOQNYlGLOVcfW m6EtV89Pw3XDppG3xAGxgVENvZ6a6aLW/x2oUOEPL1jZFNr76DiA5LE4ZvLKJAcKLbnr8c8WEZl KW6Fav7q3swV44d004gvd2AQcrGD+XavnBb5rtNVTpzv6eiBecbx2Z0J3XkQ2UevFhzqyVQ4Tf9 aWv+Z8Sb9CX9/dAf8P6h8hdDX8BRnbsgPBN0PdWnWx2oowPe+nf5wuxkv2brR45QN1UEtF4cFzc 6r1b8ceiika/43GI70UzvlPlBhB7pjor5QlZOTUC8fz9OGhIKNHsB1quGIZr7pYQHdE0BHgTDfD 3tdZiT70+4r1vJKR9xuLEihSZ0xfp49PBlVEJ9D4GFsl X-Received: by 2002:a05:600c:a012:b0:4a2:150f:1609 with SMTP id 5b1f17b1804b1-4ac17b2cdffmr76561575e9.8.1793397576641; Fri, 30 Oct 2026 14:59:36 -0700 (PDT) Received: from linux-virtualbox ([176.123.18.39]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4aa80f04785sm163840985e9.4.2026.10.30.14.59.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Oct 2026 14:59:36 -0700 (PDT) From: Salah Ahmed To: Lee Jones , Pavel Machek , linux-leds@vger.kernel.org Cc: Griffin Kroah-Hartman , Peng Fan , Rosen Penev , Rong Zhang , linux-kernel@vger.kernel.org Subject: [PATCH] leds: trigger-netdev: add byte-rate threshold mode Date: Sat, 31 Oct 2026 00:59:24 +0300 Message-ID: <20261030215924.18060-1-haddad2inc@gmail.com> X-Mailer: git-send-email 2.53.0 X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit The tx/rx modes blink once per observed change of the packet counters, which misses traffic forwarded in hardware (DSA bridge offload) and flickers on background broadcast noise. Add a rate_threshold attribute (KiB/s): when non-zero the trigger drives the LED from the interface byte rate instead, blinking on a 50..150 ms ladder above the threshold and resting in the baseline state (solid on link, dark without carrier) below it. The rate is measured over the interval between two observed counter changes, not over a fixed window. Drivers that refresh their counters in bursts - a cached switch MIB polled every few seconds, for instance - would otherwise dump a whole accumulation period into the first fixed window that follows the refresh: the reported rate is then several times the real one, followed by zero until the next refresh, which turns the LED into a stutter instead of a steady blink. Measuring between changes makes the trigger independent of the driver's refresh interval, and the last measured rate is kept until the counter has been silent for twice the interval it was measured over (capped at 10 s) so that the blink lasts as long as the traffic does. The blink is re-armed only when the ladder step changes: led_blink_set() restarts the software blink timer and truncates the phase in progress, so re-arming it on every evaluation shows up as an irregular cadence. Rate mode is independent of the link/tx/rx mode bits on purpose, so that ports whose packet counters are blind to offloaded flows still show activity. It yields to hw_control, and writing 0 restores the link state even for an LED with no tx/rx bit set, because the packet path only touches the LED when the activity counter changes. Byte counters can legitimately go backwards when a driver resets them, and an unsigned subtraction would then report an enormous rate, so a decreasing counter re-anchors the measurement instead. div_u64() keeps 32-bit architectures linking without __udivdi3/__aeabi_uldivmod. Bursty transfers can straddle a ladder boundary on every evaluation, and cutting the blink off the instant the counter goes quiet makes a pausing transfer look like the LED died. The measured rate is therefore smoothed the way field-accepted userspace activity daemons do it - immediate attack at or above the threshold, a slow rise and a quick release below it - and when the counter goes silent the blink fades out quadratically from the last peak over 20 s, with the ladder step only ever slowing during the fade. set_baseline_state() runs after every netdev notifier event and after every mode or device_name write, and both callers cancel the work first. It sets a solid brightness and cancels the work first, so with a rate threshold set it also owns the rate-mode bookkeeping: clear the armed ladder step, stop a software blink that is still armed (while it runs, a solid brightness set here only lands in blink_brightness), and reschedule the work. Rescheduling is what keeps rate mode alive on a link-only LED across carrier changes and honest on tx/rx LEDs under load: without it the work computes the same ladder step as before, skips the re-arm while the brightness has just been reset to solid, and a below-threshold sample after a cleared rate_last_ms leaves an old blink running. The state a rate-mode blink rests in is the trigger's baseline state, not a second policy: netdev_baseline_on() answers "is this LED on at rest?" from the same link, speed and duplex mode bits the baseline uses, and netdev_set_resting() applies the same level, blink_brightness. Both set_baseline_state() and the post-blink path call it, so the two cannot drift apart: a mode without a link bit rests dark, and a multi-level LED rests at its blinking level rather than at LED_ON (1), which would read nearly dark. On a single-level LED (max_brightness = 1) the two values coincide, so nothing differs there. Signed-off-by: Salah Ahmed --- drivers/leds/trigger/ledtrig-netdev.c | 417 +++++++++++++++++++++----- 1 file changed, 349 insertions(+), 68 deletions(-) diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c index 5b01324845..941cb13cdf 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -31,6 +32,21 @@ #define NETDEV_LED_DEFAULT_INTERVAL 50 +/* Rate mode: a measured rate is kept for two counter refresh intervals before + * it counts as stale, capped so that a dead link cannot blink for long. + * NETDEV_RATE_MAX_ELAPSED_MS keeps the u32 divisor of div_u64() in range. + */ +#define NETDEV_RATE_STALE_MULT 2 +#define NETDEV_RATE_STALE_MAX (10 * HZ) +#define NETDEV_RATE_MAX_ELAPSED_MS 1000000 + +/* Afterglow: when the counter goes quiet the blink does not cut off at once; + * the last measured peak fades out quadratically over this window, mirroring + * the decay shape of the userspace activity daemons this mode replaces in the + * field, so a transfer that pauses briefly does not look like the LED died. + */ +#define NETDEV_RATE_AFTERGLOW (20 * HZ) + /* * Configurable sysfs attributes: * @@ -42,6 +58,14 @@ * rx - LED blinks on receive data * tx_err - LED blinks on transmit error * rx_err - LED blinks on receive error + * rate_threshold - KiB/s; when non-zero the trigger blinks according to + * the interface byte rate (ladder 50..150 ms) instead of per-packet, + * resting in the baseline (link) state below the threshold. + * Byte-rate mode sees traffic that packet counters miss once the + * driver reports wire bytes in its stats (e.g. cached switch MIB on + * DSA ports). The rate is measured between two counter updates + * rather than over a fixed window, so counters that are refreshed + * in bursts report their true average rate. * * 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 @@ -68,6 +92,23 @@ struct led_netdev_data { atomic_t interval; unsigned int last_activity; + /* Rate mode state: rate_bytes/rate_jiffies anchor the last observed + * counter change, rate_interval is the time that produced it, + * rate_kbps the rate measured over that interval, and rate_last_ms the + * blink period currently armed (0 = solid). rate_threshold_kbps is the + * threshold in KiB/s, 0 = rate mode disabled. + */ + u64 rate_bytes; + unsigned long rate_jiffies; + unsigned long rate_interval; + unsigned long rate_kbps; + unsigned long rate_last_ms; + unsigned long rate_threshold_kbps; + unsigned long rate_smooth; + unsigned long rate_peak; + unsigned long rate_ag; + bool rate_valid; + unsigned long mode; int link_speed; __ETHTOOL_DECLARE_LINK_MODE_MASK(supported_link_modes); @@ -79,6 +120,80 @@ struct led_netdev_data { static const struct attribute_group netdev_trig_link_speed_attrs_group; +/* + * The resting (non-blinking) state of a netdev LED. The link and duplex + * mode bits decide whether it is lit at all, and the level it is lit at is + * blink_brightness rather than LED_ON, so that a multi-level LED is not + * dimmed to its lowest step and a tx/rx-only LED is not left solid once the + * traffic stops. Both the baseline path and the rate-mode path that returns + * the LED to rest after a blink use this, so they cannot drift apart. + */ +static bool netdev_baseline_on(struct led_netdev_data *trigger_data) +{ + if (test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode)) + return true; + + if (test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) && + trigger_data->link_speed == SPEED_10) + return true; + + if (test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) && + trigger_data->link_speed == SPEED_100) + return true; + + if (test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_1000) + return true; + + if (test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) && + trigger_data->link_speed == SPEED_2500) + return true; + + if (test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_5000) + return true; + + if (test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_10000) + return true; + + if (test_bit(TRIGGER_NETDEV_LINK_25000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_25000) + return true; + + if (test_bit(TRIGGER_NETDEV_LINK_40000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_40000) + return true; + + if (test_bit(TRIGGER_NETDEV_LINK_50000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_50000) + return true; + + if (test_bit(TRIGGER_NETDEV_LINK_100000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_100000) + return true; + + if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) && + trigger_data->duplex == DUPLEX_HALF) + return true; + + if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode) && + trigger_data->duplex == DUPLEX_FULL) + return true; + + return false; +} + +static void netdev_set_resting(struct led_netdev_data *trigger_data) +{ + struct led_classdev *led_cdev = trigger_data->led_cdev; + + if (trigger_data->carrier_link_up && netdev_baseline_on(trigger_data)) + led_set_brightness(led_cdev, led_cdev->blink_brightness); + else + led_set_brightness(led_cdev, LED_OFF); +} + static void set_baseline_state(struct led_netdev_data *trigger_data) { int current_brightness; @@ -91,85 +206,54 @@ static void set_baseline_state(struct led_netdev_data *trigger_data) return; } + /* While a rate-mode software blink is still armed, the solid + * brightness below only lands in blink_brightness (LED_BLINK_SW), + * and the work re-arms the blink only on a ladder step change: with + * rate_last_ms cleared, a below-threshold sample computes the same + * zero step and the old blink keeps running. Stop it here; the work + * re-arms it from the measured rate, as rate_threshold_store() does. + */ + if (trigger_data->rate_threshold_kbps) + led_stop_software_blink(led_cdev); + 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 (!trigger_data->carrier_link_up) { - led_set_brightness(led_cdev, LED_OFF); - } else { - bool blink_on = false; + netdev_set_resting(trigger_data); - 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_LINK_25000, &trigger_data->mode) && - trigger_data->link_speed == SPEED_25000) - blink_on = true; - - if (test_bit(TRIGGER_NETDEV_LINK_40000, &trigger_data->mode) && - trigger_data->link_speed == SPEED_40000) - blink_on = true; - - if (test_bit(TRIGGER_NETDEV_LINK_50000, &trigger_data->mode) && - trigger_data->link_speed == SPEED_50000) - blink_on = true; - - if (test_bit(TRIGGER_NETDEV_LINK_100000, &trigger_data->mode) && - trigger_data->link_speed == SPEED_100000) - 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 - led_set_brightness(led_cdev, LED_OFF); - - /* If we are looking for RX/TX start periodically - * checking stats + /* If we are looking for RX/TX (or a rate threshold is set) start + * periodically checking stats + */ + if (trigger_data->carrier_link_up && + (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) || + trigger_data->rate_threshold_kbps)) { + /* Rate mode re-arms the blink only when the ladder step + * changes, and the brightness has just been set to a solid + * state, so forget the step that was armed before this. */ - 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); + trigger_data->rate_last_ms = 0; + schedule_delayed_work(&trigger_data->work, 0); } } +/* Drop the measured rate whenever the counter source changes, so that the + * next sample re-anchors instead of subtracting counters of two interfaces. + */ +static void netdev_trig_rate_reset(struct led_netdev_data *trigger_data) +{ + trigger_data->rate_valid = false; + trigger_data->rate_kbps = 0; + trigger_data->rate_smooth = 0; + trigger_data->rate_peak = 0; + trigger_data->rate_ag = 0; +} + static bool supports_hw_control(struct led_classdev *led_cdev) { if (!led_cdev->hw_control_get || !led_cdev->hw_control_set || @@ -311,6 +395,7 @@ static int set_device_name(struct led_netdev_data *trigger_data, get_device_state(trigger_data); trigger_data->last_activity = 0; + netdev_trig_rate_reset(trigger_data); /* 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)) @@ -509,7 +594,51 @@ static ssize_t interval_store(struct device *dev, return size; } +static ssize_t rate_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + + return sprintf(buf, "%lu\n", trigger_data->rate_threshold_kbps); +} + +static ssize_t rate_threshold_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); + unsigned long value; + int ret; + + if (trigger_data->hw_control) + return -EINVAL; + + ret = kstrtoul(buf, 0, &value); + if (ret) + return ret; + + cancel_delayed_work_sync(&trigger_data->work); + + trigger_data->rate_threshold_kbps = value; + trigger_data->rate_last_ms = 0; + netdev_trig_rate_reset(trigger_data); + + /* Leaving rate mode has to undo the blink it armed: while a software + * blink is running led_set_brightness() only changes the blink + * brightness, and the packet mode below touches the LED only when the + * activity counter changes, so a link-only LED would keep blinking. + */ + led_stop_software_blink(trigger_data->led_cdev); + set_baseline_state(trigger_data); /* resets timer */ + + if (value) + schedule_delayed_work(&trigger_data->work, 0); + + return size; +} + static DEVICE_ATTR_RW(interval); +static DEVICE_ATTR_RW(rate_threshold); static ssize_t offloaded_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -593,6 +722,7 @@ static struct attribute *netdev_trig_attrs[] = { &dev_attr_rx_err.attr, &dev_attr_tx_err.attr, &dev_attr_interval.attr, + &dev_attr_rate_threshold.attr, &dev_attr_offloaded.attr, NULL }; @@ -639,12 +769,14 @@ static int netdev_trig_notify(struct notifier_block *nb, dev_put(trigger_data->net_dev); dev_hold(dev); trigger_data->net_dev = dev; + netdev_trig_rate_reset(trigger_data); if (evt == NETDEV_CHANGENAME) get_device_state(trigger_data); break; case NETDEV_UNREGISTER: dev_put(trigger_data->net_dev); trigger_data->net_dev = NULL; + netdev_trig_rate_reset(trigger_data); break; case NETDEV_UP: trigger_data->hw_control = can_hw_control(trigger_data); @@ -674,6 +806,8 @@ static void netdev_trig_work(struct work_struct *work) unsigned int new_activity; struct rtnl_link_stats64 temp; unsigned long interval; + unsigned long now, ms, elapsed, stale; + u64 bytes; int invert; /* If we dont have a device, insure we are off */ @@ -682,6 +816,153 @@ static void netdev_trig_work(struct work_struct *work) return; } + /* Rate mode: blink on a ladder driven by the interface byte rate instead + * of per packet. Independent of the packet-mode bits so that interfaces + * whose packet counters are blind to hardware-forwarded traffic still + * show activity, and so that a sub-threshold link rests in its baseline + * state (solid with a link bit, dark without one). + * + * The rate is measured over the interval between two observed counter + * changes, not over a fixed window. Counters that are refreshed in + * bursts - a cached switch MIB polled every few seconds, for instance - + * dump their whole accumulation into the first fixed window that follows + * the refresh, which reports a rate several times too high and then + * zero until the next refresh. Measuring between changes makes the + * trigger independent of how often the driver refreshes its counters, + * and keeps the blink running for as long as the traffic does. + */ + if (trigger_data->rate_threshold_kbps && !trigger_data->hw_control) { + dev_stats = dev_get_stats(trigger_data->net_dev, &temp); + bytes = dev_stats->rx_bytes + dev_stats->tx_bytes; + now = jiffies; + + if (!trigger_data->rate_valid) { + trigger_data->rate_bytes = bytes; + trigger_data->rate_jiffies = now; + trigger_data->rate_interval = HZ; + trigger_data->rate_valid = true; + } else if (bytes != trigger_data->rate_bytes) { + if (bytes > trigger_data->rate_bytes) { + trigger_data->rate_interval = + now - trigger_data->rate_jiffies; + elapsed = jiffies_to_msecs(trigger_data->rate_interval); + if (elapsed > NETDEV_RATE_MAX_ELAPSED_MS) + elapsed = NETDEV_RATE_MAX_ELAPSED_MS; + trigger_data->rate_kbps = + div_u64((bytes - trigger_data->rate_bytes) * 1000, + elapsed * 1024); + } else { + /* Counters went backwards (reset or wrapped): + * re-anchor instead of reporting a huge rate. + */ + trigger_data->rate_kbps = 0; + } + trigger_data->rate_bytes = bytes; + trigger_data->rate_jiffies = now; + } else { + /* No change: keep the last measured rate for as long as + * the counter keeps being refreshed, then fall back to + * the link state. + */ + stale = trigger_data->rate_interval * NETDEV_RATE_STALE_MULT; + if (stale > NETDEV_RATE_STALE_MAX) + stale = NETDEV_RATE_STALE_MAX; + if (time_after(now, trigger_data->rate_jiffies + stale)) + trigger_data->rate_kbps = 0; + } + + /* Smoothing ported from the userspace daemon this replaces: attack + * immediately at or above the threshold so the first evaluation of + * a transfer already blinks, rise slowly and release quickly below + * it so the ladder step does not chatter when a bursty rate + * straddles a boundary. + */ + if (trigger_data->rate_kbps >= trigger_data->rate_threshold_kbps) + trigger_data->rate_smooth = trigger_data->rate_kbps; + else if (trigger_data->rate_kbps < trigger_data->rate_smooth) + trigger_data->rate_smooth = (trigger_data->rate_smooth + + 3 * trigger_data->rate_kbps) / 4; + else + trigger_data->rate_smooth = (3 * trigger_data->rate_smooth + + trigger_data->rate_kbps) / 4; + + if (trigger_data->rate_smooth >= trigger_data->rate_threshold_kbps) { + if (trigger_data->rate_smooth > trigger_data->rate_peak) + trigger_data->rate_peak = trigger_data->rate_smooth; + trigger_data->rate_ag = 0; + } + + ms = 0; + if (trigger_data->carrier_link_up) { + unsigned long rate = trigger_data->rate_smooth; + + /* Quadratic afterglow from the last peak: the synthetic rate + * decays as peak * remaining^2 / window^2 over + * NETDEV_RATE_AFTERGLOW, then the LED returns to the link + * state. div_u64() keeps 32-bit builds linking. + */ + if (rate < trigger_data->rate_threshold_kbps && + trigger_data->rate_peak >= trigger_data->rate_threshold_kbps && + trigger_data->rate_ag < NETDEV_RATE_AFTERGLOW) { + unsigned long rem; + + trigger_data->rate_ag += HZ / 2; + rem = NETDEV_RATE_AFTERGLOW - trigger_data->rate_ag; + rate = div_u64((u64)trigger_data->rate_peak * rem * rem, + NETDEV_RATE_AFTERGLOW * + NETDEV_RATE_AFTERGLOW); + } + + if (rate >= trigger_data->rate_threshold_kbps) { + if (rate >= 4096) + ms = 50; + else if (rate >= 1024) + ms = 60; + else if (rate >= 256) + ms = 80; + else if (rate >= 32) + ms = 100; + else + ms = 150; + } + + /* During the fade the step only ever gets longer: re-arming + * a faster blink mid-decay looks like stutter. + */ + if (ms && trigger_data->rate_ag && + ms < trigger_data->rate_last_ms) + ms = trigger_data->rate_last_ms; + } + + if (!ms) { + trigger_data->rate_peak = 0; + trigger_data->rate_ag = 0; + } + + /* Re-arm only when the blink period actually changes: every + * led_blink_set() restarts the software blink and truncates the + * phase in progress, which shows up as an irregular cadence. + */ + if (ms != trigger_data->rate_last_ms) { + led_stop_software_blink(trigger_data->led_cdev); + if (ms) { + unsigned long delay_on = ms, delay_off = ms; + + led_blink_set(trigger_data->led_cdev, &delay_on, + &delay_off); + } else { + /* Resting state == the baseline state: same mode bits, + * same brightness level (see netdev_set_resting()). + */ + netdev_set_resting(trigger_data); + } + trigger_data->rate_last_ms = ms; + } + + schedule_delayed_work(&trigger_data->work, HZ / 2); + return; + } + /* If we are not looking for RX/TX then return */ if (!test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) && !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) && -- 2.53.0