brintos

brintos / linux-shallow public Read only

0
0
Text · 15.3 KiB · 97507e0 Raw
596 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * PCIe Enclosure management driver created for LED interfaces based on4 * 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 is8 * first indication oriented standard with specification.9 *10 * Native PCIe Enclosure Management (NPEM)11 *	PCIe Base Specification r6.1 sec 6.28, 7.9.1912 *13 * _DSM Definitions for PCIe SSD Status LED14 *	 PCI Firmware Specification, r3.3 sec 4.715 *16 * Two backends are supported to manipulate indications: Direct NPEM register17 * 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.21 * Copyright (c) 2023-2024 Intel Corporation22 *	Mariusz Tkaczyk <mariusz.tkaczyk@linux.intel.com>23 */24 25#include <linux/acpi.h>26#include <linux/bitops.h>27#include <linux/errno.h>28#include <linux/iopoll.h>29#include <linux/leds.h>30#include <linux/mutex.h>31#include <linux/pci.h>32#include <linux/pci_regs.h>33#include <linux/types.h>34#include <linux/uleds.h>35 36#include "pci.h"37 38struct indication {39	u32 bit;40	const char *name;41};42 43static const struct indication npem_indications[] = {44	{PCI_NPEM_IND_OK,	"enclosure:ok"},45	{PCI_NPEM_IND_LOCATE,	"enclosure:locate"},46	{PCI_NPEM_IND_FAIL,	"enclosure:fail"},47	{PCI_NPEM_IND_REBUILD,	"enclosure:rebuild"},48	{PCI_NPEM_IND_PFA,	"enclosure:pfa"},49	{PCI_NPEM_IND_HOTSPARE,	"enclosure:hotspare"},50	{PCI_NPEM_IND_ICA,	"enclosure:ica"},51	{PCI_NPEM_IND_IFA,	"enclosure:ifa"},52	{PCI_NPEM_IND_IDT,	"enclosure:idt"},53	{PCI_NPEM_IND_DISABLED,	"enclosure:disabled"},54	{PCI_NPEM_IND_SPEC_0,	"enclosure:specific_0"},55	{PCI_NPEM_IND_SPEC_1,	"enclosure:specific_1"},56	{PCI_NPEM_IND_SPEC_2,	"enclosure:specific_2"},57	{PCI_NPEM_IND_SPEC_3,	"enclosure:specific_3"},58	{PCI_NPEM_IND_SPEC_4,	"enclosure:specific_4"},59	{PCI_NPEM_IND_SPEC_5,	"enclosure:specific_5"},60	{PCI_NPEM_IND_SPEC_6,	"enclosure:specific_6"},61	{PCI_NPEM_IND_SPEC_7,	"enclosure:specific_7"},62	{0,			NULL}63};64 65/* _DSM PCIe SSD LED States correspond to NPEM register values */66static 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 80#define for_each_indication(ind, inds) \81	for (ind = inds; ind->bit; ind++)82 83/*84 * The driver has internal list of supported indications. Ideally, the driver85 * should not touch bits that are not defined and for which LED devices are86 * not exposed but in reality, it needs to turn them off.87 *88 * Otherwise, there will be no possibility to turn off indications turned on by89 * other utilities or turned on by default and it leads to bad user experience.90 *91 * Additionally, it excludes NPEM commands like RESET or ENABLE.92 */93static u32 reg_to_indications(u32 caps, const struct indication *inds)94{95	const struct indication *ind;96	u32 supported_indications = 0;97 98	for_each_indication(ind, inds)99		supported_indications |= ind->bit;100 101	return caps & supported_indications;102}103 104/**105 * struct npem_led - LED details106 * @indication: indication details107 * @npem: NPEM device108 * @name: LED name109 * @led: LED device110 */111struct npem_led {112	const struct indication *indication;113	struct npem *npem;114	char name[LED_MAX_NAME_SIZE];115	struct led_classdev led;116};117 118/**119 * struct npem_ops - backend specific callbacks120 * @get_active_indications: get active indications121 *	npem: NPEM device122 *	inds: response buffer123 * @set_active_indications: set new indications124 *	npem: npem device125 *	inds: bit mask to set126 * @inds: supported indications array, set of indications is backend specific127 * @name: backend name128 */129struct npem_ops {130	int (*get_active_indications)(struct npem *npem, u32 *inds);131	int (*set_active_indications)(struct npem *npem, u32 inds);132	const struct indication *inds;133	const char *name;134};135 136/**137 * struct npem - NPEM device properties138 * @dev: PCI device this driver is attached to139 * @ops: backend specific callbacks140 * @lock: serializes concurrent access to NPEM device by multiple LED devices141 * @pos: cached offset of NPEM Capability Register in Configuration Space;142 *	only used if NPEM registers are accessed directly and not through _DSM143 * @supported_indications: cached bit mask of supported indications;144 *	non-indication and reserved bits in the NPEM Capability Register are145 *	cleared in this bit mask146 * @active_indications: cached bit mask of active indications;147 *	non-indication and reserved bits in the NPEM Control Register are148 *	cleared in this bit mask149 * @active_inds_initialized: whether @active_indications has been initialized;150 *	On Dell platforms, it is required that IPMI drivers are loaded before151 *	the GET_STATE_DSM method is invoked: They use an IPMI OpRegion to152 *	get/set the active LEDs. By initializing @active_indications lazily153 *	(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 LED155 *	access in dmesg. Once they are loaded, the errors go away and LED156 *	access becomes possible.157 * @led_cnt: size of @leds array158 * @leds: array containing LED class devices of all supported LEDs159 */160struct npem {161	struct pci_dev *dev;162	const struct npem_ops *ops;163	struct mutex lock;164	u16 pos;165	u32 supported_indications;166	u32 active_indications;167	unsigned int active_inds_initialized:1;168	int led_cnt;169	struct npem_led leds[];170};171 172static int npem_read_reg(struct npem *npem, u16 reg, u32 *val)173{174	int ret = pci_read_config_dword(npem->dev, npem->pos + reg, val);175 176	return pcibios_err_to_errno(ret);177}178 179static int npem_write_ctrl(struct npem *npem, u32 reg)180{181	int pos = npem->pos + PCI_NPEM_CTRL;182	int ret = pci_write_config_dword(npem->dev, pos, reg);183 184	return pcibios_err_to_errno(ret);185}186 187static int npem_get_active_indications(struct npem *npem, u32 *inds)188{189	u32 ctrl;190	int ret;191 192	ret = npem_read_reg(npem, PCI_NPEM_CTRL, &ctrl);193	if (ret)194		return ret;195 196	/* If PCI_NPEM_CTRL_ENABLE is not set then no indication should blink */197	if (!(ctrl & PCI_NPEM_CTRL_ENABLE)) {198		*inds = 0;199		return 0;200	}201 202	*inds = ctrl & npem->supported_indications;203 204	return 0;205}206 207static int npem_set_active_indications(struct npem *npem, u32 inds)208{209	int ctrl, ret, ret_val;210	u32 cc_status;211 212	lockdep_assert_held(&npem->lock);213 214	/* This bit is always required */215	ctrl = inds | PCI_NPEM_CTRL_ENABLE;216 217	ret = npem_write_ctrl(npem, ctrl);218	if (ret)219		return ret;220 221	/*222	 * For the case where a NPEM command has not completed immediately,223	 * it is recommended that software not continuously "spin" on polling224	 * the status register, but rather poll under interrupt at a reduced225	 * rate; for example at 10 ms intervals.226	 *227	 * PCIe r6.1 sec 6.28 "Implementation Note: Software Polling of NPEM228	 * Command Completed"229	 */230	ret = read_poll_timeout(npem_read_reg, ret_val,231				ret_val || (cc_status & PCI_NPEM_STATUS_CC),232				10 * USEC_PER_MSEC, USEC_PER_SEC, false, npem,233				PCI_NPEM_STATUS, &cc_status);234	if (ret)235		return ret;236	if (ret_val)237		return ret_val;238 239	/*240	 * All writes to control register, including writes that do not change241	 * the register value, are NPEM commands and should eventually result242	 * in a command completion indication in the NPEM Status Register.243	 *244	 * PCIe Base Specification r6.1 sec 7.9.19.3245	 *246	 * Register may not be updated, or other conflicting bits may be247	 * cleared. Spec is not strict here. Read NPEM Control register after248	 * write to keep cache in-sync.249	 */250	return npem_get_active_indications(npem, &npem->active_indications);251}252 253static const struct npem_ops npem_ops = {254	.get_active_indications = npem_get_active_indications,255	.set_active_indications = npem_set_active_indications,256	.name = "Native PCIe Enclosure Management",257	.inds = npem_indications,258};259 260#define DSM_GUID GUID_INIT(0x5d524d9d, 0xfff9, 0x4d4b, 0x8c, 0xb7, 0x74, 0x7e,\261			   0xd5, 0x1e, 0x19, 0x4d)262#define GET_SUPPORTED_STATES_DSM	1263#define GET_STATE_DSM			2264#define SET_STATE_DSM			3265 266static const guid_t dsm_guid = DSM_GUID;267 268static bool npem_has_dsm(struct pci_dev *pdev)269{270	acpi_handle handle;271 272	handle = ACPI_HANDLE(&pdev->dev);273	if (!handle)274		return false;275 276	return acpi_check_dsm(handle, &dsm_guid, 0x1,277			      BIT(GET_SUPPORTED_STATES_DSM) |278			      BIT(GET_STATE_DSM) | BIT(SET_STATE_DSM));279}280 281struct 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 command290 * @pdev: PCI device291 * @dsm_func: DSM LED Function292 * @output: buffer to copy DSM Response293 * @value_to_set: value for SET_STATE_DSM function294 *295 * To not bother caller with ACPI context, the returned _DSM Output Buffer is296 * copied.297 */298static 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 333static 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 348static 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 357static 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 platform369		 * disregarded some or all of the request state changes. OSPM370		 * should check the resulting PCIe SSD Status LED States to see371		 * 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 389static 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 396static 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 414/*415 * The status of each indicator is cached on first brightness_ get/set time416 * and updated at write time.  brightness_get() is only responsible for417 * reflecting the last written/cached value.418 */419static enum led_brightness brightness_get(struct led_classdev *led)420{421	struct npem_led *nled = container_of(led, struct npem_led, led);422	struct npem *npem = nled->npem;423	int ret, val = 0;424 425	ret = mutex_lock_interruptible(&npem->lock);426	if (ret)427		return ret;428 429	ret = npem_initialize_active_indications(npem);430	if (ret)431		goto out;432 433	if (npem->active_indications & nled->indication->bit)434		val = 1;435 436out:437	mutex_unlock(&npem->lock);438	return val;439}440 441static int brightness_set(struct led_classdev *led,442			  enum led_brightness brightness)443{444	struct npem_led *nled = container_of(led, struct npem_led, led);445	struct npem *npem = nled->npem;446	u32 indications;447	int ret;448 449	ret = mutex_lock_interruptible(&npem->lock);450	if (ret)451		return ret;452 453	ret = npem_initialize_active_indications(npem);454	if (ret)455		goto out;456 457	if (brightness == 0)458		indications = npem->active_indications & ~(nled->indication->bit);459	else460		indications = npem->active_indications | nled->indication->bit;461 462	ret = npem->ops->set_active_indications(npem, indications);463 464out:465	mutex_unlock(&npem->lock);466	return ret;467}468 469static void npem_free(struct npem *npem)470{471	struct npem_led *nled;472	int cnt;473 474	if (!npem)475		return;476 477	for (cnt = 0; cnt < npem->led_cnt; cnt++) {478		nled = &npem->leds[cnt];479 480		if (nled->name[0])481			led_classdev_unregister(&nled->led);482	}483 484	mutex_destroy(&npem->lock);485	kfree(npem);486}487 488static int pci_npem_set_led_classdev(struct npem *npem, struct npem_led *nled)489{490	struct led_classdev *led = &nled->led;491	struct led_init_data init_data = {};492	char *name = nled->name;493	int ret;494 495	init_data.devicename = pci_name(npem->dev);496	init_data.default_label = nled->indication->name;497 498	ret = led_compose_name(&npem->dev->dev, &init_data, name);499	if (ret)500		return ret;501 502	led->name = name;503	led->brightness_set_blocking = brightness_set;504	led->brightness_get = brightness_get;505	led->max_brightness = 1;506	led->default_trigger = "none";507	led->flags = 0;508 509	ret = led_classdev_register(&npem->dev->dev, led);510	if (ret)511		/* Clear the name to indicate that it is not registered. */512		name[0] = 0;513	return ret;514}515 516static int pci_npem_init(struct pci_dev *dev, const struct npem_ops *ops,517			 int pos, u32 caps)518{519	u32 supported = reg_to_indications(caps, ops->inds);520	int supported_cnt = hweight32(supported);521	const struct indication *indication;522	struct npem_led *nled;523	struct npem *npem;524	int led_idx = 0;525	int ret;526 527	npem = kzalloc(struct_size(npem, leds, supported_cnt), GFP_KERNEL);528	if (!npem)529		return -ENOMEM;530 531	npem->supported_indications = supported;532	npem->led_cnt = supported_cnt;533	npem->pos = pos;534	npem->dev = dev;535	npem->ops = ops;536 537	mutex_init(&npem->lock);538 539	for_each_indication(indication, npem_indications) {540		if (!(npem->supported_indications & indication->bit))541			continue;542 543		nled = &npem->leds[led_idx++];544		nled->indication = indication;545		nled->npem = npem;546 547		ret = pci_npem_set_led_classdev(npem, nled);548		if (ret) {549			npem_free(npem);550			return ret;551		}552	}553 554	dev->npem = npem;555	return 0;556}557 558void pci_npem_remove(struct pci_dev *dev)559{560	npem_free(dev->npem);561}562 563void pci_npem_create(struct pci_dev *dev)564{565	const struct npem_ops *ops = &npem_ops;566	int pos = 0, ret;567	u32 cap;568 569	if (npem_has_dsm(dev)) {570		/*571		 * OS should use the DSM for LED control if it is available572		 * PCI Firmware Spec r3.3 sec 4.7.573		 */574		ret = dsm_get(dev, GET_SUPPORTED_STATES_DSM, &cap);575		if (ret)576			return;577 578		ops = &dsm_ops;579	} else {580		pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_NPEM);581		if (pos == 0)582			return;583 584		if (pci_read_config_dword(dev, pos + PCI_NPEM_CAP, &cap) != 0 ||585		    (cap & PCI_NPEM_CAP_CAPABLE) == 0)586			return;587	}588 589	pci_info(dev, "Configuring %s\n", ops->name);590 591	ret = pci_npem_init(dev, ops, pos, cap);592	if (ret)593		pci_err(dev, "Failed to register %s, err: %d\n", ops->name,594			ret);595}596