@@ -563,3 +563,12 @@ Description:
indication is usually presented as one or two LEDs blinking at
4 Hz frequency:
https://en.wikipedia.org/wiki/International_Blinking_Pattern_Interpretation
+
+ PCI Firmware Specification r3.3 sec 4.7 defines a DSM interface
+ to facilitate shared access by operating system and platform
+ firmware to a device's NPEM registers. The kernel will use
+ this DSM interface where available, instead of accessing NPEM
+ registers directly. The DSM interface does not support the
+ enclosure-specific indications "specific0" to "specific7",
+ hence the corresponding led class devices are unavailable if
+ the DSM interface is used.
@@ -11,6 +11,14 @@
* PCIe Base Specification r6.1 sec 6.28
* PCIe Base Specification r6.1 sec 7.9.19
*
+ * _DSM Definitions for PCIe SSD Status LED
+ * PCI Firmware Specification, r3.3 sec 4.7
+ *
+ * Two backends are supported to manipulate indications: Direct NPEM register
+ * access (npem_ops) and indirect access through the ACPI _DSM (dsm_ops).
+ * _DSM is used if supported, else NPEM.
+ *
+ * Copyright (c) 2021-2022 Dell Inc.
* Copyright (c) 2023-2024 Intel Corporation
* Mariusz Tkaczyk <mariusz.tkaczyk@linux.intel.com>
*/
@@ -55,6 +63,21 @@ static const struct indication npem_indications[] = {
{0, NULL}
};
+/* _DSM PCIe SSD LED States are corresponding to NPEM register values */
+static const struct indication dsm_indications[] = {
+ {PCI_NPEM_IND_OK, "enclosure:ok"},
+ {PCI_NPEM_IND_LOCATE, "enclosure:locate"},
+ {PCI_NPEM_IND_FAIL, "enclosure:fail"},
+ {PCI_NPEM_IND_REBUILD, "enclosure:rebuild"},
+ {PCI_NPEM_IND_PFA, "enclosure:pfa"},
+ {PCI_NPEM_IND_HOTSPARE, "enclosure:hotspare"},
+ {PCI_NPEM_IND_ICA, "enclosure:ica"},
+ {PCI_NPEM_IND_IFA, "enclosure:ifa"},
+ {PCI_NPEM_IND_IDT, "enclosure:idt"},
+ {PCI_NPEM_IND_DISABLED, "enclosure:disabled"},
+ {0, NULL}
+};
+
#define for_each_indication(ind, inds) \
for (ind = inds; ind->bit; ind++)
@@ -124,6 +147,14 @@ struct npem_ops {
* @active_indications: cached bit mask of active indications;
* non-indication and reserved bits in the NPEM Control Register are
* cleared in this bit mask
+ * @active_inds_initialized: whether @active_indications has been initialized;
+ * On Dell platforms, it is required that IPMI drivers are loaded before
+ * the GET_STATE_DSM method is invoked: They use an IPMI OpRegion to
+ * get/set the active LEDs. By initializing @active_indications lazily
+ * (on first access to an LED), IPMI drivers are given a chance to load.
+ * If they are not loaded in time, users will see various errors on LED
+ * access in dmesg. Once they are loaded, the errors go away and LED
+ * access becomes possible.
* @led_cnt: size of @leds array
* @leds: array containing LED class devices of all supported LEDs
*/
@@ -134,6 +165,7 @@ struct npem {
u16 pos;
u32 supported_indications;
u32 active_indications;
+ unsigned int active_inds_initialized:1;
int led_cnt;
struct npem_led leds[];
};
@@ -247,6 +279,139 @@ static bool npem_has_dsm(struct pci_dev *pdev)
BIT(GET_STATE_DSM) | BIT(SET_STATE_DSM));
}
+struct dsm_output {
+ u16 status;
+ u8 function_specific_err;
+ u8 vendor_specific_err;
+ u32 state;
+};
+
+/**
+ * dsm_evaluate() - send DSM PCIe SSD Status LED command
+ * @pdev: PCI device
+ * @dsm_func: DSM LED Function
+ * @output: buffer to copy DSM Response
+ * @value_to_set: value for SET_STATE_DSM function
+ *
+ * To not bother caller with ACPI context, the returned _DSM Output Buffer is
+ * copied.
+ */
+static int dsm_evaluate(struct pci_dev *pdev, u64 dsm_func,
+ struct dsm_output *output, u32 value_to_set)
+{
+ acpi_handle handle = ACPI_HANDLE(&pdev->dev);
+ union acpi_object *out_obj, arg3[2];
+ union acpi_object *arg3_p = NULL;
+
+ if (dsm_func == SET_STATE_DSM) {
+ 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_to_set;
+
+ arg3_p = arg3;
+ }
+
+ out_obj = acpi_evaluate_dsm_typed(handle, &dsm_guid, 0x1, dsm_func,
+ arg3_p, ACPI_TYPE_BUFFER);
+ if (!out_obj)
+ return -EIO;
+
+ if (out_obj->buffer.length < sizeof(struct dsm_output)) {
+ ACPI_FREE(out_obj);
+ return -EIO;
+ }
+
+ memcpy(output, out_obj->buffer.pointer, sizeof(struct dsm_output));
+
+ ACPI_FREE(out_obj);
+ return 0;
+}
+
+static int dsm_get(struct pci_dev *pdev, u64 dsm_func, u32 *buf)
+{
+ struct dsm_output output;
+ int ret = dsm_evaluate(pdev, dsm_func, &output, 0);
+
+ if (ret)
+ return ret;
+
+ if (output.status != 0)
+ return -EIO;
+
+ *buf = output.state;
+ return 0;
+}
+
+static int dsm_get_active_indications(struct npem *npem, u32 *buf)
+{
+ int ret = dsm_get(npem->dev, GET_STATE_DSM, buf);
+
+ /* Filter out not supported indications in response */
+ *buf &= npem->supported_indications;
+ return ret;
+}
+
+static int dsm_set_active_indications(struct npem *npem, u32 value)
+{
+ struct dsm_output output;
+ int ret = dsm_evaluate(npem->dev, SET_STATE_DSM, &output, value);
+
+ if (ret)
+ return ret;
+
+ switch (output.status) {
+ case 4:
+ /*
+ * Not all bits are set. If this bit is set, the platform
+ * disregarded some or all of the request state changes. OSPM
+ * should check the resulting PCIe SSD Status LED States to see
+ * what, if anything, has changed.
+ *
+ * PCI Firmware Specification, r3.3 Table 4-19.
+ */
+ if (output.function_specific_err != 1)
+ return -EIO;
+ fallthrough;
+ case 0:
+ break;
+ default:
+ return -EIO;
+ }
+
+ npem->active_indications = output.state;
+
+ return 0;
+}
+
+static const struct npem_ops dsm_ops = {
+ .get_active_indications = dsm_get_active_indications,
+ .set_active_indications = dsm_set_active_indications,
+ .name = "_DSM PCIe SSD Status LED Management",
+ .inds = dsm_indications,
+};
+
+static int npem_initialize_active_indications(struct npem *npem)
+{
+ int ret;
+
+ lockdep_assert_held(&npem->lock);
+
+ if (npem->active_inds_initialized)
+ return 0;
+
+ ret = npem->ops->get_active_indications(npem,
+ &npem->active_indications);
+ if (ret)
+ return ret;
+
+ npem->active_inds_initialized = true;
+ return 0;
+}
+
/*
* The status of each indicator is cached on first brightness_ get/set time and
* updated at write time.
@@ -263,9 +428,14 @@ static enum led_brightness brightness_get(struct led_classdev *led)
if (ret)
return ret;
+ ret = npem_initialize_active_indications(npem);
+ if (ret)
+ goto out;
+
if (npem->active_indications & nled->indication->bit)
val = 1;
+out:
mutex_unlock(&npem->lock);
return val;
}
@@ -282,6 +452,10 @@ static int brightness_set(struct led_classdev *led,
if (ret)
return ret;
+ ret = npem_initialize_active_indications(npem);
+ if (ret)
+ goto out;
+
if (brightness == 0)
indications = npem->active_indications & ~(nled->indication->bit);
else
@@ -289,6 +463,7 @@ static int brightness_set(struct led_classdev *led,
ret = npem->ops->set_active_indications(npem, indications);
+out:
mutex_unlock(&npem->lock);
return ret;
}
@@ -361,11 +536,6 @@ static int pci_npem_init(struct pci_dev *dev, const struct npem_ops *ops,
npem->dev = dev;
npem->ops = ops;
- ret = npem->ops->get_active_indications(npem,
- &npem->active_indications);
- if (ret)
- return ret;
-
mutex_init(&npem->lock);
for_each_indication(indication, npem_indications) {
@@ -411,9 +581,11 @@ void pci_npem_create(struct pci_dev *dev)
* OS should use the DSM for LED control if it is available
* PCI Firmware Spec r3.3 sec 4.7.
*/
- pci_info(dev, "Not configuring %s because _DSM is present\n",
- ops->name);
- return;
+ ret = dsm_get(dev, GET_SUPPORTED_STATES_DSM, &cap);
+ if (ret)
+ return;
+
+ ops = &dsm_ops;
}
pci_info(dev, "Configuring %s\n", ops->name);