// SPDX-License-Identifier: GPL-2.0 /* * PCIe Enclosure management driver created for LED interfaces based on * indications. It says *what indications* blink but does not specify *how* * they blink - it is hardware defined. * * The driver name refers to Native PCIe Enclosure Management. It is * first indication oriented standard with specification. * * Native PCIe Enclosure Management (NPEM) * PCIe Base Specification r6.1 sec 6.28, 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>
*/
#define for_each_indication(ind, inds) \ for (ind = inds; ind->bit; ind++)
/* * The driver has internal list of supported indications. Ideally, the driver * should not touch bits that are not defined and for which LED devices are * not exposed but in reality, it needs to turn them off. * * Otherwise, there will be no possibility to turn off indications turned on by * other utilities or turned on by default and it leads to bad user experience. * * Additionally, it excludes NPEM commands like RESET or ENABLE.
*/ static u32 reg_to_indications(u32 caps, conststruct indication *inds)
{ conststruct indication *ind;
u32 supported_indications = 0;
/** * struct npem_led - LED details * @indication: indication details * @npem: NPEM device * @name: LED name * @led: LED device
*/ struct npem_led { conststruct indication *indication; struct npem *npem; char name[LED_MAX_NAME_SIZE]; struct led_classdev led;
};
/** * struct npem_ops - backend specific callbacks * @get_active_indications: get active indications * npem: NPEM device * inds: response buffer * @set_active_indications: set new indications * npem: npem device * inds: bit mask to set * @inds: supported indications array, set of indications is backend specific * @name: backend name
*/ struct npem_ops { int (*get_active_indications)(struct npem *npem, u32 *inds); int (*set_active_indications)(struct npem *npem, u32 inds); conststruct indication *inds; constchar *name;
};
/** * struct npem - NPEM device properties * @dev: PCI device this driver is attached to * @ops: backend specific callbacks * @lock: serializes concurrent access to NPEM device by multiple LED devices * @pos: cached offset of NPEM Capability Register in Configuration Space; * only used if NPEM registers are accessed directly and not through _DSM * @supported_indications: cached bit mask of supported indications; * non-indication and reserved bits in the NPEM Capability Register are * cleared in this bit mask * @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
*/ struct npem { struct pci_dev *dev; conststruct npem_ops *ops; struct mutex lock;
u16 pos;
u32 supported_indications;
u32 active_indications; unsignedint active_inds_initialized:1; int led_cnt; struct npem_led leds[];
};
staticint npem_read_reg(struct npem *npem, u16 reg, u32 *val)
{ int ret = pci_read_config_dword(npem->dev, npem->pos + reg, val);
return pcibios_err_to_errno(ret);
}
staticint npem_write_ctrl(struct npem *npem, u32 reg)
{ int pos = npem->pos + PCI_NPEM_CTRL; int ret = pci_write_config_dword(npem->dev, pos, reg);
/* This bit is always required */
ctrl = inds | PCI_NPEM_CTRL_ENABLE;
ret = npem_write_ctrl(npem, ctrl); if (ret) return ret;
/* * For the case where a NPEM command has not completed immediately, * it is recommended that software not continuously "spin" on polling * the status register, but rather poll under interrupt at a reduced * rate; for example at 10 ms intervals. * * PCIe r6.1 sec 6.28 "Implementation Note: Software Polling of NPEM * Command Completed"
*/
ret = read_poll_timeout(npem_read_reg, ret_val,
ret_val || (cc_status & PCI_NPEM_STATUS_CC),
10 * USEC_PER_MSEC, USEC_PER_SEC, false, npem,
PCI_NPEM_STATUS, &cc_status); if (ret) return ret; if (ret_val) return ret_val;
/* * All writes to control register, including writes that do not change * the register value, are NPEM commands and should eventually result * in a command completion indication in the NPEM Status Register. * * PCIe Base Specification r6.1 sec 7.9.19.3 * * Register may not be updated, or other conflicting bits may be * cleared. Spec is not strict here. Read NPEM Control register after * write to keep cache in-sync.
*/ return npem_get_active_indications(npem, &npem->active_indications);
}
/** * 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.
*/ staticint 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;
staticint 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;
}
staticint 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;
}
staticint 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;
}
staticint 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. brightness_get() is only responsible for * reflecting the last written/cached value.
*/ staticenum led_brightness brightness_get(struct led_classdev *led)
{ struct npem_led *nled = container_of(led, struct npem_led, led); struct npem *npem = nled->npem; int ret, val = 0;
ret = mutex_lock_interruptible(&npem->lock); if (ret) return ret;
ret = npem_initialize_active_indications(npem); if (ret) goto out;
if (npem->active_indications & nled->indication->bit)
val = 1;
if (npem_has_dsm(dev)) { /* * OS should use the DSM for LED control if it is available * PCI Firmware Spec r3.3 sec 4.7.
*/
ret = dsm_get(dev, GET_SUPPORTED_STATES_DSM, &cap); if (ret) return;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.