Skip to content

Commit 759ec28

Browse files
mtkaczykbjorn-helgaas
authored andcommitted
PCI/NPEM: Add _DSM PCIe SSD status LED management
The PCIe SSD Status LED Management _DSM defined in PCI Firmware Spec r3.3 sec 4.7 provides a way to manage LEDs via ACPI. The design is similar to NPEM defined in PCIe Base Specification r6.1 sec 6.28: - Both standards are indication oriented, - _DSM supported bits correspond to NPEM capability register bits, - _DSM control bits correspond to NPEM control register bits. _DSM does not support enclosure-specific indications or the special NPEM commands NPEM_ENABLE and NPEM_RESET. _DSM is implemented as a second backend in NPEM driver. The backend used is logged with info priority. The same sysfs interface is used for both NPEM and _DSM. According to spec, _DSM has higher priority, and availability of _DSM in not limited to devices with NPEM support. The Dell implementation of DSM uses acpi ipmi, which may not be available immediately (in fact it may take up to 10s for this interface to be available). It can determine if DSM is supported (GET_SUPPORTED_STATES_DSM is working) but it cannot serve GET_STATE_DSM or SET_STATE_DSM commands in this time. From userspace application perspective (primarily configured by systemd service) it is better to have not working but configured interface rather than have it available after few seconds. For that reason, npem->active_indications cache is now loaded lazily, i.e. any GET or SET request want cache to be updated if it is not done yet. Link: https://lore.kernel.org/r/20240904104848.23480-4-mariusz.tkaczyk@linux.intel.com Suggested-by: Lukas Wunner <lukas@wunner.de> Signed-off-by: Stuart Hayes <stuart.w.hayes@gmail.com> Signed-off-by: Mariusz Tkaczyk <mariusz.tkaczyk@linux.intel.com> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Tested-by: Stuart Hayes <stuart.w.hayes@gmail.com> Reviewed-by: Christoph Hellwig <hch@lst.de> Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
1 parent 4e89354 commit 759ec28

2 files changed

Lines changed: 189 additions & 8 deletions

File tree

Documentation/ABI/testing/sysfs-bus-pci

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,3 +563,12 @@ Description:
563563
indication is usually presented as one or two LEDs blinking at
564564
4 Hz frequency:
565565
https://en.wikipedia.org/wiki/International_Blinking_Pattern_Interpretation
566+
567+
PCI Firmware Specification r3.3 sec 4.7 defines a DSM interface
568+
to facilitate shared access by operating system and platform
569+
firmware to a device's NPEM registers. The kernel will use
570+
this DSM interface where available, instead of accessing NPEM
571+
registers directly. The DSM interface does not support the
572+
enclosure-specific indications "specific0" to "specific7",
573+
hence the corresponding led class devices are unavailable if
574+
the DSM interface is used.

drivers/pci/npem.c

Lines changed: 180 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
* Native PCIe Enclosure Management (NPEM)
1111
* PCIe Base Specification r6.1 sec 6.28, 7.9.19
1212
*
13+
* _DSM Definitions for PCIe SSD Status LED
14+
* PCI Firmware Specification, r3.3 sec 4.7
15+
*
16+
* Two backends are supported to manipulate indications: Direct NPEM register
17+
* access (npem_ops) and indirect access through the ACPI _DSM (dsm_ops).
18+
* _DSM is used if supported, else NPEM.
19+
*
20+
* Copyright (c) 2021-2022 Dell Inc.
1321
* Copyright (c) 2023-2024 Intel Corporation
1422
* Mariusz Tkaczyk <mariusz.tkaczyk@linux.intel.com>
1523
*/
@@ -54,6 +62,21 @@ static const struct indication npem_indications[] = {
5462
{0, NULL}
5563
};
5664

65+
/* _DSM PCIe SSD LED States correspond to NPEM register values */
66+
static const struct indication dsm_indications[] = {
67+
{PCI_NPEM_IND_OK, "enclosure:ok"},
68+
{PCI_NPEM_IND_LOCATE, "enclosure:locate"},
69+
{PCI_NPEM_IND_FAIL, "enclosure:fail"},
70+
{PCI_NPEM_IND_REBUILD, "enclosure:rebuild"},
71+
{PCI_NPEM_IND_PFA, "enclosure:pfa"},
72+
{PCI_NPEM_IND_HOTSPARE, "enclosure:hotspare"},
73+
{PCI_NPEM_IND_ICA, "enclosure:ica"},
74+
{PCI_NPEM_IND_IFA, "enclosure:ifa"},
75+
{PCI_NPEM_IND_IDT, "enclosure:idt"},
76+
{PCI_NPEM_IND_DISABLED, "enclosure:disabled"},
77+
{0, NULL}
78+
};
79+
5780
#define for_each_indication(ind, inds) \
5881
for (ind = inds; ind->bit; ind++)
5982

@@ -123,6 +146,14 @@ struct npem_ops {
123146
* @active_indications: cached bit mask of active indications;
124147
* non-indication and reserved bits in the NPEM Control Register are
125148
* cleared in this bit mask
149+
* @active_inds_initialized: whether @active_indications has been initialized;
150+
* On Dell platforms, it is required that IPMI drivers are loaded before
151+
* the GET_STATE_DSM method is invoked: They use an IPMI OpRegion to
152+
* get/set the active LEDs. By initializing @active_indications lazily
153+
* (on first access to an LED), IPMI drivers are given a chance to load.
154+
* If they are not loaded in time, users will see various errors on LED
155+
* access in dmesg. Once they are loaded, the errors go away and LED
156+
* access becomes possible.
126157
* @led_cnt: size of @leds array
127158
* @leds: array containing LED class devices of all supported LEDs
128159
*/
@@ -133,6 +164,7 @@ struct npem {
133164
u16 pos;
134165
u32 supported_indications;
135166
u32 active_indications;
167+
unsigned int active_inds_initialized:1;
136168
int led_cnt;
137169
struct npem_led leds[];
138170
};
@@ -246,6 +278,139 @@ static bool npem_has_dsm(struct pci_dev *pdev)
246278
BIT(GET_STATE_DSM) | BIT(SET_STATE_DSM));
247279
}
248280

281+
struct dsm_output {
282+
u16 status;
283+
u8 function_specific_err;
284+
u8 vendor_specific_err;
285+
u32 state;
286+
};
287+
288+
/**
289+
* dsm_evaluate() - send DSM PCIe SSD Status LED command
290+
* @pdev: PCI device
291+
* @dsm_func: DSM LED Function
292+
* @output: buffer to copy DSM Response
293+
* @value_to_set: value for SET_STATE_DSM function
294+
*
295+
* To not bother caller with ACPI context, the returned _DSM Output Buffer is
296+
* copied.
297+
*/
298+
static int dsm_evaluate(struct pci_dev *pdev, u64 dsm_func,
299+
struct dsm_output *output, u32 value_to_set)
300+
{
301+
acpi_handle handle = ACPI_HANDLE(&pdev->dev);
302+
union acpi_object *out_obj, arg3[2];
303+
union acpi_object *arg3_p = NULL;
304+
305+
if (dsm_func == SET_STATE_DSM) {
306+
arg3[0].type = ACPI_TYPE_PACKAGE;
307+
arg3[0].package.count = 1;
308+
arg3[0].package.elements = &arg3[1];
309+
310+
arg3[1].type = ACPI_TYPE_BUFFER;
311+
arg3[1].buffer.length = 4;
312+
arg3[1].buffer.pointer = (u8 *)&value_to_set;
313+
314+
arg3_p = arg3;
315+
}
316+
317+
out_obj = acpi_evaluate_dsm_typed(handle, &dsm_guid, 0x1, dsm_func,
318+
arg3_p, ACPI_TYPE_BUFFER);
319+
if (!out_obj)
320+
return -EIO;
321+
322+
if (out_obj->buffer.length < sizeof(struct dsm_output)) {
323+
ACPI_FREE(out_obj);
324+
return -EIO;
325+
}
326+
327+
memcpy(output, out_obj->buffer.pointer, sizeof(struct dsm_output));
328+
329+
ACPI_FREE(out_obj);
330+
return 0;
331+
}
332+
333+
static int dsm_get(struct pci_dev *pdev, u64 dsm_func, u32 *buf)
334+
{
335+
struct dsm_output output;
336+
int ret = dsm_evaluate(pdev, dsm_func, &output, 0);
337+
338+
if (ret)
339+
return ret;
340+
341+
if (output.status != 0)
342+
return -EIO;
343+
344+
*buf = output.state;
345+
return 0;
346+
}
347+
348+
static int dsm_get_active_indications(struct npem *npem, u32 *buf)
349+
{
350+
int ret = dsm_get(npem->dev, GET_STATE_DSM, buf);
351+
352+
/* Filter out not supported indications in response */
353+
*buf &= npem->supported_indications;
354+
return ret;
355+
}
356+
357+
static int dsm_set_active_indications(struct npem *npem, u32 value)
358+
{
359+
struct dsm_output output;
360+
int ret = dsm_evaluate(npem->dev, SET_STATE_DSM, &output, value);
361+
362+
if (ret)
363+
return ret;
364+
365+
switch (output.status) {
366+
case 4:
367+
/*
368+
* Not all bits are set. If this bit is set, the platform
369+
* disregarded some or all of the request state changes. OSPM
370+
* should check the resulting PCIe SSD Status LED States to see
371+
* what, if anything, has changed.
372+
*
373+
* PCI Firmware Specification, r3.3 Table 4-19.
374+
*/
375+
if (output.function_specific_err != 1)
376+
return -EIO;
377+
fallthrough;
378+
case 0:
379+
break;
380+
default:
381+
return -EIO;
382+
}
383+
384+
npem->active_indications = output.state;
385+
386+
return 0;
387+
}
388+
389+
static const struct npem_ops dsm_ops = {
390+
.get_active_indications = dsm_get_active_indications,
391+
.set_active_indications = dsm_set_active_indications,
392+
.name = "_DSM PCIe SSD Status LED Management",
393+
.inds = dsm_indications,
394+
};
395+
396+
static int npem_initialize_active_indications(struct npem *npem)
397+
{
398+
int ret;
399+
400+
lockdep_assert_held(&npem->lock);
401+
402+
if (npem->active_inds_initialized)
403+
return 0;
404+
405+
ret = npem->ops->get_active_indications(npem,
406+
&npem->active_indications);
407+
if (ret)
408+
return ret;
409+
410+
npem->active_inds_initialized = true;
411+
return 0;
412+
}
413+
249414
/*
250415
* The status of each indicator is cached on first brightness_ get/set time
251416
* and updated at write time. brightness_get() is only responsible for
@@ -261,9 +426,14 @@ static enum led_brightness brightness_get(struct led_classdev *led)
261426
if (ret)
262427
return ret;
263428

429+
ret = npem_initialize_active_indications(npem);
430+
if (ret)
431+
goto out;
432+
264433
if (npem->active_indications & nled->indication->bit)
265434
val = 1;
266435

436+
out:
267437
mutex_unlock(&npem->lock);
268438
return val;
269439
}
@@ -280,13 +450,18 @@ static int brightness_set(struct led_classdev *led,
280450
if (ret)
281451
return ret;
282452

453+
ret = npem_initialize_active_indications(npem);
454+
if (ret)
455+
goto out;
456+
283457
if (brightness == 0)
284458
indications = npem->active_indications & ~(nled->indication->bit);
285459
else
286460
indications = npem->active_indications | nled->indication->bit;
287461

288462
ret = npem->ops->set_active_indications(npem, indications);
289463

464+
out:
290465
mutex_unlock(&npem->lock);
291466
return ret;
292467
}
@@ -359,11 +534,6 @@ static int pci_npem_init(struct pci_dev *dev, const struct npem_ops *ops,
359534
npem->dev = dev;
360535
npem->ops = ops;
361536

362-
ret = npem->ops->get_active_indications(npem,
363-
&npem->active_indications);
364-
if (ret)
365-
return ret;
366-
367537
mutex_init(&npem->lock);
368538

369539
for_each_indication(indication, npem_indications) {
@@ -401,9 +571,11 @@ void pci_npem_create(struct pci_dev *dev)
401571
* OS should use the DSM for LED control if it is available
402572
* PCI Firmware Spec r3.3 sec 4.7.
403573
*/
404-
pci_info(dev, "Not configuring %s because _DSM is present\n",
405-
ops->name);
406-
return;
574+
ret = dsm_get(dev, GET_SUPPORTED_STATES_DSM, &cap);
575+
if (ret)
576+
return;
577+
578+
ops = &dsm_ops;
407579
} else {
408580
pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_NPEM);
409581
if (pos == 0)

0 commit comments

Comments
 (0)