From patchwork Tue Sep 14 20:17:11 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ian Pilcher X-Patchwork-Id: 511279 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.7 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 38D4AC4332F for ; Tue, 14 Sep 2021 20:17:24 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1E2F160FDA for ; Tue, 14 Sep 2021 20:17:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233593AbhINUSl (ORCPT ); Tue, 14 Sep 2021 16:18:41 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36410 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233411AbhINUSk (ORCPT ); Tue, 14 Sep 2021 16:18:40 -0400 Received: from mail-ot1-x32a.google.com (mail-ot1-x32a.google.com [IPv6:2607:f8b0:4864:20::32a]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C4B80C061574; Tue, 14 Sep 2021 13:17:22 -0700 (PDT) Received: by mail-ot1-x32a.google.com with SMTP id c8-20020a9d6c88000000b00517cd06302dso229707otr.13; Tue, 14 Sep 2021 13:17:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=simrJYKVcEEEHVVwviqnsBOEbrvECEkhXevQ+NusywQ=; b=Q25OH5fpaD9IPaBCWYryWBbxRQPbbzHOgvAevHPxcoXdJky6rWhpLPZndajIJHcJGb LRm5C0Q3pdBL0nJ/t1vGaU0vRxmT9KMTH+O36zyoA9hL0MeJaYXHc+xAcwklP5thcREH PWkAoZnL8IY8ixtyi/2Fh9JlnttEn18QfnO1eOy7t8JumVLU5Kx4gkarSfDPH+PvSkHR HyDYM4txPIeZcYhBkwBMQzcrLrOKXe1GI2VQx0GyO383pL9MJ9un5DnRx5zIm9XN48Eh uglVa7IKSOi/ZpFD71ERwQI5nQTklQMPk1TEevmCpSVlGaMeM5rJKCGQv4E+g6prZIgo 1Q4A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=simrJYKVcEEEHVVwviqnsBOEbrvECEkhXevQ+NusywQ=; b=2fsWXcsxi1fmS3Qetv+xN/CkhPI+p+Lvez9QTVopiXpkMo6UxEfXiUO6/F7eZ1uXpB u8MF3uHGAZPXrkM6FevsFHmnAywfCKj1lYMoXzoC3G/VRnxeWfxN/1Qc0ruvNmhufPkT uiBMbvbkkNpOdxBk/c/NQd/toQ/hF0SsaSvRuwDYqnIT77azVDnWCx2v+Pp3b+xNjnNq uMtzwxaCosxEBflp+iWlz4YoXKMzeLnX0P+ndZQIZbPR/Aw1tq24G/P2ILzyJOZrhkyY hmUAgN6Nyk6mX0dp104zUxM5Ydq0fOr0TjFrSTZ6fKDqGdtEq0g0XqesrPF1WqlF2tFC ENUQ== X-Gm-Message-State: AOAM533g2ie+pHUdH+wCWwNpPWWaMakznYze/O5rxXdijcPnXtPUHHDU f01SnFWX4EjSzCc/aMt1ipw= X-Google-Smtp-Source: ABdhPJxwemDIrUy+6nryFvOY211hL7y/3tXzsiBdxh/LFkCh2TqggG1s3MO9h/nRYG+LMqH+QnIqvg== X-Received: by 2002:a9d:6215:: with SMTP id g21mr16951716otj.116.1631650642103; Tue, 14 Sep 2021 13:17:22 -0700 (PDT) Received: from ian.penurio.us ([47.184.51.90]) by smtp.gmail.com with ESMTPSA id p81sm2629455oia.48.2021.09.14.13.17.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Sep 2021 13:17:21 -0700 (PDT) From: Ian Pilcher To: axboe@kernel.dk, pavel@ucw.cz Cc: linux-leds@vger.kernel.org, linux-block@vger.kernel.org, linux-kernel@vger.kernel.org, gregkh@linuxfoundation.org, kabel@kernel.org, chaitanyak@nvidia.com Subject: [PATCH v3 1/3] docs: Add block device (blkdev) LED trigger documentation Date: Tue, 14 Sep 2021 15:17:11 -0500 Message-Id: <20210914201713.14922-2-arequipeno@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210914201713.14922-1-arequipeno@gmail.com> References: <20210914201713.14922-1-arequipeno@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org Add Documentation/ABI/testing/sysfs-class-led-trigger-blkdev to document: * /sys/class/leds//blink_time * /sys/class/leds//mode * /sys/class/leds//link_device * /sys/class/leds//unlink_device * /sys/class/leds//linked_devices * /sys/class/ledtrig_blkdev/interval Add /sys/block//linked_leds to Documentation/ABI/testing/sysfs-block. Add overview in Documentation/leds/ledtrig-blkdev.rst. Signed-off-by: Ian Pilcher --- Documentation/ABI/testing/sysfs-block | 9 ++ .../testing/sysfs-class-led-trigger-blkdev | 43 ++++++ Documentation/leds/index.rst | 1 + Documentation/leds/ledtrig-blkdev.rst | 144 ++++++++++++++++++ 4 files changed, 197 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-led-trigger-blkdev create mode 100644 Documentation/leds/ledtrig-blkdev.rst diff --git a/Documentation/ABI/testing/sysfs-block b/Documentation/ABI/testing/sysfs-block index a0ed87386639..80d4becc4e6d 100644 --- a/Documentation/ABI/testing/sysfs-block +++ b/Documentation/ABI/testing/sysfs-block @@ -328,3 +328,12 @@ Description: does not complete in this time then the block driver timeout handler is invoked. That timeout handler can decide to retry the request, to fail it or to start a device recovery strategy. + +What: /sys/block//linked_leds +Date: September 2021 +Contact: Ian Pilcher +Description: + Directory containing links to all LEDs that are associated + with this block device through the blkdev LED trigger. Only + present when at least one LED is associated. (See + Documentation/leds/ledtrig-blkdev.rst.) diff --git a/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev b/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev new file mode 100644 index 000000000000..2deee0e42212 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-led-trigger-blkdev @@ -0,0 +1,43 @@ +What: /sys/class/leds//blink_time +Date: September 2021 +Contact: Ian Pilcher +Description: + Time (in milliseconds) that the LED will be on during a single + "blink". + +What: /sys/class/leds//mode +Date: September 2021 +Contact: Ian Pilcher +Description: + Type of events for which LED will blink - read, write, + or rw (both). Note that any activity that changes the state of + the device's non-volatile storage (including discards and cache + flushes) is considered to be a write. + +What: /sys/class/leds//link_device +Date: September 2021 +Contact: Ian Pilcher +Description: + Associate a block device with this LED by writing its kernel + name (as shown in /sys/class/block) to this attribute. + +What: /sys/class/leds//unlink_device +Date: September 2021 +Contact: Ian Pilcher +Description: + Remove the association between this LED and a block device by + writing the device's kernel name to this attribute. + +What: /sys/class/leds//linked_devices +Date: September 2021 +Contact: Ian Pilcher +Description: + Directory containing links to all block devices that are + associated with this LED. + +What: /sys/class/ledtrig_blkdev/interval +Date: September 2021 +Contact: Ian Pilcher +Description: + Frequency (in milliseconds) with which block devices associated + with the blkdev LED trigger will be checked for activity. diff --git a/Documentation/leds/index.rst b/Documentation/leds/index.rst index e5d63b940045..e3c24e468cbc 100644 --- a/Documentation/leds/index.rst +++ b/Documentation/leds/index.rst @@ -10,6 +10,7 @@ LEDs leds-class leds-class-flash leds-class-multicolor + ledtrig-blkdev ledtrig-oneshot ledtrig-transient ledtrig-usbport diff --git a/Documentation/leds/ledtrig-blkdev.rst b/Documentation/leds/ledtrig-blkdev.rst new file mode 100644 index 000000000000..04bb47ec38b6 --- /dev/null +++ b/Documentation/leds/ledtrig-blkdev.rst @@ -0,0 +1,144 @@ +.. SPDX-License-Identifier: GPL-2.0 + +================================= +Block Device (blkdev) LED Trigger +================================= + +Available when ``CONFIG_LEDS_TRIGGER_BLKDEV=y`` or +``CONFIG_LEDS_TRIGGER_BLKDEV=m``. + +See also: + +* ``Documentation/ABI/testing/sysfs-class-led-trigger-blkdev`` +* ``Documentation/ABI/testing/sysfs-block`` (``/sys/block//linked_leds``) + +Overview +======== + +.. note:: + The examples below use ```` to refer to the name of a + system-specific LED. If no suitable LED is available on a test + system (in a virtual machine, for example), it is possible to + use a userspace LED. (See ``Documentation/leds/uleds.rst``.) + +Verify that the ``blkdev`` LED trigger is available:: + + # grep blkdev /sys/class/leds//trigger + ... rfkill-none blkdev + +(If the previous command produces no output, you may need to load the trigger +module - ``modprobe ledtrig_blkdev``. If the module is not available, check +the value of ``CONFIG_LEDS_TRIGGER_BLKDEV`` in your kernel configuration.) + +Associate the LED with the ``blkdev`` LED trigger:: + + # echo blkdev > /sys/class/leds//trigger + + # cat /sys/class/leds//trigger + ... rfkill-none [blkdev] + +Note that several new device attributes are available in the +``/sys/class/leds/`` directory. + +* ``link_device`` and ``unlink_device`` are used to manage the set of block + devices associated with this LED. The LED will blink in response to read or + write activity on its linked devices. + +* ``mode`` is used to control the type of device activity that will cause this + LED to blink - read activity, write activity, or both. (Note that any + activity that changes the state of a device's non-volatile storage is + considered to be a write. This includes discard and cache flush requests.) + +* ``blink_time`` is the duration (in milliseconds) of each blink of this LED. + (The minimum value is 10 milliseconds.) + +* The ``linked_devices`` directory will contain a symbolic link to every device + that is associated with this LED. + +Link a block device to the LED:: + + # echo sda > /sys/class/leds//link_device + + # ls /sys/class/leds//linked_devices + sda + +Read and write activity on the device should cause the LED to blink. The +duration of each blink (in milliseconds) can be adjusted by setting +``/sys/class/leds//blink_time``. (But see **interval and blink_time** +below.) + +Associate a second device with the LED:: + + # echo sdb > /sys/class/leds//link_device + + # ls /sys/class/leds//linked_devices + sda sdb + +When a block device is linked to one or more LEDs, the LEDs are linked from +the device's ``linked_leds`` directory:: + + # ls /sys/block/sd{a,b}/linked_leds + /sys/block/sda/linked_leds: + + + /sys/block/sdb/linked_leds: + + +(The ``linked_leds`` directory only exists when the block device is linked to +at least one LED.) + +``interval`` and ``blink_time`` +=============================== + +* By default, linked block devices are checked for activity every 100 + milliseconds. This frequency can be changed via the + ``/sys/class/ledtrig_blkdev/interval`` attribute. (The minimum value is 25 + milliseconds.) + +* All associated devices are checked for activity every ``interval`` + milliseconds, and a blink is triggered on appropriate LEDs. The duration + of an LED's blink is determined by its ``blink_time`` attribute. Thus + (assuming that activity of the relevant type has occurred on one of an LED's + linked devices), the LED will be on for ``blink_time`` milliseconds and off + for ``interval - blink_time`` milliseconds. + +* The LED subsystem ignores new blink requests for an LED that is already in + in the process of blinking, so setting a ``blink_time`` greater than or equal + to ``interval`` will cause some blinks to be missed. + +* Because of processing times, scheduling latencies, etc., avoiding missed + blinks actually requires a difference of at least a few milliseconds between + the ``blink_time`` and ``interval``. The required difference is likely to + vary from system to system. As a reference, a Thecus N5550 NAS requires a + difference of 7 milliseconds (``interval == 100``, ``blink_time == 93``). + +* The default values (``interval == 100``, ``blink_time == 75``) cause the LED + associated with a continuously active device to blink rapidly. For a more + "always on" effect, increase the ``blink_time`` (but not too much; see the + previous bullet). + +Other Notes +=========== + +* Many (possibly all) types of block devices work with this trigger, including: + + * SCSI (including SATA and USB) hard disk drives and SSDs + * SCSI (including SATA and USB) optical drives + * NVMe SSDs + * SD cards + * loopback block devices (``/dev/loop*``) + * device mapper devices, such as LVM logical volumes + * MD RAID devices + * zRAM compressed RAM-disks + * partitions on block devics that support them + +* The device names written to the ``link_device`` and ``unlink_device`` + attributes must be kernel names (the names shown in + ``/sys/class/block``). Symbolic link names, such as the names commonly + used to access logical volumes will not work. (Kernel names of logical + volumes and other device mapper devices can be seen as link targets in the + ``/dev/mapper`` directory.) + +* The ``blkdev`` LED trigger supports many-to-many device/LED associations. + A device can be associated with multiple LEDs, and an LED can be associated + with multiple devices. From patchwork Tue Sep 14 20:17:12 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ian Pilcher X-Patchwork-Id: 512575 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.7 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id C1D86C4332F for ; Tue, 14 Sep 2021 20:17:29 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 9FBDE6023F for ; Tue, 14 Sep 2021 20:17:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233680AbhINUSl (ORCPT ); Tue, 14 Sep 2021 16:18:41 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36418 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233572AbhINUSl (ORCPT ); Tue, 14 Sep 2021 16:18:41 -0400 Received: from mail-oo1-xc2d.google.com (mail-oo1-xc2d.google.com [IPv6:2607:f8b0:4864:20::c2d]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 3D11CC061762; Tue, 14 Sep 2021 13:17:23 -0700 (PDT) Received: by mail-oo1-xc2d.google.com with SMTP id y3-20020a4ab403000000b00290e2a52c71so73646oon.2; Tue, 14 Sep 2021 13:17:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=aWIrGWSAZnWLUYS/1IqkrKk3N3j/RhEty7rQ3CX36ok=; b=p8zdga1hqRq2hYPE4rHam3VkTD66a2GE9EUushxnlUigjSoOGtL7aX2oF5LSFwWaR3 PZviwaXUH55+Ydd0CNxKkFukD1TElHDyew8vY+x/hqHleVHub7metlhunclsk8J9TTlr UhD5CfoGT1gpvmD4WP0r2XNfp8gC3N6hpxw/xxDI1PN3AONjiIEjB6ablPCRxDA+GF9v NqsZKwaml3msSXSLKiXB8pdgdX3h1W6lTuiWOPn9fenP0+AvrqtN2PmzS8GhBwCEpkS9 N/r+r3VDH27Xd3G8B+MT5C7I9HQS/V3Gu6Exhrv6pwB5LTR7gRovpjevpkx+VIAOdM4w n1hQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=aWIrGWSAZnWLUYS/1IqkrKk3N3j/RhEty7rQ3CX36ok=; b=vtOCNCcfjucLg5onVG8fy1hVCB2gFrHyrunvsRQCUtck3l2X9iedxByUyY6XyFOp+t 7btlkQGPOx/RJIBs0frE4qP6tKTVqU5jv3boX6EZK27fnam6dX/Vcxk1ry0+1DvLUsdp hBosiJUvDey9VVWeGYcpxDBG9h4G+BMBLKl9sfvDR3BBpzmhMSnLafauBO5D0nsVqY0m xZyev0lbKIWoMobJQon93JzltRhiM2b4Lh5hfCe5LA0s31dGWvkVcwgqjnDf+0+8/w0Y o3539CccZy9YTXh6WJyrIDK6eVcTMYoOk0f92H1+OVrA+H6F+fFLiYIAAicI919Z7hCd zkNg== X-Gm-Message-State: AOAM533GXyKdu+fYtfT6F/obuMQTSjt4sgraNbhxxj5Dlu2lfbLOZXGA 7oHqWbhzYG2Gx1kAc+h7jOQ= X-Google-Smtp-Source: ABdhPJzfZQW/k2Uyfr16K89xBg+L16ARuFiCG+Z8QsLrRel9y3sjX4MtkNYikVF3JR4ZBnGB8F6BUA== X-Received: by 2002:a4a:de90:: with SMTP id v16mr15596744oou.42.1631650642573; Tue, 14 Sep 2021 13:17:22 -0700 (PDT) Received: from ian.penurio.us ([47.184.51.90]) by smtp.gmail.com with ESMTPSA id p81sm2629455oia.48.2021.09.14.13.17.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Sep 2021 13:17:22 -0700 (PDT) From: Ian Pilcher To: axboe@kernel.dk, pavel@ucw.cz Cc: linux-leds@vger.kernel.org, linux-block@vger.kernel.org, linux-kernel@vger.kernel.org, gregkh@linuxfoundation.org, kabel@kernel.org, chaitanyak@nvidia.com Subject: [PATCH v3 2/3] block: export block_class for use by the blkdev LED trigger Date: Tue, 14 Sep 2021 15:17:12 -0500 Message-Id: <20210914201713.14922-3-arequipeno@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210914201713.14922-1-arequipeno@gmail.com> References: <20210914201713.14922-1-arequipeno@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org Export block_class to the LEDTRIG_BLKDEV namespace. The block device (blkdev) LED trigger periodically checks the activity counters of block devices and blinks LEDs to reflect that activity. The specific block device(s) checked can be configured through a sysfs API. Once an LED has been associated with the blkdev trigger, the LED can be "linked" to a block device by writing that device's name to the LED's link_device attribute. For example: echo sda > /sys/class/leds//link_device When a block device name is written to the sysfs attribute, the trigger uses class_find_device_by_name() to resolve the actual block_device structure: dev = class_find_device_by_name(&block_class, name); Exporting the block_class symbol makes it possible to build the LED trigger as a module. Signed-off-by: Ian Pilcher --- block/genhd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/block/genhd.c b/block/genhd.c index 7b6e5e1cf956..ebea496bc283 100644 --- a/block/genhd.c +++ b/block/genhd.c @@ -1076,6 +1076,7 @@ struct class block_class = { .name = "block", .dev_uevent = block_uevent, }; +EXPORT_SYMBOL_NS_GPL(block_class, LEDTRIG_BLKDEV); static char *block_devnode(struct device *dev, umode_t *mode, kuid_t *uid, kgid_t *gid) From patchwork Tue Sep 14 20:17:13 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ian Pilcher X-Patchwork-Id: 511278 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-15.7 required=3.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 168C5C433F5 for ; Tue, 14 Sep 2021 20:17:30 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id EDF106112E for ; Tue, 14 Sep 2021 20:17:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S233748AbhINUSm (ORCPT ); Tue, 14 Sep 2021 16:18:42 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36420 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232545AbhINUSm (ORCPT ); Tue, 14 Sep 2021 16:18:42 -0400 Received: from mail-oo1-xc34.google.com (mail-oo1-xc34.google.com [IPv6:2607:f8b0:4864:20::c34]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 08C35C061574; Tue, 14 Sep 2021 13:17:24 -0700 (PDT) Received: by mail-oo1-xc34.google.com with SMTP id e206-20020a4a55d7000000b00291379cb2baso62434oob.9; Tue, 14 Sep 2021 13:17:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=BCiHJtxV9GZBJL85kS7L2wDkE/aMWwLW7EQ8KBu9X3U=; b=eVQBl8J8V2K9mSMPBx5wAk9H6RV1mXqKwr9980SnKGJ50cEXxRCP4iVYrd2mucx0wr AJ85gGCqvvJM7fEiMDvz2CNUgPXmMecEf2PH9rAb8dSxGWvzI37/lxdkSq0CI70IicGJ 7///1umvAH6CDQf0mkG+b36Rsa7moJQpj3nY78DwM5R9kALfvYngAkLNNM78uhJd1Nkl wELGpq86GOYKIxCxTAMLS5Vt7wEnhal8ESqzup2CA0l4dt6UbfWYDlK12mm4uOn0a+02 3kUHDOAwXCeoZtj23ZyhT6a563oYIH2p21DWkN2ewQAdn/Ew6mrfshfMjnmZguRLHlI4 /dlQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=BCiHJtxV9GZBJL85kS7L2wDkE/aMWwLW7EQ8KBu9X3U=; b=KafGnwTjCBPrVyL/cbPT0Pr/aOiI8SjUANj72P3uiq1p8sBOKGIaLgC3y7GE+UBeKd PtbIqUMr6d3+j17L/iBbYli5gECOxoE+IB/vRC4PM7voArWLgITLFpDFFjISkiO7NoZg uwDZupELrEQCSZbpwnmdeHuMlaxWObGnbqnpbTksyVO282AmYyygQDptmk/q4srDgb2x Fnxa7+rJ4828qc2ha/YVCl6sflaw7YHK3nHkFjw6c5Sy5kWXOiM/CFcxa/kkgJwdbQ93 3X4XO3SogEnAL/2QhzZHWbilwcey9jW/Keu4C/wYcPLJBsfsilGk6c2sCkcOPHOuip8n Tfyw== X-Gm-Message-State: AOAM533qXRADgdubNoMORuVOD1BO1hC3p043/cLuSkp68dlS7KjYkfad Aadn89+Vg4M8RmolrYiP6WQ= X-Google-Smtp-Source: ABdhPJxBwUv2AMH6stezsUOsosWFAgxNIbD0jd8rWT6Mr+VWQi/ramWywbZRD9cU2vOib1TSqo85sA== X-Received: by 2002:a4a:b502:: with SMTP id r2mr15710974ooo.70.1631650643092; Tue, 14 Sep 2021 13:17:23 -0700 (PDT) Received: from ian.penurio.us ([47.184.51.90]) by smtp.gmail.com with ESMTPSA id p81sm2629455oia.48.2021.09.14.13.17.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Sep 2021 13:17:22 -0700 (PDT) From: Ian Pilcher To: axboe@kernel.dk, pavel@ucw.cz Cc: linux-leds@vger.kernel.org, linux-block@vger.kernel.org, linux-kernel@vger.kernel.org, gregkh@linuxfoundation.org, kabel@kernel.org, chaitanyak@nvidia.com Subject: [PATCH v3 3/3] leds: trigger: Add block device LED trigger Date: Tue, 14 Sep 2021 15:17:13 -0500 Message-Id: <20210914201713.14922-4-arequipeno@gmail.com> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210914201713.14922-1-arequipeno@gmail.com> References: <20210914201713.14922-1-arequipeno@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org Add LEDS_TRIGGER_BLKDEV (tristate) config option to control building of the trigger. Signed-off-by: Ian Pilcher --- drivers/leds/trigger/Kconfig | 9 + drivers/leds/trigger/Makefile | 1 + drivers/leds/trigger/ledtrig-blkdev.c | 889 ++++++++++++++++++++++++++ 3 files changed, 899 insertions(+) create mode 100644 drivers/leds/trigger/ledtrig-blkdev.c diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index 1f1d57288085..219d15c046d7 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -153,4 +153,13 @@ config LEDS_TRIGGER_TTY When build as a module this driver will be called ledtrig-tty. +config LEDS_TRIGGER_BLKDEV + tristate "LED Trigger for block devices" + depends on BLOCK + help + The blkdev LED trigger allows LEDs to be controlled by block device + activity (reads and writes). + + See Documentation/leds/ledtrig-blkdev.rst. + endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile index 25c4db97cdd4..d53bab5d93f1 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o obj-$(CONFIG_LEDS_TRIGGER_AUDIO) += ledtrig-audio.o obj-$(CONFIG_LEDS_TRIGGER_TTY) += ledtrig-tty.o +obj-$(CONFIG_LEDS_TRIGGER_BLKDEV) += ledtrig-blkdev.o diff --git a/drivers/leds/trigger/ledtrig-blkdev.c b/drivers/leds/trigger/ledtrig-blkdev.c new file mode 100644 index 000000000000..4b7b17a91949 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-blkdev.c @@ -0,0 +1,889 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Block device LED trigger + * + * Copyright 2021 Ian Pilcher + */ + +#include +#include +#include +#include + +/* Default blink time & check interval (milliseconds) */ +#define LED_BDEV_BLINK_MSEC 75 +#define LED_BDEV_INTERVAL 100 + +/* Minimum blink time & check interval (milliseconds) */ +#define LED_BDEV_MIN_BLINK 10 +#define LED_BDEV_MIN_INT 25 + +/* Device activity type that will make LED blink */ +enum led_bdev_mode { + LED_BDEV_MODE_RO = 0, + LED_BDEV_MODE_WO = 1, + LED_BDEV_MODE_RW = 2 +}; + +/* Block device information (BDI) - 1 per blkdev linked to at least 1 LED */ +struct led_bdev_bdi { + struct block_device *bdev; + struct hlist_head leds; + unsigned long read_ios; + unsigned long write_ios; + unsigned int generation; + bool read_act; + bool write_act; +}; + +/* For many-to-many relationships between block devices and LEDs */ +struct led_bdev_link { + struct hlist_node bdi_leds_node; + struct hlist_node led_bdis_node; + struct led_bdev_bdi *bdi; + struct led_bdev_led *led; +}; + +/* Every LED associated with the led_bdev trigger gets one of these */ +struct led_bdev_led { + struct led_classdev *led_cdev; + unsigned int blink_msec; + struct hlist_head bdis; + struct hlist_node leds_node; + enum led_bdev_mode mode; +}; + +/* Forward declarations to make this file compile in a reasonable order */ +static void led_bdev_process(struct work_struct *work); +static struct led_bdev_bdi *led_bdev_get_bdi(const char *name); +static int led_bdev_link(struct led_bdev_led *led, struct led_bdev_bdi *bdi); +static void led_bdev_put_bdi(struct led_bdev_bdi *bdi); +static void led_bdev_bdi_release(struct device *dev, void *res); +static void led_bdev_unlink(struct led_bdev_led *led, + struct led_bdev_link *link, + struct led_bdev_bdi *bdi, bool releasing); +static bool led_bdev_process_link(const struct led_bdev_led *led, + const struct led_bdev_link *link, + unsigned int generation); + +/* Protects everything except atomic sysfs attributes */ +static DEFINE_MUTEX(led_bdev_mutex); + +/* All LEDs associated with the trigger */ +static HLIST_HEAD(led_bdev_leds); + +/* Class for "global" trigger attributes (interval) */ +static struct class *led_bdev_class; + +/* How often to check for drive activity - in jiffies */ +static unsigned int led_bdev_interval; + +/* Delayed work to periodically check for activity & blink LEDs */ +static DECLARE_DELAYED_WORK(led_bdev_work, led_bdev_process); + +/* Total number of device-to-LED associations (links) */ +static unsigned int led_bdev_link_count; + +/* Empty attribute list for the linked_leds & linked_devices "groups" */ +static struct attribute *led_bdev_attrs_empty[] = { NULL }; + +/* linked_leds sysfs directory for block devs linked to 1 or more LEDs */ +static const struct attribute_group led_bdev_linked_leds = { + .name = "linked_leds", + .attrs = led_bdev_attrs_empty, +}; + +/* linked_devices sysfs directory for each LED associated with the trigger */ +static const struct attribute_group led_bdev_linked_devs = { + .name = "linked_devices", + .attrs = led_bdev_attrs_empty, +}; + +/** + * led_bdev_activate() - Called when an LED is associated with the trigger. + * @led_cdev: The LED + * + * Allocates & initializes the @led_bdev_led structure and adds it to the + * @led_bdev_leds list. + * + * Context: Process context. Takes and releases @led_bdev_mutex. + * Return: ``0`` on success, ``-errno`` on error. + */ +static int led_bdev_activate(struct led_classdev *const led_cdev) +{ + struct led_bdev_led *led; + int ret; + + led = kmalloc(sizeof(*led), GFP_KERNEL); + if (led == NULL) { + ret = -ENOMEM; + goto exit_return; + } + + led->led_cdev = led_cdev; + led->blink_msec = LED_BDEV_BLINK_MSEC; + led->mode = LED_BDEV_MODE_RW; + INIT_HLIST_HEAD(&led->bdis); + + ret = mutex_lock_interruptible(&led_bdev_mutex); + if (ret != 0) + goto exit_free; + + hlist_add_head(&led->leds_node, &led_bdev_leds); + led_set_trigger_data(led_cdev, led); + + mutex_unlock(&led_bdev_mutex); + +exit_free: + if (ret != 0) + kfree(led); +exit_return: + return ret; +} + +/** + * link_device_store() - ``link_device`` device attribute store function. + * @dev: The LED device + * @attr: The ``link_device`` attribute (@dev_attr_link_device) + * @buf: The name of the block device (as written to sysfs attribute) + * @count: The number of bytes written to the attribute + * + * Block device name is written to the attribute to "link" the block device + * to the LED. LED will blink to show activity on any of its linked blocked + * devices. + * + * * Calls led_bdev_get_bdi() to find or create the BDI for the device. + * * Checks that device isn't already linked to this LED. + * * Calls led_bdev_link() to create the link. + * + * Context: Process context. Takes and releases @led_bdev_mutex. + * Return: @count on success, ``-errno`` on error. + */ +static ssize_t link_device_store(struct device *const dev, + struct device_attribute *const attr, + const char *const buf, const size_t count) +{ + struct led_bdev_led *const led = led_trigger_get_drvdata(dev); + const struct led_bdev_link *link; + struct led_bdev_bdi *bdi; + int ret; + + ret = mutex_lock_interruptible(&led_bdev_mutex); + if (ret != 0) + goto exit_return; + + bdi = led_bdev_get_bdi(buf); + if (IS_ERR(bdi)) { + ret = PTR_ERR(bdi); + goto exit_unlock; + } + + hlist_for_each_entry(link, &bdi->leds, bdi_leds_node) { + if (link->led == led) { + ret = -EEXIST; + goto exit_put_bdi; + } + } + + ret = led_bdev_link(led, bdi); + +exit_put_bdi: + if (ret != 0) + led_bdev_put_bdi(bdi); +exit_unlock: + mutex_unlock(&led_bdev_mutex); +exit_return: + return (ret == 0) ? count : ret; +} + +/** + * led_bdev_get_bdi() - Find or create the BDI for a block device. + * @name: The name of the block device + * + * If the device already has a BDI (because it is already linked to an LED), + * simply return the existing BDI. + * + * Otherwise, allocate a new BDI (as a device resource), create the block + * device's ``linked_leds`` directory (attribute group), and add the BDI + * resource to the block device. + * + * Context: Process context. Caller must hold @led_bdev_mutex. + * Return: Pointer to the BDI, error pointer on error. + */ +static struct led_bdev_bdi *led_bdev_get_bdi(const char *const name) +{ + struct led_bdev_bdi *bdi; + struct device *dev; + int ret; + + dev = class_find_device_by_name(&block_class, name); + if (dev == NULL) { + ret = -ENODEV; + goto exit_return; + } + + bdi = devres_find(dev, led_bdev_bdi_release, NULL, NULL); + if (bdi != NULL) { + ret = 0; + goto exit_put_dev; + } + + bdi = devres_alloc(led_bdev_bdi_release, sizeof(*bdi), GFP_KERNEL); + if (bdi == NULL) { + ret = -ENOMEM; + goto exit_put_dev; + } + + ret = sysfs_create_group(&dev->kobj, &led_bdev_linked_leds); + if (ret != 0) + goto exit_free_bdi; + + INIT_HLIST_HEAD(&bdi->leds); + bdi->bdev = dev_to_bdev(dev); + bdi->read_ios = 0; + bdi->write_ios = 0; + + devres_add(dev, bdi); + +exit_free_bdi: + if (ret != 0) + devres_free(bdi); +exit_put_dev: + put_device(dev); /* Allow the device to be released */ +exit_return: + return (ret == 0) ? bdi : ERR_PTR(ret); +} + +/** + * led_bdev_link() - "Link" a block device to an LED. + * @led: The LED + * @bdi: The block device + * + * Called from link_device_store() to create the link between an LED and a + * block device. + * + * * Allocates & initializes link structure. + * * Adds block device symlink to LED's ``linked_devices`` directory. + * * Adds LED symlink to block devices's ``linked_leds`` directory. + * * Adds link to LED's list of block devices and device's list of LEDs. + * + * If the new link is the only one (i.e. no other block device/LED links + * already exist), schedule delayed work to periodically check for block + * device activity and blink LEDs. + * + * Context: Process context. Caller must hold @led_bdev_mutex. + * Return: 0 on success, ``-errno`` on error. + */ +static int led_bdev_link(struct led_bdev_led *const led, + struct led_bdev_bdi *const bdi) +{ + struct led_bdev_link *link; + unsigned long delay; + int ret; + + link = kmalloc(sizeof(*link), GFP_KERNEL); + if (link == NULL) { + ret = -ENOMEM; + goto error_return; + } + + ret = sysfs_add_link_to_group(bdev_kobj(bdi->bdev), + led_bdev_linked_leds.name, + &led->led_cdev->dev->kobj, + led->led_cdev->name); + if (ret != 0) + goto error_free_link; + + ret = sysfs_add_link_to_group(&led->led_cdev->dev->kobj, + led_bdev_linked_devs.name, + bdev_kobj(bdi->bdev), + dev_name(&bdi->bdev->bd_device)); + if (ret != 0) + goto error_remove_symlink; + + link->bdi = bdi; + link->led = led; + hlist_add_head(&link->led_bdis_node, &led->bdis); + hlist_add_head(&link->bdi_leds_node, &bdi->leds); + + if (led_bdev_link_count == 0) { + delay = READ_ONCE(led_bdev_interval); + WARN_ON(!schedule_delayed_work(&led_bdev_work, delay)); + } + + ++led_bdev_link_count; + + return 0; + +error_remove_symlink: + sysfs_remove_link_from_group(bdev_kobj(bdi->bdev), + led_bdev_linked_leds.name, + led->led_cdev->name); +error_free_link: + kfree(link); +error_return: + return ret; +} + +/** + * unlink_device_store() - ``unlink_device`` device attribute store function. + * @dev: The LED device + * @attr: The ``unlink_device`` attribute (@dev_attr_unlink_device) + * @buf: The name of the block device (as written to the sysfs attribute) + * @count: The number of bytes written to the attribute + * + * Block device name is written to the attribute to "unlink" the block device + * from the LED. I.e. the LED will no longer blink to show activity on that + * block device. + * + * * Finds block device (BDI) in LED's list of linked devices. + * * Calls led_bdev_unlink() to destroy the link. + * + * Context: Process context. Takes and releases @led_bdev_mutex. + * Return: @count on success, ``-errno`` on error. + */ +static ssize_t unlink_device_store(struct device *const dev, + struct device_attribute *const attr, + const char *const buf, const size_t count) +{ + struct led_bdev_led *const led = led_trigger_get_drvdata(dev); + struct led_bdev_link *link; + int ret; + + ret = mutex_lock_interruptible(&led_bdev_mutex); + if (ret != 0) + return ret; + + hlist_for_each_entry(link, &led->bdis, led_bdis_node) { + + if (sysfs_streq(dev_name(&link->bdi->bdev->bd_device), buf)) { + led_bdev_unlink(led, link, link->bdi, false); + break; + } + } + + mutex_unlock(&led_bdev_mutex); + + return count; +} + +/** + * led_bdev_unlink() - "Unlink" a block device from an LED. + * @led: The LED + * @link: The existing link between the LED and the block device + * @bdi: The block device + * @releasing: Indicates whether the BDI is being released (because the + * block device has been removed) + * + * Removes and frees the link between an LED and a block device. + * + * * Removes the link from the LED's list of block devices and the + * device's list of LEDs. + * * Frees the link structure. + * * Removes the block device symlink from the LED's ``linked_devices`` + * directory. + * + * If the block device is **not** being released: + * + * * Removes the LED symlink from the block device's ``linked_leds`` + * directory. + * * Calls led_bdev_put_bdi() to clean up the BDI, if required. + * + * If the removed link was the only one (i.e. there are no existing block + * device/LED links after its removal), cancel the periodic delayed work + * which checks for device activity. + * + * Called from multiple locations: + * + * * unlink_device_store() - requested via ``unlink_device`` sysfs attribute + * (@releasing == ``false``) + * * led_bdev_deactivate() - unlink a block device from an LED that is no + * longer associated with the trigger (@releasing == ``false``) + * * led_bdev_bdi_release() - unlink an LED from a block device that has + * been removed (@releasing == ``true``) + * + * Context: Process context. Caller must hold @led_bdev_mutex. + */ +static void led_bdev_unlink(struct led_bdev_led *const led, + struct led_bdev_link *const link, + struct led_bdev_bdi *const bdi, + const bool releasing) +{ + --led_bdev_link_count; + + if (led_bdev_link_count == 0) + WARN_ON(!cancel_delayed_work_sync(&led_bdev_work)); + + hlist_del(&link->led_bdis_node); + hlist_del(&link->bdi_leds_node); + kfree(link); + + sysfs_remove_link_from_group(&led->led_cdev->dev->kobj, + led_bdev_linked_devs.name, + dev_name(&bdi->bdev->bd_device)); + + /* + * If the BDI is being released, the device's attribute groups have + * already been removed, and the BDI will be freed automatically. + */ + if (!releasing) { + + sysfs_remove_link_from_group(bdev_kobj(bdi->bdev), + led_bdev_linked_leds.name, + led->led_cdev->name); + led_bdev_put_bdi(bdi); + } +} + +/** + * led_bdev_put_bdi() - Remove and free a BDI, if it is no longer needed. + * @bdi: The BDI + * + * Does nothing if the BDI (block device) is still linked to at least one LED. + * + * If the BDI is no longer linked to any LEDs: + * + * * Removes the block device's ``linked_leds`` directory (attribute group). + * * Removes the BDI from the block device and frees it. + * + * Called from led_bdev_unlink() (and in link_device_store() error path). + * + * Context: Process context. Caller must hold @led_bdev_mutex. + */ +static void led_bdev_put_bdi(struct led_bdev_bdi *const bdi) +{ + struct block_device *const bdev = bdi->bdev; + int ret; + + if (hlist_empty(&bdi->leds)) { + + sysfs_remove_group(bdev_kobj(bdev), &led_bdev_linked_leds); + ret = devres_destroy(&bdev->bd_device, led_bdev_bdi_release, + NULL, NULL); + WARN_ON(ret != 0); + } +} + +/** + * led_bdev_deactivate() - Called when an LED is disassociated from the + * trigger. + * @led_cdev: The LED + * + * Calls led_bdev_unlink() for each block device linked to the LED, removes + * the LED from the @led_bdev_leds list, and frees the @led_bdev_led structure. + * + * Context: Process context. Takes and releases @led_bdev_mutex. + */ +static void led_bdev_deactivate(struct led_classdev *const led_cdev) +{ + struct led_bdev_led *const led = led_get_trigger_data(led_cdev); + struct led_bdev_link *link; + struct hlist_node *next; + + mutex_lock(&led_bdev_mutex); + + hlist_for_each_entry_safe(link, next, &led->bdis, led_bdis_node) + led_bdev_unlink(led, link, link->bdi, false); + + hlist_del(&led->leds_node); + kfree(led); + + mutex_unlock(&led_bdev_mutex); +} + +/** + * led_bdev_bdi_release() - BDI device resource release function. + * @dev: The block device + * @res: The BDI + * + * Called when the block device with which a BDI is associated has been + * removed. Calls led_bdev_unlink() for each LED linked to the block device. + * + * Context: Process context. Takes and releases @led_bdev_mutex. + */ +static void led_bdev_bdi_release(struct device *const dev, void *const res) +{ + struct led_bdev_bdi *const bdi = res; + struct led_bdev_link *link; + struct hlist_node *next; + + mutex_lock(&led_bdev_mutex); + + hlist_for_each_entry_safe(link, next, &bdi->leds, bdi_leds_node) + led_bdev_unlink(link->led, link, bdi, true); + + mutex_unlock(&led_bdev_mutex); +} + +/** + * led_bdev_process() - Check linked devices for activity and blink LEDs. + * @work: Delayed work (@led_bdev_work) + * + * Iterates through block devices linked to LEDs and calls + * led_bdev_process_link() to check the device and (if appropriate) blink + * the LED. + * + * * If led_bdev_process_link() blinks an LED (returns ``true``), there is + * no need to check more devices associated with that LED. + * * A block device can be linked to multiple LEDs, so we use a "generation" + * that is incremented each time this function is called to ensure that + * we only check a block device once per call to this function. + * + * When finished, schedules itself to run again after @led_bdev_interval + * jiffies. + * + * Context: Process context. Takes and releases @led_bdev_mutex. + */ +static void led_bdev_process(struct work_struct *const work) +{ + static unsigned int generation; + + struct led_bdev_led *led; + struct led_bdev_link *link; + unsigned long delay; + + if (!mutex_trylock(&led_bdev_mutex)) + goto exit_reschedule; + + hlist_for_each_entry(led, &led_bdev_leds, leds_node) { + + hlist_for_each_entry(link, &led->bdis, led_bdis_node) { + + if (led_bdev_process_link(led, link, generation)) + break; + } + } + + ++generation; + + mutex_unlock(&led_bdev_mutex); + +exit_reschedule: + delay = READ_ONCE(led_bdev_interval); + WARN_ON_ONCE(!schedule_delayed_work(&led_bdev_work, delay)); +} + +/** + * led_bdev_process_link() - Check a linked block device for activity and blink + * the linked LED if appropriate. + * @led: The LED + * @link: The link + * @generation: Identifies when the linked BDI was last updated + * + * Updates BDI's activity counters if BDI's generation doesn't match the + * current value. Blinks LED if the correct type of activity (based on + * the LED's @mode) has occurred since the last check. + * + * Context: Process context. Caller must hold @led_bdev_mutex. + * Return: ``true`` if the LED was blinked, ``false`` if not. + */ +static bool led_bdev_process_link(const struct led_bdev_led *const led, + const struct led_bdev_link *const link, + const unsigned int generation) +{ + struct led_bdev_bdi *const bdi = link->bdi; + + /* + * If this block device is linked to multiple LEDs, it may have already + * been processed in this "generation". No need to update its activity + * counters in that case. + */ + if (bdi->generation != generation) { + + unsigned long read_ios, write_ios; + + read_ios = part_stat_read(bdi->bdev, ios[STAT_READ]); + + write_ios = part_stat_read(bdi->bdev, ios[STAT_WRITE]) + + part_stat_read(bdi->bdev, ios[STAT_DISCARD]) + + part_stat_read(bdi->bdev, ios[STAT_FLUSH]); + + if (bdi->read_ios != read_ios) { + bdi->read_act = true; + bdi->read_ios = read_ios; + } else { + bdi->read_act = false; + } + + if (bdi->write_ios != write_ios) { + bdi->write_act = true; + bdi->write_ios = write_ios; + } else { + bdi->write_act = false; + } + + bdi->generation = generation; + } + + if ((bdi->read_act && led->mode != LED_BDEV_MODE_WO) + || (bdi->write_act && led->mode != LED_BDEV_MODE_RO)) { + + unsigned long delay_on = READ_ONCE(led->blink_msec); + unsigned long delay_off = 1; /* 0 leaves LED turned on */ + + led_blink_set_oneshot(led->led_cdev, &delay_on, &delay_off, 0); + return true; + } + + return false; +} + +/** + * blink_time_show() - ``blink_time`` device attribute show function. + * @dev: The LED device + * @attr: The ``blink_time`` attribute (@dev_attr_blink_time) + * @buf: Output buffer + * + * Writes the current value of the LED's @blink_msec to @buf. + * + * Context: Process context. + * Return: The number of characters written to @buf. + */ +static ssize_t blink_time_show(struct device *const dev, + struct device_attribute *const attr, + char *const buf) +{ + const struct led_bdev_led *const led = led_trigger_get_drvdata(dev); + + return sprintf(buf, "%u\n", READ_ONCE(led->blink_msec)); +} + +/** + * blink_time_store() - ``blink_time`` device attribute store function. + * @dev: The LED device + * @attr: The ``blink_time`` attribute (@dev_attr_blink_time) + * @buf: The new value (as written to the sysfs attribute) + * @count: The number of bytes written to the attribute + * + * Sets the LED's @blink_msec (the duration in milliseconds of one blink). + * + * Context: Process context. + * Return: @count on success, ``-errno`` on error. + */ +static ssize_t blink_time_store(struct device *const dev, + struct device_attribute *const attr, + const char *const buf, const size_t count) +{ + struct led_bdev_led *const led = led_trigger_get_drvdata(dev); + unsigned int value; + int ret; + + ret = kstrtouint(buf, 0, &value); + if (ret != 0) + return ret; + + if (value < LED_BDEV_MIN_BLINK) + return -ERANGE; + + WRITE_ONCE(led->blink_msec, value); + return count; +} + +/* Data for mode_show() & mode_store() */ +static const struct { + char name[sizeof("write")]; /* longest name */ + char show[sizeof("[read] write rw\n")]; /* all the same size */ +} led_bdev_modes[] = { + [LED_BDEV_MODE_RO] = { + .name = "read", + .show = "[read] write rw\n", + }, + [LED_BDEV_MODE_WO] = { + .name = "write", + .show = "read [write] rw\n", + }, + [LED_BDEV_MODE_RW] = { + .name = "rw", + .show = "read write [rw]\n", + }, +}; + +/** + * mode_show() - ``mode`` device attribute show function. + * @dev: The LED device + * @attr: The ``mode`` attribute (@dev_attr_mode) + * @buf: Output buffer + * + * Writes the current value of the LED's @mode to @buf. + * + * Context: Process context. + * Return: The number of characters written to @buf. + */ +static ssize_t mode_show(struct device *const dev, + struct device_attribute *const attr, char *const buf) +{ + const struct led_bdev_led *const led = led_trigger_get_drvdata(dev); + + return sprintf(buf, led_bdev_modes[READ_ONCE(led->mode)].show); +} + +/** + * mode_store() - ``mode`` device attribute store function. + * @dev: The LED device + * @attr: The ``mode`` attribute (@dev_attr_mode) + * @buf: The new value (as written to the sysfs attribute) + * @count: The number of bytes written to the attribute + * + * Sets the LED's @mode (``read``, ``write``, or ``rw``). + * + * Context: Process context. + * Return: @count on success, ``-errno`` on error. + */ +static ssize_t mode_store(struct device *const dev, + struct device_attribute *const attr, + const char *const buf, const size_t count) +{ + struct led_bdev_led *const led = led_trigger_get_drvdata(dev); + enum led_bdev_mode mode; + + for (mode = LED_BDEV_MODE_RO; mode <= LED_BDEV_MODE_RW; ++mode) { + + if (sysfs_streq(led_bdev_modes[mode].name, buf)) { + WRITE_ONCE(led->mode, mode); + return count; + } + } + + return -EINVAL; +} + +/** + * interval_show() - ``interval`` class attribute show function. + * @class: The ``ledtrig_blkdev`` class (@led_bdev_class) + * @attr: The ``interval`` attribute (@class_attr_interval) + * @buf: Output buffer + * + * Writes the current value of @led_bdev_interval to @buf. + * + * Context: Process context. + * Return: The number of characters written to @buf. + */ +static ssize_t interval_show(struct class *const class, + struct class_attribute *const attr, + char *const buf) +{ + return sprintf(buf, "%u\n", + jiffies_to_msecs(READ_ONCE(led_bdev_interval))); +} + +/** + * interval_store() - ``interval`` class attribute store function + * @class: The ``ledtrig_blkdev`` class (@led_bdev_class) + * @attr: The ``interval`` attribute (@class_attr_interval) + * @buf: The new value (as written to the sysfs attribute) + * @count: The number of bytes written to the attribute + * + * Sets @led_bdev_interval (after converting to jiffies). + * + * Context: Process context. + * Return: @count on success, ``-errno`` on error. + */ +static ssize_t interval_store(struct class *const class, + struct class_attribute *const attr, + const char *const buf, const size_t count) +{ + unsigned int value; + int ret; + + ret = kstrtouint(buf, 0, &value); + if (ret != 0) + return ret; + + if (value < LED_BDEV_MIN_INT) + return -ERANGE; + + WRITE_ONCE(led_bdev_interval, msecs_to_jiffies(value)); + + return count; +} + +/* Device and class attributes */ +static DEVICE_ATTR_WO(link_device); +static DEVICE_ATTR_WO(unlink_device); +static DEVICE_ATTR_RW(blink_time); +static DEVICE_ATTR_RW(mode); +static CLASS_ATTR_RW(interval); + +/* Device attributes in LED directory (/sys/class/leds//...) */ +static struct attribute *led_bdev_attrs[] = { + &dev_attr_link_device.attr, + &dev_attr_unlink_device.attr, + &dev_attr_blink_time.attr, + &dev_attr_mode.attr, + NULL +}; + +/* Unnamed attribute group == no subdirectory */ +static const struct attribute_group led_bdev_attr_group = { + .attrs = led_bdev_attrs, +}; + +/* Attribute groups for the trigger */ +static const struct attribute_group *led_bdev_attr_groups[] = { + &led_bdev_attr_group, /* /sys/class/leds//... */ + &led_bdev_linked_devs, /* /sys/class/leds//linked_devices/ */ + NULL +}; + +/* Trigger registration data */ +static struct led_trigger led_bdev_trigger = { + .name = "blkdev", + .activate = led_bdev_activate, + .deactivate = led_bdev_deactivate, + .groups = led_bdev_attr_groups, +}; + +/** + * led_bdev_init() - Block device LED trigger initialization. + * + * Converts default @led_bdev_interval from milliseconds to jiffies, creates + * the ``/sys/class/ledtrig_blkdev/interval`` attribute, and registers the LED + * trigger. + * + * Return: 0 on success, ``-errno`` on failure. + */ +static int __init led_bdev_init(void) +{ + int ret; + + WRITE_ONCE(led_bdev_interval, + msecs_to_jiffies(LED_BDEV_INTERVAL)); + + led_bdev_class = class_create(THIS_MODULE, "ledtrig_blkdev"); + if (IS_ERR(led_bdev_class)) { + ret = PTR_ERR(led_bdev_class); + goto exit_return; + } + + ret = class_create_file(led_bdev_class, &class_attr_interval); + if (ret != 0) + goto exit_destroy_class; + + ret = led_trigger_register(&led_bdev_trigger); + +exit_destroy_class: + if (ret != 0) + class_destroy(led_bdev_class); /* removes the class attr */ +exit_return: + return ret; +} +module_init(led_bdev_init); + +/** + * led_bdev_exit() - Block device LED trigger module exit. + * + * Unregisters the LED trigger and removes the + * ``/sys/class/ledtrig_blkdev/interval`` attribute. + */ +static void __exit led_bdev_exit(void) +{ + led_trigger_unregister(&led_bdev_trigger); + class_destroy(led_bdev_class); +} +module_exit(led_bdev_exit); + +MODULE_DESCRIPTION("Block device LED trigger"); +MODULE_AUTHOR("Ian Pilcher "); +MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS(LEDTRIG_BLKDEV);