diff mbox series

Expose PCIe SSD Status LED Management DSM in sysfs

Message ID 20201106194221.59353-1-stuart.w.hayes@gmail.com
State New
Headers show
Series Expose PCIe SSD Status LED Management DSM in sysfs | expand

Commit Message

Stuart Hayes Nov. 6, 2020, 7:42 p.m. UTC
This patch will expose the PCIe SSD Status LED Management interface in sysfs
for devices that have the relevant _DSM method, per the "_DSM additions for
PCIe SSD Status LED Management" ECN to the PCI Firmware Specification
revision 3.2.

The interface is exposed in two sysfs files, ssdleds_supported_states (RO)
and ssdleds_current_state (RW).

Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com>
---
 Documentation/ABI/testing/sysfs-bus-pci |  14 ++
 drivers/pci/Kconfig                     |   7 +
 drivers/pci/Makefile                    |   1 +
 drivers/pci/pci-ssdleds.c               | 194 ++++++++++++++++++++++++
 drivers/pci/pci-sysfs.c                 |   1 +
 drivers/pci/pci.h                       |  11 ++
 6 files changed, 228 insertions(+)
 create mode 100644 drivers/pci/pci-ssdleds.c

Comments

kernel test robot Nov. 9, 2020, 10:11 a.m. UTC | #1
Hi Stuart,

I love your patch! Perhaps something to improve:

[auto build test WARNING on pci/next]
[also build test WARNING on linus/master v5.10-rc3 next-20201109]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Stuart-Hayes/Expose-PCIe-SSD-Status-LED-Management-DSM-in-sysfs/20201109-100709
base:   https://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci.git next
config: i386-randconfig-r015-20201109 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-15) 9.3.0
reproduce (this is a W=1 build):
        # https://github.com/0day-ci/linux/commit/71fa2ed2b9ac8be92eb60fb757e0333dd6788d2f
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Stuart-Hayes/Expose-PCIe-SSD-Status-LED-Management-DSM-in-sysfs/20201109-100709
        git checkout 71fa2ed2b9ac8be92eb60fb757e0333dd6788d2f
        # save the attached .config to linux build tree
        make W=1 ARCH=i386 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> drivers/pci/pci-ssdleds.c:184:6: warning: no previous prototype for 'pci_create_ssdleds_files' [-Wmissing-prototypes]
     184 | void pci_create_ssdleds_files(struct pci_dev *pdev)
         |      ^~~~~~~~~~~~~~~~~~~~~~~~
   drivers/pci/pci-ssdleds.c: In function 'pci_create_ssdleds_files':
>> drivers/pci/pci-ssdleds.c:186:6: warning: variable 'ret' set but not used [-Wunused-but-set-variable]
     186 |  int ret;
         |      ^~~
   drivers/pci/pci-ssdleds.c: At top level:
>> drivers/pci/pci-ssdleds.c:191:6: warning: no previous prototype for 'pci_remove_ssdleds_files' [-Wmissing-prototypes]
     191 | void pci_remove_ssdleds_files(struct pci_dev *pdev)
         |      ^~~~~~~~~~~~~~~~~~~~~~~~

vim +/pci_create_ssdleds_files +184 drivers/pci/pci-ssdleds.c

   183	
 > 184	void pci_create_ssdleds_files(struct pci_dev *pdev)
   185	{
 > 186		int ret;
   187	
   188		ret = sysfs_create_group(&pdev->dev.kobj, &ssdleds_attr_group);
   189	}
   190	
 > 191	void pci_remove_ssdleds_files(struct pci_dev *pdev)

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
kernel test robot Nov. 9, 2020, 1:41 p.m. UTC | #2
Hi Stuart,

I love your patch! Perhaps something to improve:

[auto build test WARNING on pci/next]
[also build test WARNING on linus/master v5.10-rc3 next-20201109]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Stuart-Hayes/Expose-PCIe-SSD-Status-LED-Management-DSM-in-sysfs/20201109-100709
base:   https://git.kernel.org/pub/scm/linux/kernel/git/helgaas/pci.git next
config: arm64-randconfig-r024-20201109 (attached as .config)
compiler: clang version 12.0.0 (https://github.com/llvm/llvm-project 09ec07827b1128504457a93dee80b2ceee1af600)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # install arm64 cross compiling tool for clang build
        # apt-get install binutils-aarch64-linux-gnu
        # https://github.com/0day-ci/linux/commit/71fa2ed2b9ac8be92eb60fb757e0333dd6788d2f
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Stuart-Hayes/Expose-PCIe-SSD-Status-LED-Management-DSM-in-sysfs/20201109-100709
        git checkout 71fa2ed2b9ac8be92eb60fb757e0333dd6788d2f
        # save the attached .config to linux build tree
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross ARCH=arm64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> drivers/pci/pci-ssdleds.c:126:48: warning: use of logical '||' with constant operand [-Wconstant-logical-operand]
                          1 << SSDLEDS_GET_SUPPORTED_STATES_DSM ||
                                                                ^
   drivers/pci/pci-ssdleds.c:126:48: note: use '|' for a bitwise operation
                          1 << SSDLEDS_GET_SUPPORTED_STATES_DSM ||
                                                                ^~
                                                                |
   drivers/pci/pci-ssdleds.c:127:37: warning: use of logical '||' with constant operand [-Wconstant-logical-operand]
                          1 << SSDLEDS_GET_STATE_DSM ||
                                                     ^
   drivers/pci/pci-ssdleds.c:127:37: note: use '|' for a bitwise operation
                          1 << SSDLEDS_GET_STATE_DSM ||
                                                     ^~
                                                     |
>> drivers/pci/pci-ssdleds.c:128:12: warning: converting the result of '<<' to a boolean always evaluates to true [-Wtautological-constant-compare]
                          1 << SSDLEDS_SET_STATE_DSM);
                            ^
   drivers/pci/pci-ssdleds.c:126:12: warning: converting the result of '<<' to a boolean always evaluates to true [-Wtautological-constant-compare]
                          1 << SSDLEDS_GET_SUPPORTED_STATES_DSM ||
                            ^
   drivers/pci/pci-ssdleds.c:127:12: warning: converting the result of '<<' to a boolean always evaluates to true [-Wtautological-constant-compare]
                          1 << SSDLEDS_GET_STATE_DSM ||
                            ^
>> drivers/pci/pci-ssdleds.c:184:6: warning: no previous prototype for function 'pci_create_ssdleds_files' [-Wmissing-prototypes]
   void pci_create_ssdleds_files(struct pci_dev *pdev)
        ^
   drivers/pci/pci-ssdleds.c:184:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
   void pci_create_ssdleds_files(struct pci_dev *pdev)
   ^
   static 
>> drivers/pci/pci-ssdleds.c:191:6: warning: no previous prototype for function 'pci_remove_ssdleds_files' [-Wmissing-prototypes]
   void pci_remove_ssdleds_files(struct pci_dev *pdev)
        ^
   drivers/pci/pci-ssdleds.c:191:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
   void pci_remove_ssdleds_files(struct pci_dev *pdev)
   ^
   static 
   7 warnings generated.

vim +126 drivers/pci/pci-ssdleds.c

   116	
   117	static bool device_has_dsm(struct device *dev)
   118	{
   119		acpi_handle handle;
   120	
   121		handle = ACPI_HANDLE(dev);
   122		if (!handle)
   123			return false;
   124	
   125		return !!acpi_check_dsm(handle, &pci_ssdleds_dsm_guid, 0x1,
 > 126			       1 << SSDLEDS_GET_SUPPORTED_STATES_DSM ||
   127			       1 << SSDLEDS_GET_STATE_DSM ||
 > 128			       1 << SSDLEDS_SET_STATE_DSM);
   129	}
   130	
   131	static ssize_t ssdleds_current_store(struct device *dev,
   132					struct device_attribute *attr,
   133					const char *buf, size_t count)
   134	{
   135		int ret;
   136	
   137		ret = ssdleds_dsm_set(dev, buf, SSDLEDS_SET_STATE_DSM);
   138		return (ret < 0) ? ret : count;
   139	}
   140	
   141	static ssize_t ssdleds_current_show(struct device *dev,
   142				      struct device_attribute *attr, char *buf)
   143	{
   144		return ssdleds_dsm_get(dev, buf, SSDLEDS_GET_STATE_DSM);
   145	}
   146	
   147	static ssize_t ssdleds_supported_show(struct device *dev,
   148				      struct device_attribute *attr, char *buf)
   149	{
   150		return ssdleds_dsm_get(dev, buf, SSDLEDS_GET_SUPPORTED_STATES_DSM);
   151	}
   152	
   153	static umode_t ssdleds_attr_visible(struct kobject *kobj,
   154					    struct attribute *attr, int n)
   155	{
   156		struct device *dev = kobj_to_dev(kobj);
   157		umode_t mode = attr->mode;
   158	
   159		return device_has_dsm(dev) ? mode : 0;
   160	}
   161	
   162	static struct device_attribute ssdleds_attr_current = {
   163		.attr = {.name = "ssdleds_current_state", .mode = 0644},
   164		.show = ssdleds_current_show,
   165		.store = ssdleds_current_store,
   166	};
   167	
   168	static struct device_attribute ssdleds_attr_supported = {
   169		.attr = {.name = "ssdleds_supported_states", .mode = 0444},
   170		.show = ssdleds_supported_show,
   171	};
   172	
   173	static struct attribute *ssdleds_attributes[] = {
   174		&ssdleds_attr_current.attr,
   175		&ssdleds_attr_supported.attr,
   176		NULL,
   177	};
   178	
   179	static const struct attribute_group ssdleds_attr_group = {
   180		.attrs = ssdleds_attributes,
   181		.is_visible = ssdleds_attr_visible,
   182	};
   183	
 > 184	void pci_create_ssdleds_files(struct pci_dev *pdev)
   185	{
   186		int ret;
   187	
   188		ret = sysfs_create_group(&pdev->dev.kobj, &ssdleds_attr_group);
   189	}
   190	
 > 191	void pci_remove_ssdleds_files(struct pci_dev *pdev)

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-bus-pci b/Documentation/ABI/testing/sysfs-bus-pci
index 450296cc7948..bee7855e408a 100644
--- a/Documentation/ABI/testing/sysfs-bus-pci
+++ b/Documentation/ABI/testing/sysfs-bus-pci
@@ -360,3 +360,17 @@  Contact:	Heiner Kallweit <hkallweit1@gmail.com>
 Description:	If ASPM is supported for an endpoint, these files can be
 		used to disable or enable the individual power management
 		states. Write y/1/on to enable, n/0/off to disable.
+
+What:		/sys/bus/pci/devices/.../ssdleds_supported_states
+Date:		October 2020
+Contact:	Stuart Hayes <stuart.w.hayes@gmail.com>
+Description:	If the device supports the ACPI _DSM method to control the
+		PCIe SSD LED states, ssdleds_supported_states (read only)
+		will show the LED states that are supported by the _DSM.
+
+What:		/sys/bus/pci/devices/.../ssdleds_current_state
+Date:		October 2020
+Contact:	Stuart Hayes <stuart.w.hayes@gmail.com>
+Description:	If the device supports the ACPI _DSM method to control the
+		PCIe SSD LED states, ssdleds_current_state will show or set
+		the current LED states that are active.
diff --git a/drivers/pci/Kconfig b/drivers/pci/Kconfig
index 0c473d75e625..a56b94764182 100644
--- a/drivers/pci/Kconfig
+++ b/drivers/pci/Kconfig
@@ -182,6 +182,13 @@  config PCI_LABEL
 	def_bool y if (DMI || ACPI)
 	select NLS
 
+config PCI_SSDLEDS
+	def_bool y if (ACPI)
+	depends on ACPI
+	help
+	  Enables userspace access to PCIe SSD LED management interface via
+	  sysfs.
+
 config PCI_HYPERV
 	tristate "Hyper-V PCI Frontend"
 	depends on X86_64 && HYPERV && PCI_MSI && PCI_MSI_IRQ_DOMAIN && SYSFS
diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile
index 522d2b974e91..75d3d5a3b1ed 100644
--- a/drivers/pci/Makefile
+++ b/drivers/pci/Makefile
@@ -23,6 +23,7 @@  obj-$(CONFIG_PCI_ATS)		+= ats.o
 obj-$(CONFIG_PCI_IOV)		+= iov.o
 obj-$(CONFIG_PCI_BRIDGE_EMUL)	+= pci-bridge-emul.o
 obj-$(CONFIG_PCI_LABEL)		+= pci-label.o
+obj-$(CONFIG_PCI_SSDLEDS)	+= pci-ssdleds.o
 obj-$(CONFIG_X86_INTEL_MID)	+= pci-mid.o
 obj-$(CONFIG_PCI_SYSCALL)	+= syscall.o
 obj-$(CONFIG_PCI_STUB)		+= pci-stub.o
diff --git a/drivers/pci/pci-ssdleds.c b/drivers/pci/pci-ssdleds.c
new file mode 100644
index 000000000000..7b9afd3d0ccb
--- /dev/null
+++ b/drivers/pci/pci-ssdleds.c
@@ -0,0 +1,194 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Provide userspace access to PCIe SSD Status LED Management
+ *
+ * The PCIe spec defines an ACPI _DSM method to allow PCIe SSD status LED
+ * management (see "_DSM additions for PCIe SSD Status LED Management" ECN,
+ * February 12, 2020). This code provides a sysfs interface to this _DSM.
+ *
+ * Copyright (C) 2020 Dell Inc.
+ *
+ */
+
+#include <linux/sysfs.h>
+#include <linux/pci.h>
+#include <linux/device.h>
+#include <linux/acpi.h>
+
+const guid_t pci_ssdleds_dsm_guid =
+	GUID_INIT(0x5d524d9d, 0xfff9, 0x4d4b,
+		  0x8c, 0xb7, 0x74, 0x7e, 0xd5, 0x1e, 0x19, 0x4d);
+
+#define SSDLEDS_GET_SUPPORTED_STATES_DSM	0x01
+#define SSDLEDS_GET_STATE_DSM			0x02
+#define SSDLEDS_SET_STATE_DSM			0x03
+
+struct pci_ssdleds_dsm_output {
+	u16 status;
+	u8 function_specific_err;
+	u8 vendor_specific_err;
+	u32 state;
+};
+
+static int ssdleds_dsm_set(struct device *dev, const char *buf, u64 dsm_func)
+{
+	acpi_handle handle;
+	union acpi_object *out_obj, arg3[2];
+	struct pci_ssdleds_dsm_output *dsm_output;
+	u32 val;
+	int err;
+
+	handle = ACPI_HANDLE(dev);
+	if (!handle)
+		return -ENODEV;
+
+	err = kstrtou32(buf, 0, &val);
+	if (err || val > U32_MAX)
+		return -EINVAL;
+
+	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 *)&val;
+
+	out_obj = acpi_evaluate_dsm_typed(handle, &pci_ssdleds_dsm_guid, 0x1,
+				      dsm_func, &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;
+	/*
+	 * Ignore function specific error bit 1 (some LED state bits weren't
+	 * set), since write was done. User can read current state to see which
+	 * bits were set.
+	 */
+	if (dsm_output->status != 0 &&
+	    !(dsm_output->status == 4 && dsm_output->function_specific_err == 1)) {
+		ACPI_FREE(out_obj);
+		return -EIO;
+	}
+
+	ACPI_FREE(out_obj);
+
+	return 0;
+}
+
+static int ssdleds_dsm_get(struct device *dev, char *buf, u64 dsm_func)
+{
+	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, &pci_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) {
+		ACPI_FREE(out_obj);
+		return -EIO;
+	}
+
+	scnprintf(buf, PAGE_SIZE, "%#x\n", dsm_output->state);
+
+	ACPI_FREE(out_obj);
+
+	return strlen(buf) > 0 ? strlen(buf) : -EIO;
+}
+
+static bool device_has_dsm(struct device *dev)
+{
+	acpi_handle handle;
+
+	handle = ACPI_HANDLE(dev);
+	if (!handle)
+		return false;
+
+	return !!acpi_check_dsm(handle, &pci_ssdleds_dsm_guid, 0x1,
+		       1 << SSDLEDS_GET_SUPPORTED_STATES_DSM ||
+		       1 << SSDLEDS_GET_STATE_DSM ||
+		       1 << SSDLEDS_SET_STATE_DSM);
+}
+
+static ssize_t ssdleds_current_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	int ret;
+
+	ret = ssdleds_dsm_set(dev, buf, SSDLEDS_SET_STATE_DSM);
+	return (ret < 0) ? ret : count;
+}
+
+static ssize_t ssdleds_current_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	return ssdleds_dsm_get(dev, buf, SSDLEDS_GET_STATE_DSM);
+}
+
+static ssize_t ssdleds_supported_show(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	return ssdleds_dsm_get(dev, buf, SSDLEDS_GET_SUPPORTED_STATES_DSM);
+}
+
+static umode_t ssdleds_attr_visible(struct kobject *kobj,
+				    struct attribute *attr, int n)
+{
+	struct device *dev = kobj_to_dev(kobj);
+	umode_t mode = attr->mode;
+
+	return device_has_dsm(dev) ? mode : 0;
+}
+
+static struct device_attribute ssdleds_attr_current = {
+	.attr = {.name = "ssdleds_current_state", .mode = 0644},
+	.show = ssdleds_current_show,
+	.store = ssdleds_current_store,
+};
+
+static struct device_attribute ssdleds_attr_supported = {
+	.attr = {.name = "ssdleds_supported_states", .mode = 0444},
+	.show = ssdleds_supported_show,
+};
+
+static struct attribute *ssdleds_attributes[] = {
+	&ssdleds_attr_current.attr,
+	&ssdleds_attr_supported.attr,
+	NULL,
+};
+
+static const struct attribute_group ssdleds_attr_group = {
+	.attrs = ssdleds_attributes,
+	.is_visible = ssdleds_attr_visible,
+};
+
+void pci_create_ssdleds_files(struct pci_dev *pdev)
+{
+	int ret;
+
+	ret = sysfs_create_group(&pdev->dev.kobj, &ssdleds_attr_group);
+}
+
+void pci_remove_ssdleds_files(struct pci_dev *pdev)
+{
+	sysfs_remove_group(&pdev->dev.kobj, &ssdleds_attr_group);
+}
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index d15c881e2e7e..820f32956971 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -1377,6 +1377,7 @@  int __must_check pci_create_sysfs_dev_files(struct pci_dev *pdev)
 		goto err_rom_file;
 
 	pci_create_firmware_label_files(pdev);
+	pci_create_ssdleds_files(pdev);
 
 	return 0;
 
diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h
index f86cae9aa1f4..8e6883a1b701 100644
--- a/drivers/pci/pci.h
+++ b/drivers/pci/pci.h
@@ -30,6 +30,17 @@  static inline void pci_remove_firmware_label_files(struct pci_dev *pdev)
 void pci_create_firmware_label_files(struct pci_dev *pdev);
 void pci_remove_firmware_label_files(struct pci_dev *pdev);
 #endif
+
+#if !defined(CONFIG_PCI_SSDLEDS)
+static inline void pci_create_ssdleds_files(struct pci_dev *pdev)
+{ return; }
+static inline void pci_remove_ssdleds_files(struct pci_dev *pdev)
+{ return; }
+#else
+void pci_create_ssdleds_files(struct pci_dev *pdev);
+void pci_remove_ssdleds_files(struct pci_dev *pdev);
+#endif
+
 void pci_cleanup_rom(struct pci_dev *dev);
 
 enum pci_mmap_api {