All of lore.kernel.org
 help / color / mirror / Atom feed
From: Stuart Hayes <stuart.w.hayes@gmail.com>
To: Bjorn Helgaas <bhelgaas@google.com>, linux-pci@vger.kernel.org
Cc: Stuart Hayes <stuart.w.hayes@gmail.com>
Subject: [PATCH] Add support for PCIe SSD status LED management
Date: Fri, 16 Apr 2021 15:20:10 -0400	[thread overview]
Message-ID: <20210416192010.3197-1-stuart.w.hayes@gmail.com> (raw)

This patch adds support for the PCIe SSD Status LED Management
interface, as described in the "_DSM Additions for PCIe SSD Status LED
Management" ECN to the PCI Firmware Specification revision 3.2.

It will add a single (led_classdev) LED for any PCIe device that has the
relevant _DSM. The ten possible status states are exposed using
attributes current_states and supported_states. Reading current_states
(and supported_states) will show the definition and value of each bit:

>cat /sys/class/leds/0000:88:00.0::pcie_ssd_status/supported_states
ok                              0x0004 [ ]
locate                          0x0008 [*]
fail                            0x0010 [ ]
rebuild                         0x0020 [ ]
pfa                             0x0040 [ ]
hotspare                        0x0080 [ ]
criticalarray                   0x0100 [ ]
failedarray                     0x0200 [ ]
invaliddevice                   0x0400 [ ]
disabled                        0x0800 [ ]
--
supported_states = 0x0008

>cat /sys/class/leds/0000:88:00.0::pcie_ssd_status/current_states
locate                          0x0008 [ ]
--
current_states = 0x0000

Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
---
 .../sysfs-class-led-driver-pcie-ssd-leds      |  30 ++
 drivers/pci/Kconfig                           |  12 +
 drivers/pci/Makefile                          |   1 +
 drivers/pci/pcie-ssd-leds.c                   | 421 ++++++++++++++++++
 4 files changed, 464 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-led-driver-pcie-ssd-leds
 create mode 100644 drivers/pci/pcie-ssd-leds.c

diff --git a/Documentation/ABI/testing/sysfs-class-led-driver-pcie-ssd-leds b/Documentation/ABI/testing/sysfs-class-led-driver-pcie-ssd-leds
new file mode 100644
index 000000000000..856f5a078568
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-led-driver-pcie-ssd-leds
@@ -0,0 +1,30 @@
+What:		/sys/class/leds/<led>/supported_states
+Date:		April 2021
+Contact:	linux-pci@vger.kernel.org
+Description:
+		This attribute indicates the status states supported by this
+		display.  Reading will show a verbose output with the value on
+		the final line (example below), writing only takes a single
+		numerical value.
+
+		>cat /sys/class/leds/0000:88:00.0::pcie_ssd_status/supported_states
+		ok                              0x0004 [ ]
+		locate                          0x0008 [*]
+		fail                            0x0010 [ ]
+		rebuild                         0x0020 [ ]
+		pfa                             0x0040 [ ]
+		hotspare                        0x0080 [ ]
+		criticalarray                   0x0100 [ ]
+		failedarray                     0x0200 [ ]
+		invaliddevice                   0x0400 [ ]
+		disabled                        0x0800 [ ]
+		--
+		supported_states = 0x0008
+
+What:		/sys/class/leds/<led>/current_states
+Date:		April 2021
+Contact:	linux-pci@vger.kernel.org
+Description:
+		This attribute indicates the currently active status states on
+		this display.  Reading will show a verbose output with the value
+		the final line, writing only takes a single numerical value.
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 0c473d75e625..f4acf1ad0fb5 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -190,6 +190,18 @@ config PCI_HYPERV
 	  The PCI device frontend driver allows the kernel to import arbitrary
 	  PCI devices from a PCI backend to support PCI driver domains.
 
+config PCIE_SSD_LEDS
+	tristate "PCIe SSD status LED support"
+	depends on ACPI && NEW_LEDS
+	help
+	  Driver for PCIe SSD status LED management as described in a PCI
+	  Firmware Specification, Revision 3.2 ECN.
+
+	  When enabled, an LED interface will be created for each PCIe device
+	  that has the ACPI method described in the referenced specification,
+	  to allow the device status LEDs for that PCIe device (presumably a
+	  solid state storage device or its slot) to be seen and controlled.
+
 choice
 	prompt "PCI Express hierarchy optimization setting"
 	default PCIE_BUS_DEFAULT
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index d62c4ac4ae1b..ea0a0f351ad2 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_PCI_PF_STUB)	+= pci-pf-stub.o
 obj-$(CONFIG_PCI_ECAM)		+= ecam.o
 obj-$(CONFIG_PCI_P2PDMA)	+= p2pdma.o
 obj-$(CONFIG_XEN_PCIDEV_FRONTEND) += xen-pcifront.o
+obj-$(CONFIG_PCIE_SSD_LEDS)	+= pcie-ssd-leds.o
 
 # Endpoint library must be initialized before its users
 obj-$(CONFIG_PCI_ENDPOINT)	+= endpoint/
diff --git a/drivers/pci/pcie-ssd-leds.c b/drivers/pci/pcie-ssd-leds.c
new file mode 100644
index 000000000000..cd00b7dd1a8e
--- /dev/null
+++ b/drivers/pci/pcie-ssd-leds.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Module to expose interface to control PCIe SSD LEDs as defined in the
+ * "_DSM additions for PCIe SSD Status LED Management" ECN to the PCI
+ * Firmware Specification Revision 3.2, dated 12 February 2020.
+ *
+ *  Copyright (c) 2021 Dell Inc.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pci.h>
+#include <uapi/linux/uleds.h>
+
+#define DRIVER_NAME	"pcie-ssd-leds"
+#define DRIVER_VERSION	"v1.0"
+
+struct led_state {
+	char *name;
+	u32 mask;
+};
+
+static struct led_state led_states[] = {
+	{ .name = "ok",			.mask = 1 << 2 },
+	{ .name = "locate",		.mask = 1 << 3 },
+	{ .name = "fail",		.mask = 1 << 4 },
+	{ .name = "rebuild",		.mask = 1 << 5 },
+	{ .name = "pfa",		.mask = 1 << 6 },
+	{ .name = "hotspare",		.mask = 1 << 7 },
+	{ .name = "criticalarray",	.mask = 1 << 8 },
+	{ .name = "failedarray",	.mask = 1 << 9 },
+	{ .name = "invaliddevice",	.mask = 1 << 10 },
+	{ .name = "disabled",		.mask = 1 << 11 },
+};
+
+/*
+ * ssd_status_dev could be the SSD's PCIe dev or its hotplug slot
+ */
+struct ssd_status_dev {
+	struct list_head ssd_list;
+	struct pci_dev *pdev;
+	u32 supported_states;
+	struct led_classdev led_cdev;
+	enum led_brightness brightness;
+};
+
+struct mutex ssd_list_lock;
+struct list_head ssd_list;
+
+const guid_t pcie_ssdleds_dsm_guid =
+	GUID_INIT(0x5d524d9d, 0xfff9, 0x4d4b,
+		  0x8c, 0xb7, 0x74, 0x7e, 0xd5, 0x1e, 0x19, 0x4d);
+
+#define GET_SUPPORTED_STATES_DSM	0x01
+#define GET_STATE_DSM			0x02
+#define SET_STATE_DSM			0x03
+
+struct pci_ssdleds_dsm_output {
+	u16 status;
+	u8 function_specific_err;
+	u8 vendor_specific_err;
+	u32 state;
+};
+
+static void dsm_status_err_print(struct device *dev,
+			     struct pci_ssdleds_dsm_output *output)
+{
+	switch (output->status) {
+	case 0:
+		break;
+	case 1:
+		dev_dbg(dev, "_DSM not supported\n");
+		break;
+	case 2:
+		dev_dbg(dev, "_DSM invalid input parameters\n");
+		break;
+	case 3:
+		dev_dbg(dev, "_DSM communication error\n");
+		break;
+	case 4:
+		dev_dbg(dev, "_DSM function-specific error 0x%x\n",
+			output->function_specific_err);
+		break;
+	case 5:
+		dev_dbg(dev, "_DSM vendor-specific error 0x%x\n",
+			output->vendor_specific_err);
+		break;
+	default:
+		dev_dbg(dev, "_DSM returned unknown status 0x%x\n",
+			output->status);
+	}
+}
+
+static int dsm_set(struct device *dev, u32 value)
+{
+	acpi_handle handle;
+	union acpi_object *out_obj, arg3[2];
+	struct pci_ssdleds_dsm_output *dsm_output;
+
+	handle = ACPI_HANDLE(dev);
+	if (!handle)
+		return -ENODEV;
+
+	arg3[0].type = ACPI_TYPE_PACKAGE;
+	arg3[0].package.count = 1;
+	arg3[0].package.elements = &arg3[1];
+
+	arg3[1].type = ACPI_TYPE_BUFFER;
+	arg3[1].buffer.length = 4;
+	arg3[1].buffer.pointer = (u8 *)&value;
+
+	out_obj = acpi_evaluate_dsm_typed(handle, &pcie_ssdleds_dsm_guid,
+				1, SET_STATE_DSM, &arg3[0], ACPI_TYPE_BUFFER);
+	if (!out_obj)
+		return -EIO;
+
+	if (out_obj->buffer.length < 8) {
+		ACPI_FREE(out_obj);
+		return -EIO;
+	}
+
+	dsm_output = (struct pci_ssdleds_dsm_output *)out_obj->buffer.pointer;
+
+	if (dsm_output->status != 0) {
+		dsm_status_err_print(dev, dsm_output);
+		ACPI_FREE(out_obj);
+		return -EIO;
+	}
+	ACPI_FREE(out_obj);
+	return 0;
+}
+
+static int dsm_get(struct device *dev, u64 dsm_func, u32 *output)
+{
+	acpi_handle handle;
+	union acpi_object *out_obj;
+	struct pci_ssdleds_dsm_output *dsm_output;
+
+	handle = ACPI_HANDLE(dev);
+	if (!handle)
+		return -ENODEV;
+
+	out_obj = acpi_evaluate_dsm_typed(handle, &pcie_ssdleds_dsm_guid, 0x1,
+					  dsm_func, NULL, ACPI_TYPE_BUFFER);
+	if (!out_obj)
+		return -EIO;
+
+	if (out_obj->buffer.length < 8) {
+		ACPI_FREE(out_obj);
+		return -EIO;
+	}
+
+	dsm_output = (struct pci_ssdleds_dsm_output *)out_obj->buffer.pointer;
+	if (dsm_output->status != 0) {
+		dsm_status_err_print(dev, dsm_output);
+		ACPI_FREE(out_obj);
+		return -EIO;
+	}
+
+	*output = dsm_output->state;
+	ACPI_FREE(out_obj);
+	return 0;
+}
+
+static bool pdev_has_dsm(struct pci_dev *pdev)
+{
+	acpi_handle handle;
+
+	handle = ACPI_HANDLE(&pdev->dev);
+	if (!handle)
+		return false;
+
+	return !!acpi_check_dsm(handle, &pcie_ssdleds_dsm_guid, 0x1,
+		1 << GET_SUPPORTED_STATES_DSM ||
+		1 << GET_STATE_DSM ||
+		1 << SET_STATE_DSM);
+}
+
+static ssize_t current_states_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct device *dsm_dev = led_cdev->dev->parent;
+	struct ssd_status_dev *ssd;
+	u32 val;
+	int err, i, result = 0;
+
+	ssd = container_of(led_cdev, struct ssd_status_dev, led_cdev);
+
+	err = dsm_get(dsm_dev, GET_STATE_DSM, &val);
+	if (err < 0)
+		return err;
+	for (i = 0; i < ARRAY_SIZE(led_states); i++)
+		if (led_states[i].mask & ssd->supported_states)
+			result += sprintf(buf + result, "%-25s\t0x%04X [%c]\n",
+					  led_states[i].name,
+					  led_states[i].mask,
+					  (val & led_states[i].mask)
+					  ? '*' : ' ');
+
+	result += sprintf(buf + result, "--\ncurrent_states = 0x%04X\n", val);
+	return result;
+}
+
+static ssize_t current_states_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t size)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct device *dsm_dev = led_cdev->dev->parent;
+	struct ssd_status_dev *ssd;
+	u32 val;
+	int err;
+
+	ssd = container_of(led_cdev, struct ssd_status_dev, led_cdev);
+
+	err = kstrtou32(buf, 10, &val);
+	if (err)
+		return err;
+
+	val &= ssd->supported_states;
+	if (val)
+		ssd->brightness = LED_ON;
+	err = dsm_set(dsm_dev, val);
+	if (err < 0)
+		return err;
+
+	return size;
+}
+
+static ssize_t supported_states_show(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct ssd_status_dev *ssd;
+	int i, result = 0;
+
+	ssd = container_of(led_cdev, struct ssd_status_dev, led_cdev);
+
+	for (i = 0; i < ARRAY_SIZE(led_states); i++)
+		result += sprintf(buf + result, "%-25s\t0x%04X [%c]\n",
+				  led_states[i].name,
+				  led_states[i].mask,
+				  (ssd->supported_states & led_states[i].mask)
+				  ? '*' : ' ');
+
+	result += sprintf(buf + result, "--\nsupported_states = 0x%04X\n",
+			  ssd->supported_states);
+	return result;
+}
+
+static DEVICE_ATTR_RW(current_states);
+static DEVICE_ATTR_RO(supported_states);
+
+static struct attribute *pcie_ssd_status_attrs[] = {
+	&dev_attr_current_states.attr,
+	&dev_attr_supported_states.attr,
+	NULL
+};
+
+ATTRIBUTE_GROUPS(pcie_ssd_status);
+
+static int ssdleds_set_brightness(struct led_classdev *led_cdev,
+				  enum led_brightness brightness)
+{
+	struct device *dsm_dev = led_cdev->dev->parent;
+	struct ssd_status_dev *ssd;
+	int err;
+
+	ssd = container_of(led_cdev, struct ssd_status_dev, led_cdev);
+	ssd->brightness = brightness;
+
+	if (brightness == LED_OFF) {
+		err = dsm_set(dsm_dev, 0);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static enum led_brightness ssdleds_get_brightness(struct led_classdev *led_cdev)
+{
+	struct ssd_status_dev *ssd;
+
+	ssd = container_of(led_cdev, struct ssd_status_dev, led_cdev);
+	return ssd->brightness;
+}
+
+static void remove_ssd(struct ssd_status_dev *ssd)
+{
+	mutex_lock(&ssd_list_lock);
+	list_del(&ssd->ssd_list);
+	mutex_unlock(&ssd_list_lock);
+	led_classdev_unregister(&ssd->led_cdev);
+	kfree(ssd);
+}
+
+static struct ssd_status_dev *pci_dev_to_ssd_status_dev(struct pci_dev *pdev)
+{
+	struct ssd_status_dev *ssd;
+
+	mutex_lock(&ssd_list_lock);
+	list_for_each_entry(ssd, &ssd_list, ssd_list)
+		if (pdev == ssd->pdev) {
+			mutex_unlock(&ssd_list_lock);
+			return ssd;
+		}
+	mutex_unlock(&ssd_list_lock);
+	return NULL;
+}
+
+static void remove_ssd_for_pdev(struct pci_dev *pdev)
+{
+	struct ssd_status_dev *ssd = pci_dev_to_ssd_status_dev(pdev);
+
+	if (ssd)
+		remove_ssd(ssd);
+}
+
+static void add_ssd(struct pci_dev *pdev)
+{
+	u32 supported_states;
+	int ret;
+	struct ssd_status_dev *ssd;
+	char name[LED_MAX_NAME_SIZE];
+
+	if (dsm_get(&pdev->dev, GET_SUPPORTED_STATES_DSM, &supported_states) < 0)
+		return;
+
+	ssd = kzalloc(sizeof(*ssd), GFP_KERNEL);
+	if (!ssd)
+		return;
+
+	ssd->pdev = pdev;
+	ssd->supported_states = supported_states;
+	ssd->brightness = LED_ON;
+	snprintf(name, sizeof(name), "%s::%s",
+		 dev_name(&pdev->dev), "pcie_ssd_status");
+	ssd->led_cdev.name = name;
+	ssd->led_cdev.max_brightness = LED_ON;
+	ssd->led_cdev.brightness_set_blocking = ssdleds_set_brightness;
+	ssd->led_cdev.brightness_get = ssdleds_get_brightness;
+	ssd->led_cdev.groups = pcie_ssd_status_groups;
+
+	ret = led_classdev_register(&pdev->dev, &ssd->led_cdev);
+	if (ret) {
+		pr_warn("Failed to register LED %s\n", ssd->led_cdev.name);
+		remove_ssd(ssd);
+		return;
+	}
+	mutex_lock(&ssd_list_lock);
+	list_add_tail(&ssd->ssd_list, &ssd_list);
+	mutex_unlock(&ssd_list_lock);
+}
+
+static void probe_pdev(struct pci_dev *pdev)
+{
+	if (pci_dev_to_ssd_status_dev(pdev))
+		/*
+		 * leds have already been added for this pdev
+		 */
+		return;
+
+	if (pdev_has_dsm(pdev))
+		add_ssd(pdev);
+}
+
+static int pciessdleds_pci_bus_notifier_cb(struct notifier_block *nb,
+					   unsigned long action, void *data)
+{
+	struct pci_dev *pdev = to_pci_dev(data);
+
+	if (action == BUS_NOTIFY_ADD_DEVICE)
+		probe_pdev(pdev);
+	else if (action == BUS_NOTIFY_REMOVED_DEVICE)
+		remove_ssd_for_pdev(pdev);
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block pciessdleds_pci_bus_nb = {
+	.notifier_call = pciessdleds_pci_bus_notifier_cb,
+	.priority = INT_MIN,
+};
+
+static void initial_scan_for_leds(void)
+{
+	struct pci_dev *pdev = NULL;
+
+	for_each_pci_dev(pdev)
+		probe_pdev(pdev);
+}
+
+static int __init pciessdleds_init(void)
+{
+	mutex_init(&ssd_list_lock);
+	INIT_LIST_HEAD(&ssd_list);
+
+	bus_register_notifier(&pci_bus_type, &pciessdleds_pci_bus_nb);
+	initial_scan_for_leds();
+	return 0;
+}
+
+static void __exit pciessdleds_exit(void)
+{
+	struct ssd_status_dev *ssd, *temp;
+
+	bus_unregister_notifier(&pci_bus_type, &pciessdleds_pci_bus_nb);
+	list_for_each_entry_safe(ssd, temp, &ssd_list, ssd_list)
+		remove_ssd(ssd);
+}
+
+module_init(pciessdleds_init);
+module_exit(pciessdleds_exit);
+
+MODULE_AUTHOR("Stuart Hayes <stuart.w.hayes@gmail.com>");
+MODULE_DESCRIPTION("Support for PCIe SSD Status LED Management _DSM");
+MODULE_LICENSE("GPL");
-- 
2.27.0


             reply	other threads:[~2021-04-16 19:20 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-04-16 19:20 Stuart Hayes [this message]
2021-05-05 16:01 ` [PATCH] Add support for PCIe SSD status LED management stuart hayes
2021-05-05 16:12 ` Keith Busch
     [not found]   ` <CAL5oW00Pmnhqi1KZ9-jqFwLXQWBO0ddCyv+dr6qkry7iqZNQsw@mail.gmail.com>
2021-05-24 18:56     ` stuart hayes
2021-05-06  1:48 ` Krzysztof Wilczyński
2021-05-06  2:34   ` Keith Busch
2021-05-06  2:46     ` Krzysztof Wilczyński
2021-05-06 21:04       ` stuart hayes

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210416192010.3197-1-stuart.w.hayes@gmail.com \
    --to=stuart.w.hayes@gmail.com \
    --cc=bhelgaas@google.com \
    --cc=linux-pci@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.