| @@ -0,0 +1,423 @@ |
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* |
| 3 | + * PCIe Enclosure management driver created for LED interfaces based on |
| 4 | + * indications. It says *what indications* blink but does not specify *how* |
| 5 | + * they blink - it is hardware defined. |
| 6 | + * |
| 7 | + * The driver name refers to Native PCIe Enclosure Management. It is |
| 8 | + * first indication oriented standard with specification. |
| 9 | + * |
| 10 | + * Native PCIe Enclosure Management (NPEM) |
| 11 | + * PCIe Base Specification r6.1 sec 6.28, 7.9.19 |
| 12 | + * |
| 13 | + * Copyright (c) 2023-2024 Intel Corporation |
| 14 | + * Mariusz Tkaczyk <mariusz.tkaczyk@linux.intel.com> |
| 15 | + */ |
| 16 | + |
| 17 | +#include <linux/acpi.h> |
| 18 | +#include <linux/bitops.h> |
| 19 | +#include <linux/errno.h> |
| 20 | +#include <linux/iopoll.h> |
| 21 | +#include <linux/leds.h> |
| 22 | +#include <linux/mutex.h> |
| 23 | +#include <linux/pci.h> |
| 24 | +#include <linux/pci_regs.h> |
| 25 | +#include <linux/types.h> |
| 26 | +#include <linux/uleds.h> |
| 27 | + |
| 28 | +#include "pci.h" |
| 29 | + |
| 30 | +struct indication { |
| 31 | + u32 bit; |
| 32 | + const char *name; |
| 33 | +}; |
| 34 | + |
| 35 | +static const struct indication npem_indications[] = { |
| 36 | + {PCI_NPEM_IND_OK, "enclosure:ok"}, |
| 37 | + {PCI_NPEM_IND_LOCATE, "enclosure:locate"}, |
| 38 | + {PCI_NPEM_IND_FAIL, "enclosure:fail"}, |
| 39 | + {PCI_NPEM_IND_REBUILD, "enclosure:rebuild"}, |
| 40 | + {PCI_NPEM_IND_PFA, "enclosure:pfa"}, |
| 41 | + {PCI_NPEM_IND_HOTSPARE, "enclosure:hotspare"}, |
| 42 | + {PCI_NPEM_IND_ICA, "enclosure:ica"}, |
| 43 | + {PCI_NPEM_IND_IFA, "enclosure:ifa"}, |
| 44 | + {PCI_NPEM_IND_IDT, "enclosure:idt"}, |
| 45 | + {PCI_NPEM_IND_DISABLED, "enclosure:disabled"}, |
| 46 | + {PCI_NPEM_IND_SPEC_0, "enclosure:specific_0"}, |
| 47 | + {PCI_NPEM_IND_SPEC_1, "enclosure:specific_1"}, |
| 48 | + {PCI_NPEM_IND_SPEC_2, "enclosure:specific_2"}, |
| 49 | + {PCI_NPEM_IND_SPEC_3, "enclosure:specific_3"}, |
| 50 | + {PCI_NPEM_IND_SPEC_4, "enclosure:specific_4"}, |
| 51 | + {PCI_NPEM_IND_SPEC_5, "enclosure:specific_5"}, |
| 52 | + {PCI_NPEM_IND_SPEC_6, "enclosure:specific_6"}, |
| 53 | + {PCI_NPEM_IND_SPEC_7, "enclosure:specific_7"}, |
| 54 | + {0, NULL} |
| 55 | +}; |
| 56 | + |
| 57 | +#define for_each_indication(ind, inds) \ |
| 58 | + for (ind = inds; ind->bit; ind++) |
| 59 | + |
| 60 | +/* |
| 61 | + * The driver has internal list of supported indications. Ideally, the driver |
| 62 | + * should not touch bits that are not defined and for which LED devices are |
| 63 | + * not exposed but in reality, it needs to turn them off. |
| 64 | + * |
| 65 | + * Otherwise, there will be no possibility to turn off indications turned on by |
| 66 | + * other utilities or turned on by default and it leads to bad user experience. |
| 67 | + * |
| 68 | + * Additionally, it excludes NPEM commands like RESET or ENABLE. |
| 69 | + */ |
| 70 | +static u32 reg_to_indications(u32 caps, const struct indication *inds) |
| 71 | +{ |
| 72 | + const struct indication *ind; |
| 73 | + u32 supported_indications = 0; |
| 74 | + |
| 75 | + for_each_indication(ind, inds) |
| 76 | + supported_indications |= ind->bit; |
| 77 | + |
| 78 | + return caps & supported_indications; |
| 79 | +} |
| 80 | + |
| 81 | +/** |
| 82 | + * struct npem_led - LED details |
| 83 | + * @indication: indication details |
| 84 | + * @npem: NPEM device |
| 85 | + * @name: LED name |
| 86 | + * @led: LED device |
| 87 | + */ |
| 88 | +struct npem_led { |
| 89 | + const struct indication *indication; |
| 90 | + struct npem *npem; |
| 91 | + char name[LED_MAX_NAME_SIZE]; |
| 92 | + struct led_classdev led; |
| 93 | +}; |
| 94 | + |
| 95 | +/** |
| 96 | + * struct npem_ops - backend specific callbacks |
| 97 | + * @get_active_indications: get active indications |
| 98 | + * npem: NPEM device |
| 99 | + * inds: response buffer |
| 100 | + * @set_active_indications: set new indications |
| 101 | + * npem: npem device |
| 102 | + * inds: bit mask to set |
| 103 | + * @inds: supported indications array, set of indications is backend specific |
| 104 | + * @name: backend name |
| 105 | + */ |
| 106 | +struct npem_ops { |
| 107 | + int (*get_active_indications)(struct npem *npem, u32 *inds); |
| 108 | + int (*set_active_indications)(struct npem *npem, u32 inds); |
| 109 | + const struct indication *inds; |
| 110 | + const char *name; |
| 111 | +}; |
| 112 | + |
| 113 | +/** |
| 114 | + * struct npem - NPEM device properties |
| 115 | + * @dev: PCI device this driver is attached to |
| 116 | + * @ops: backend specific callbacks |
| 117 | + * @lock: serializes concurrent access to NPEM device by multiple LED devices |
| 118 | + * @pos: cached offset of NPEM Capability Register in Configuration Space; |
| 119 | + * only used if NPEM registers are accessed directly and not through _DSM |
| 120 | + * @supported_indications: cached bit mask of supported indications; |
| 121 | + * non-indication and reserved bits in the NPEM Capability Register are |
| 122 | + * cleared in this bit mask |
| 123 | + * @active_indications: cached bit mask of active indications; |
| 124 | + * non-indication and reserved bits in the NPEM Control Register are |
| 125 | + * cleared in this bit mask |
| 126 | + * @led_cnt: size of @leds array |
| 127 | + * @leds: array containing LED class devices of all supported LEDs |
| 128 | + */ |
| 129 | +struct npem { |
| 130 | + struct pci_dev *dev; |
| 131 | + const struct npem_ops *ops; |
| 132 | + struct mutex lock; |
| 133 | + u16 pos; |
| 134 | + u32 supported_indications; |
| 135 | + u32 active_indications; |
| 136 | + int led_cnt; |
| 137 | + struct npem_led leds[]; |
| 138 | +}; |
| 139 | + |
| 140 | +static int npem_read_reg(struct npem *npem, u16 reg, u32 *val) |
| 141 | +{ |
| 142 | + int ret = pci_read_config_dword(npem->dev, npem->pos + reg, val); |
| 143 | + |
| 144 | + return pcibios_err_to_errno(ret); |
| 145 | +} |
| 146 | + |
| 147 | +static int npem_write_ctrl(struct npem *npem, u32 reg) |
| 148 | +{ |
| 149 | + int pos = npem->pos + PCI_NPEM_CTRL; |
| 150 | + int ret = pci_write_config_dword(npem->dev, pos, reg); |
| 151 | + |
| 152 | + return pcibios_err_to_errno(ret); |
| 153 | +} |
| 154 | + |
| 155 | +static int npem_get_active_indications(struct npem *npem, u32 *inds) |
| 156 | +{ |
| 157 | + u32 ctrl; |
| 158 | + int ret; |
| 159 | + |
| 160 | + ret = npem_read_reg(npem, PCI_NPEM_CTRL, &ctrl); |
| 161 | + if (ret) |
| 162 | + return ret; |
| 163 | + |
| 164 | + /* If PCI_NPEM_CTRL_ENABLE is not set then no indication should blink */ |
| 165 | + if (!(ctrl & PCI_NPEM_CTRL_ENABLE)) { |
| 166 | + *inds = 0; |
| 167 | + return 0; |
| 168 | + } |
| 169 | + |
| 170 | + *inds = ctrl & npem->supported_indications; |
| 171 | + |
| 172 | + return 0; |
| 173 | +} |
| 174 | + |
| 175 | +static int npem_set_active_indications(struct npem *npem, u32 inds) |
| 176 | +{ |
| 177 | + int ctrl, ret, ret_val; |
| 178 | + u32 cc_status; |
| 179 | + |
| 180 | + lockdep_assert_held(&npem->lock); |
| 181 | + |
| 182 | + /* This bit is always required */ |
| 183 | + ctrl = inds | PCI_NPEM_CTRL_ENABLE; |
| 184 | + |
| 185 | + ret = npem_write_ctrl(npem, ctrl); |
| 186 | + if (ret) |
| 187 | + return ret; |
| 188 | + |
| 189 | + /* |
| 190 | + * For the case where a NPEM command has not completed immediately, |
| 191 | + * it is recommended that software not continuously "spin" on polling |
| 192 | + * the status register, but rather poll under interrupt at a reduced |
| 193 | + * rate; for example at 10 ms intervals. |
| 194 | + * |
| 195 | + * PCIe r6.1 sec 6.28 "Implementation Note: Software Polling of NPEM |
| 196 | + * Command Completed" |
| 197 | + */ |
| 198 | + ret = read_poll_timeout(npem_read_reg, ret_val, |
| 199 | + ret_val || (cc_status & PCI_NPEM_STATUS_CC), |
| 200 | + 10 * USEC_PER_MSEC, USEC_PER_SEC, false, npem, |
| 201 | + PCI_NPEM_STATUS, &cc_status); |
| 202 | + if (ret) |
| 203 | + return ret; |
| 204 | + if (ret_val) |
| 205 | + return ret_val; |
| 206 | + |
| 207 | + /* |
| 208 | + * All writes to control register, including writes that do not change |
| 209 | + * the register value, are NPEM commands and should eventually result |
| 210 | + * in a command completion indication in the NPEM Status Register. |
| 211 | + * |
| 212 | + * PCIe Base Specification r6.1 sec 7.9.19.3 |
| 213 | + * |
| 214 | + * Register may not be updated, or other conflicting bits may be |
| 215 | + * cleared. Spec is not strict here. Read NPEM Control register after |
| 216 | + * write to keep cache in-sync. |
| 217 | + */ |
| 218 | + return npem_get_active_indications(npem, &npem->active_indications); |
| 219 | +} |
| 220 | + |
| 221 | +static const struct npem_ops npem_ops = { |
| 222 | + .get_active_indications = npem_get_active_indications, |
| 223 | + .set_active_indications = npem_set_active_indications, |
| 224 | + .name = "Native PCIe Enclosure Management", |
| 225 | + .inds = npem_indications, |
| 226 | +}; |
| 227 | + |
| 228 | +#define DSM_GUID GUID_INIT(0x5d524d9d, 0xfff9, 0x4d4b, 0x8c, 0xb7, 0x74, 0x7e,\ |
| 229 | + 0xd5, 0x1e, 0x19, 0x4d) |
| 230 | +#define GET_SUPPORTED_STATES_DSM 1 |
| 231 | +#define GET_STATE_DSM 2 |
| 232 | +#define SET_STATE_DSM 3 |
| 233 | + |
| 234 | +static const guid_t dsm_guid = DSM_GUID; |
| 235 | + |
| 236 | +static bool npem_has_dsm(struct pci_dev *pdev) |
| 237 | +{ |
| 238 | + acpi_handle handle; |
| 239 | + |
| 240 | + handle = ACPI_HANDLE(&pdev->dev); |
| 241 | + if (!handle) |
| 242 | + return false; |
| 243 | + |
| 244 | + return acpi_check_dsm(handle, &dsm_guid, 0x1, |
| 245 | + BIT(GET_SUPPORTED_STATES_DSM) | |
| 246 | + BIT(GET_STATE_DSM) | BIT(SET_STATE_DSM)); |
| 247 | +} |
| 248 | + |
| 249 | +/* |
| 250 | + * The status of each indicator is cached on first brightness_ get/set time |
| 251 | + * and updated at write time. brightness_get() is only responsible for |
| 252 | + * reflecting the last written/cached value. |
| 253 | + */ |
| 254 | +static enum led_brightness brightness_get(struct led_classdev *led) |
| 255 | +{ |
| 256 | + struct npem_led *nled = container_of(led, struct npem_led, led); |
| 257 | + struct npem *npem = nled->npem; |
| 258 | + int ret, val = 0; |
| 259 | + |
| 260 | + ret = mutex_lock_interruptible(&npem->lock); |
| 261 | + if (ret) |
| 262 | + return ret; |
| 263 | + |
| 264 | + if (npem->active_indications & nled->indication->bit) |
| 265 | + val = 1; |
| 266 | + |
| 267 | + mutex_unlock(&npem->lock); |
| 268 | + return val; |
| 269 | +} |
| 270 | + |
| 271 | +static int brightness_set(struct led_classdev *led, |
| 272 | + enum led_brightness brightness) |
| 273 | +{ |
| 274 | + struct npem_led *nled = container_of(led, struct npem_led, led); |
| 275 | + struct npem *npem = nled->npem; |
| 276 | + u32 indications; |
| 277 | + int ret; |
| 278 | + |
| 279 | + ret = mutex_lock_interruptible(&npem->lock); |
| 280 | + if (ret) |
| 281 | + return ret; |
| 282 | + |
| 283 | + if (brightness == 0) |
| 284 | + indications = npem->active_indications & ~(nled->indication->bit); |
| 285 | + else |
| 286 | + indications = npem->active_indications | nled->indication->bit; |
| 287 | + |
| 288 | + ret = npem->ops->set_active_indications(npem, indications); |
| 289 | + |
| 290 | + mutex_unlock(&npem->lock); |
| 291 | + return ret; |
| 292 | +} |
| 293 | + |
| 294 | +static void npem_free(struct npem *npem) |
| 295 | +{ |
| 296 | + struct npem_led *nled; |
| 297 | + int cnt; |
| 298 | + |
| 299 | + if (!npem) |
| 300 | + return; |
| 301 | + |
| 302 | + for (cnt = 0; cnt < npem->led_cnt; cnt++) { |
| 303 | + nled = &npem->leds[cnt]; |
| 304 | + |
| 305 | + if (nled->name[0]) |
| 306 | + led_classdev_unregister(&nled->led); |
| 307 | + } |
| 308 | + |
| 309 | + mutex_destroy(&npem->lock); |
| 310 | + kfree(npem); |
| 311 | +} |
| 312 | + |
| 313 | +static int pci_npem_set_led_classdev(struct npem *npem, struct npem_led *nled) |
| 314 | +{ |
| 315 | + struct led_classdev *led = &nled->led; |
| 316 | + struct led_init_data init_data = {}; |
| 317 | + char *name = nled->name; |
| 318 | + int ret; |
| 319 | + |
| 320 | + init_data.devicename = pci_name(npem->dev); |
| 321 | + init_data.default_label = nled->indication->name; |
| 322 | + |
| 323 | + ret = led_compose_name(&npem->dev->dev, &init_data, name); |
| 324 | + if (ret) |
| 325 | + return ret; |
| 326 | + |
| 327 | + led->name = name; |
| 328 | + led->brightness_set_blocking = brightness_set; |
| 329 | + led->brightness_get = brightness_get; |
| 330 | + led->max_brightness = 1; |
| 331 | + led->default_trigger = "none"; |
| 332 | + led->flags = 0; |
| 333 | + |
| 334 | + ret = led_classdev_register(&npem->dev->dev, led); |
| 335 | + if (ret) |
| 336 | + /* Clear the name to indicate that it is not registered. */ |
| 337 | + name[0] = 0; |
| 338 | + return ret; |
| 339 | +} |
| 340 | + |
| 341 | +static int pci_npem_init(struct pci_dev *dev, const struct npem_ops *ops, |
| 342 | + int pos, u32 caps) |
| 343 | +{ |
| 344 | + u32 supported = reg_to_indications(caps, ops->inds); |
| 345 | + int supported_cnt = hweight32(supported); |
| 346 | + const struct indication *indication; |
| 347 | + struct npem_led *nled; |
| 348 | + struct npem *npem; |
| 349 | + int led_idx = 0; |
| 350 | + int ret; |
| 351 | + |
| 352 | + npem = kzalloc(struct_size(npem, leds, supported_cnt), GFP_KERNEL); |
| 353 | + if (!npem) |
| 354 | + return -ENOMEM; |
| 355 | + |
| 356 | + npem->supported_indications = supported; |
| 357 | + npem->led_cnt = supported_cnt; |
| 358 | + npem->pos = pos; |
| 359 | + npem->dev = dev; |
| 360 | + npem->ops = ops; |
| 361 | + |
| 362 | + ret = npem->ops->get_active_indications(npem, |
| 363 | + &npem->active_indications); |
| 364 | + if (ret) |
| 365 | + return ret; |
| 366 | + |
| 367 | + mutex_init(&npem->lock); |
| 368 | + |
| 369 | + for_each_indication(indication, npem_indications) { |
| 370 | + if (!(npem->supported_indications & indication->bit)) |
| 371 | + continue; |
| 372 | + |
| 373 | + nled = &npem->leds[led_idx++]; |
| 374 | + nled->indication = indication; |
| 375 | + nled->npem = npem; |
| 376 | + |
| 377 | + ret = pci_npem_set_led_classdev(npem, nled); |
| 378 | + if (ret) { |
| 379 | + npem_free(npem); |
| 380 | + return ret; |
| 381 | + } |
| 382 | + } |
| 383 | + |
| 384 | + dev->npem = npem; |
| 385 | + return 0; |
| 386 | +} |
| 387 | + |
| 388 | +void pci_npem_remove(struct pci_dev *dev) |
| 389 | +{ |
| 390 | + npem_free(dev->npem); |
| 391 | +} |
| 392 | + |
| 393 | +void pci_npem_create(struct pci_dev *dev) |
| 394 | +{ |
| 395 | + const struct npem_ops *ops = &npem_ops; |
| 396 | + int pos = 0, ret; |
| 397 | + u32 cap; |
| 398 | + |
| 399 | + if (npem_has_dsm(dev)) { |
| 400 | + /* |
| 401 | + * OS should use the DSM for LED control if it is available |
| 402 | + * PCI Firmware Spec r3.3 sec 4.7. |
| 403 | + */ |
| 404 | + pci_info(dev, "Not configuring %s because _DSM is present\n", |
| 405 | + ops->name); |
| 406 | + return; |
| 407 | + } else { |
| 408 | + pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_NPEM); |
| 409 | + if (pos == 0) |
| 410 | + return; |
| 411 | + |
| 412 | + if (pci_read_config_dword(dev, pos + PCI_NPEM_CAP, &cap) != 0 || |
| 413 | + (cap & PCI_NPEM_CAP_CAPABLE) == 0) |
| 414 | + return; |
| 415 | + } |
| 416 | + |
| 417 | + pci_info(dev, "Configuring %s\n", ops->name); |
| 418 | + |
| 419 | + ret = pci_npem_init(dev, ops, pos, cap); |
| 420 | + if (ret) |
| 421 | + pci_err(dev, "Failed to register %s, err: %d\n", ops->name, |
| 422 | + ret); |
| 423 | +} |