// 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 */ #include #include #include #include #include #include #include #include #include #include #include "pci.h" struct indication { u32 bit; const char *name; }; static const struct indication npem_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"}, {PCI_NPEM_IND_SPEC_0, "enclosure:specific_0"}, {PCI_NPEM_IND_SPEC_1, "enclosure:specific_1"}, {PCI_NPEM_IND_SPEC_2, "enclosure:specific_2"}, {PCI_NPEM_IND_SPEC_3, "enclosure:specific_3"}, {PCI_NPEM_IND_SPEC_4, "enclosure:specific_4"}, {PCI_NPEM_IND_SPEC_5, "enclosure:specific_5"}, {PCI_NPEM_IND_SPEC_6, "enclosure:specific_6"}, {PCI_NPEM_IND_SPEC_7, "enclosure:specific_7"}, {0, NULL} }; /* _DSM PCIe SSD LED States correspond 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++) /* * 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, const struct indication *inds) { const struct indication *ind; u32 supported_indications = 0; for_each_indication(ind, inds) supported_indications |= ind->bit; return caps & supported_indications; } /** * struct npem_led - LED details * @indication: indication details * @npem: NPEM device * @name: LED name * @led: LED device */ struct npem_led { const struct 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); const struct indication *inds; const char *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; const struct npem_ops *ops; struct mutex lock; u16 pos; u32 supported_indications; u32 active_indications; unsigned int active_inds_initialized:1; int led_cnt; struct npem_led leds[]; }; static int 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); } static int 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); return pcibios_err_to_errno(ret); } static int npem_get_active_indications(struct npem *npem, u32 *inds) { u32 ctrl; int ret; ret = npem_read_reg(npem, PCI_NPEM_CTRL, &ctrl); if (ret) return ret; /* If PCI_NPEM_CTRL_ENABLE is not set then no indication should blink */ if (!(ctrl & PCI_NPEM_CTRL_ENABLE)) { *inds = 0; return 0; } *inds = ctrl & npem->supported_indications; return 0; } static int npem_set_active_indications(struct npem *npem, u32 inds) { int ctrl, ret, ret_val; u32 cc_status; lockdep_assert_held(&npem->lock); /* 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); } static const struct npem_ops npem_ops = { .get_active_indications = npem_get_active_indications, .set_active_indications = npem_set_active_indications, .name = "Native PCIe Enclosure Management", .inds = npem_indications, }; #define DSM_GUID GUID_INIT(0x5d524d9d, 0xfff9, 0x4d4b, 0x8c, 0xb7, 0x74, 0x7e,\ 0xd5, 0x1e, 0x19, 0x4d) #define GET_SUPPORTED_STATES_DSM 1 #define GET_STATE_DSM 2 #define SET_STATE_DSM 3 static const guid_t dsm_guid = DSM_GUID; static bool npem_has_dsm(struct pci_dev *pdev) { acpi_handle handle; handle = ACPI_HANDLE(&pdev->dev); if (!handle) return false; return acpi_check_dsm(handle, &dsm_guid, 0x1, BIT(GET_SUPPORTED_STATES_DSM) | 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. brightness_get() is only responsible for * reflecting the last written/cached value. */ static enum 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; out: mutex_unlock(&npem->lock); return val; } static int brightness_set(struct led_classdev *led, enum led_brightness brightness) { struct npem_led *nled = container_of(led, struct npem_led, led); struct npem *npem = nled->npem; u32 indications; int ret; ret = mutex_lock_interruptible(&npem->lock); 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 indications = npem->active_indications | nled->indication->bit; ret = npem->ops->set_active_indications(npem, indications); out: mutex_unlock(&npem->lock); return ret; } static void npem_free(struct npem *npem) { struct npem_led *nled; int cnt; if (!npem) return; for (cnt = 0; cnt < npem->led_cnt; cnt++) { nled = &npem->leds[cnt]; if (nled->name[0]) led_classdev_unregister(&nled->led); } mutex_destroy(&npem->lock); kfree(npem); } static int pci_npem_set_led_classdev(struct npem *npem, struct npem_led *nled) { struct led_classdev *led = &nled->led; struct led_init_data init_data = {}; char *name = nled->name; int ret; init_data.devicename = pci_name(npem->dev); init_data.default_label = nled->indication->name; ret = led_compose_name(&npem->dev->dev, &init_data, name); if (ret) return ret; led->name = name; led->brightness_set_blocking = brightness_set; led->brightness_get = brightness_get; led->max_brightness = 1; led->default_trigger = "none"; led->flags = 0; ret = led_classdev_register(&npem->dev->dev, led); if (ret) /* Clear the name to indicate that it is not registered. */ name[0] = 0; return ret; } static int pci_npem_init(struct pci_dev *dev, const struct npem_ops *ops, int pos, u32 caps) { u32 supported = reg_to_indications(caps, ops->inds); int supported_cnt = hweight32(supported); const struct indication *indication; struct npem_led *nled; struct npem *npem; int led_idx = 0; int ret; npem = kzalloc(struct_size(npem, leds, supported_cnt), GFP_KERNEL); if (!npem) return -ENOMEM; npem->supported_indications = supported; npem->led_cnt = supported_cnt; npem->pos = pos; npem->dev = dev; npem->ops = ops; mutex_init(&npem->lock); for_each_indication(indication, npem_indications) { if (!(npem->supported_indications & indication->bit)) continue; nled = &npem->leds[led_idx++]; nled->indication = indication; nled->npem = npem; ret = pci_npem_set_led_classdev(npem, nled); if (ret) { npem_free(npem); return ret; } } dev->npem = npem; return 0; } void pci_npem_remove(struct pci_dev *dev) { npem_free(dev->npem); } void pci_npem_create(struct pci_dev *dev) { const struct npem_ops *ops = &npem_ops; int pos = 0, ret; u32 cap; 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; ops = &dsm_ops; } else { pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_NPEM); if (pos == 0) return; if (pci_read_config_dword(dev, pos + PCI_NPEM_CAP, &cap) != 0 || (cap & PCI_NPEM_CAP_CAPABLE) == 0) return; } pci_info(dev, "Configuring %s\n", ops->name); ret = pci_npem_init(dev, ops, pos, cap); if (ret) pci_err(dev, "Failed to register %s, err: %d\n", ops->name, ret); }