diff options
Diffstat (limited to 'drivers/acpi/pci_root.c')
| -rw-r--r-- | drivers/acpi/pci_root.c | 238 | 
1 files changed, 209 insertions, 29 deletions
diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index b3b507f20e87..d57cf8454b93 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -140,6 +140,17 @@ static struct pci_osc_bit_struct pci_osc_control_bit[] = {  	{ OSC_PCI_EXPRESS_DPC_CONTROL, "DPC" },  }; +static struct pci_osc_bit_struct cxl_osc_support_bit[] = { +	{ OSC_CXL_1_1_PORT_REG_ACCESS_SUPPORT, "CXL11PortRegAccess" }, +	{ OSC_CXL_2_0_PORT_DEV_REG_ACCESS_SUPPORT, "CXL20PortDevRegAccess" }, +	{ OSC_CXL_PROTOCOL_ERR_REPORTING_SUPPORT, "CXLProtocolErrorReporting" }, +	{ OSC_CXL_NATIVE_HP_SUPPORT, "CXLNativeHotPlug" }, +}; + +static struct pci_osc_bit_struct cxl_osc_control_bit[] = { +	{ OSC_CXL_ERROR_REPORTING_CONTROL, "CXLMemErrorReporting" }, +}; +  static void decode_osc_bits(struct acpi_pci_root *root, char *msg, u32 word,  			    struct pci_osc_bit_struct *table, int size)  { @@ -168,33 +179,73 @@ static void decode_osc_control(struct acpi_pci_root *root, char *msg, u32 word)  			ARRAY_SIZE(pci_osc_control_bit));  } +static void decode_cxl_osc_support(struct acpi_pci_root *root, char *msg, u32 word) +{ +	decode_osc_bits(root, msg, word, cxl_osc_support_bit, +			ARRAY_SIZE(cxl_osc_support_bit)); +} + +static void decode_cxl_osc_control(struct acpi_pci_root *root, char *msg, u32 word) +{ +	decode_osc_bits(root, msg, word, cxl_osc_control_bit, +			ARRAY_SIZE(cxl_osc_control_bit)); +} + +static inline bool is_pcie(struct acpi_pci_root *root) +{ +	return root->bridge_type == ACPI_BRIDGE_TYPE_PCIE; +} + +static inline bool is_cxl(struct acpi_pci_root *root) +{ +	return root->bridge_type == ACPI_BRIDGE_TYPE_CXL; +} +  static u8 pci_osc_uuid_str[] = "33DB4D5B-1FF7-401C-9657-7441C03DD766"; +static u8 cxl_osc_uuid_str[] = "68F2D50B-C469-4d8A-BD3D-941A103FD3FC"; -static acpi_status acpi_pci_run_osc(acpi_handle handle, -				    const u32 *capbuf, u32 *retval) +static char *to_uuid(struct acpi_pci_root *root) +{ +	if (is_cxl(root)) +		return cxl_osc_uuid_str; +	return pci_osc_uuid_str; +} + +static int cap_length(struct acpi_pci_root *root) +{ +	if (is_cxl(root)) +		return sizeof(u32) * OSC_CXL_CAPABILITY_DWORDS; +	return sizeof(u32) * OSC_PCI_CAPABILITY_DWORDS; +} + +static acpi_status acpi_pci_run_osc(struct acpi_pci_root *root, +				    const u32 *capbuf, u32 *pci_control, +				    u32 *cxl_control)  {  	struct acpi_osc_context context = { -		.uuid_str = pci_osc_uuid_str, +		.uuid_str = to_uuid(root),  		.rev = 1, -		.cap.length = 12, +		.cap.length = cap_length(root),  		.cap.pointer = (void *)capbuf,  	};  	acpi_status status; -	status = acpi_run_osc(handle, &context); +	status = acpi_run_osc(root->device->handle, &context);  	if (ACPI_SUCCESS(status)) { -		*retval = *((u32 *)(context.ret.pointer + 8)); +		*pci_control = acpi_osc_ctx_get_pci_control(&context); +		if (is_cxl(root)) +			*cxl_control = acpi_osc_ctx_get_cxl_control(&context);  		kfree(context.ret.pointer);  	}  	return status;  } -static acpi_status acpi_pci_query_osc(struct acpi_pci_root *root, -					u32 support, -					u32 *control) +static acpi_status acpi_pci_query_osc(struct acpi_pci_root *root, u32 support, +				      u32 *control, u32 cxl_support, +				      u32 *cxl_control)  {  	acpi_status status; -	u32 result, capbuf[3]; +	u32 pci_result, cxl_result, capbuf[OSC_CXL_CAPABILITY_DWORDS];  	support |= root->osc_support_set; @@ -202,10 +253,28 @@ static acpi_status acpi_pci_query_osc(struct acpi_pci_root *root,  	capbuf[OSC_SUPPORT_DWORD] = support;  	capbuf[OSC_CONTROL_DWORD] = *control | root->osc_control_set; -	status = acpi_pci_run_osc(root->device->handle, capbuf, &result); +	if (is_cxl(root)) { +		cxl_support |= root->osc_ext_support_set; +		capbuf[OSC_EXT_SUPPORT_DWORD] = cxl_support; +		capbuf[OSC_EXT_CONTROL_DWORD] = *cxl_control | root->osc_ext_control_set; +	} + +retry: +	status = acpi_pci_run_osc(root, capbuf, &pci_result, &cxl_result);  	if (ACPI_SUCCESS(status)) {  		root->osc_support_set = support; -		*control = result; +		*control = pci_result; +		if (is_cxl(root)) { +			root->osc_ext_support_set = cxl_support; +			*cxl_control = cxl_result; +		} +	} else if (is_cxl(root)) { +		/* +		 * CXL _OSC is optional on CXL 1.1 hosts. Fall back to PCIe _OSC +		 * upon any failure using CXL _OSC. +		 */ +		root->bridge_type = ACPI_BRIDGE_TYPE_PCIE; +		goto retry;  	}  	return status;  } @@ -321,6 +390,8 @@ EXPORT_SYMBOL_GPL(acpi_get_pci_dev);   * @handle: ACPI handle of a PCI root bridge (or PCIe Root Complex).   * @mask: Mask of _OSC bits to request control of, place to store control mask.   * @support: _OSC supported capability. + * @cxl_mask: Mask of CXL _OSC control bits, place to store control mask. + * @cxl_support: CXL _OSC supported capability.   *   * Run _OSC query for @mask and if that is successful, compare the returned   * mask of control bits with @req.  If all of the @req bits are set in the @@ -331,12 +402,14 @@ EXPORT_SYMBOL_GPL(acpi_get_pci_dev);   * _OSC bits the BIOS has granted control of, but its contents are meaningless   * on failure.   **/ -static acpi_status acpi_pci_osc_control_set(acpi_handle handle, u32 *mask, u32 support) +static acpi_status acpi_pci_osc_control_set(acpi_handle handle, u32 *mask, +					    u32 support, u32 *cxl_mask, +					    u32 cxl_support)  {  	u32 req = OSC_PCI_EXPRESS_CAPABILITY_CONTROL;  	struct acpi_pci_root *root;  	acpi_status status; -	u32 ctrl, capbuf[3]; +	u32 ctrl, cxl_ctrl = 0, capbuf[OSC_CXL_CAPABILITY_DWORDS];  	if (!mask)  		return AE_BAD_PARAMETER; @@ -348,20 +421,42 @@ static acpi_status acpi_pci_osc_control_set(acpi_handle handle, u32 *mask, u32 s  	ctrl   = *mask;  	*mask |= root->osc_control_set; +	if (is_cxl(root)) { +		cxl_ctrl = *cxl_mask; +		*cxl_mask |= root->osc_ext_control_set; +	} +  	/* Need to check the available controls bits before requesting them. */  	do { -		status = acpi_pci_query_osc(root, support, mask); +		u32 pci_missing = 0, cxl_missing = 0; + +		status = acpi_pci_query_osc(root, support, mask, cxl_support, +					    cxl_mask);  		if (ACPI_FAILURE(status))  			return status; -		if (ctrl == *mask) -			break; -		decode_osc_control(root, "platform does not support", -				   ctrl & ~(*mask)); +		if (is_cxl(root)) { +			if (ctrl == *mask && cxl_ctrl == *cxl_mask) +				break; +			pci_missing = ctrl & ~(*mask); +			cxl_missing = cxl_ctrl & ~(*cxl_mask); +		} else { +			if (ctrl == *mask) +				break; +			pci_missing = ctrl & ~(*mask); +		} +		if (pci_missing) +			decode_osc_control(root, "platform does not support", +					   pci_missing); +		if (cxl_missing) +			decode_cxl_osc_control(root, "CXL platform does not support", +					   cxl_missing);  		ctrl = *mask; -	} while (*mask); +		cxl_ctrl = *cxl_mask; +	} while (*mask || *cxl_mask);  	/* No need to request _OSC if the control was already granted. */ -	if ((root->osc_control_set & ctrl) == ctrl) +	if ((root->osc_control_set & ctrl) == ctrl && +	    (root->osc_ext_control_set & cxl_ctrl) == cxl_ctrl)  		return AE_OK;  	if ((ctrl & req) != req) { @@ -373,11 +468,17 @@ static acpi_status acpi_pci_osc_control_set(acpi_handle handle, u32 *mask, u32 s  	capbuf[OSC_QUERY_DWORD] = 0;  	capbuf[OSC_SUPPORT_DWORD] = root->osc_support_set;  	capbuf[OSC_CONTROL_DWORD] = ctrl; -	status = acpi_pci_run_osc(handle, capbuf, mask); +	if (is_cxl(root)) { +		capbuf[OSC_EXT_SUPPORT_DWORD] = root->osc_ext_support_set; +		capbuf[OSC_EXT_CONTROL_DWORD] = cxl_ctrl; +	} + +	status = acpi_pci_run_osc(root, capbuf, mask, cxl_mask);  	if (ACPI_FAILURE(status))  		return status;  	root->osc_control_set = *mask; +	root->osc_ext_control_set = *cxl_mask;  	return AE_OK;  } @@ -403,6 +504,53 @@ static u32 calculate_support(void)  	return support;  } +/* + * Background on hotplug support, and making it depend on only + * CONFIG_HOTPLUG_PCI_PCIE vs. also considering CONFIG_MEMORY_HOTPLUG: + * + * CONFIG_ACPI_HOTPLUG_MEMORY does depend on CONFIG_MEMORY_HOTPLUG, but + * there is no existing _OSC for memory hotplug support. The reason is that + * ACPI memory hotplug requires the OS to acknowledge / coordinate with + * memory plug events via a scan handler. On the CXL side the equivalent + * would be if Linux supported the Mechanical Retention Lock [1], or + * otherwise had some coordination for the driver of a PCI device + * undergoing hotplug to be consulted on whether the hotplug should + * proceed or not. + * + * The concern is that if Linux says no to supporting CXL hotplug then + * the BIOS may say no to giving the OS hotplug control of any other PCIe + * device. So the question here is not whether hotplug is enabled, it's + * whether it is handled natively by the at all OS, and if + * CONFIG_HOTPLUG_PCI_PCIE is enabled then the answer is "yes". + * + * Otherwise, the plan for CXL coordinated remove, since the kernel does + * not support blocking hotplug, is to require the memory device to be + * disabled before hotplug is attempted. When CONFIG_MEMORY_HOTPLUG is + * disabled that step will fail and the remove attempt cancelled by the + * user. If that is not honored and the card is removed anyway then it + * does not matter if CONFIG_MEMORY_HOTPLUG is enabled or not, it will + * cause a crash and other badness. + * + * Therefore, just say yes to CXL hotplug and require removal to + * be coordinated by userspace unless and until the kernel grows better + * mechanisms for doing "managed" removal of devices in consultation with + * the driver. + * + * [1]: https://lore.kernel.org/all/20201122014203.4706-1-ashok.raj@intel.com/ + */ +static u32 calculate_cxl_support(void) +{ +	u32 support; + +	support = OSC_CXL_2_0_PORT_DEV_REG_ACCESS_SUPPORT; +	if (pci_aer_available()) +		support |= OSC_CXL_PROTOCOL_ERR_REPORTING_SUPPORT; +	if (IS_ENABLED(CONFIG_HOTPLUG_PCI_PCIE)) +		support |= OSC_CXL_NATIVE_HP_SUPPORT; + +	return support; +} +  static u32 calculate_control(void)  {  	u32 control; @@ -434,6 +582,16 @@ static u32 calculate_control(void)  	return control;  } +static u32 calculate_cxl_control(void) +{ +	u32 control = 0; + +	if (IS_ENABLED(CONFIG_MEMORY_FAILURE)) +		control |= OSC_CXL_ERROR_REPORTING_CONTROL; + +	return control; +} +  static bool os_control_query_checks(struct acpi_pci_root *root, u32 support)  {  	struct acpi_device *device = root->device; @@ -452,10 +610,10 @@ static bool os_control_query_checks(struct acpi_pci_root *root, u32 support)  	return true;  } -static void negotiate_os_control(struct acpi_pci_root *root, int *no_aspm, -				 bool is_pcie) +static void negotiate_os_control(struct acpi_pci_root *root, int *no_aspm)  {  	u32 support, control = 0, requested = 0; +	u32 cxl_support = 0, cxl_control = 0, cxl_requested = 0;  	acpi_status status;  	struct acpi_device *device = root->device;  	acpi_handle handle = device->handle; @@ -479,10 +637,20 @@ static void negotiate_os_control(struct acpi_pci_root *root, int *no_aspm,  	if (os_control_query_checks(root, support))  		requested = control = calculate_control(); -	status = acpi_pci_osc_control_set(handle, &control, support); +	if (is_cxl(root)) { +		cxl_support = calculate_cxl_support(); +		decode_cxl_osc_support(root, "OS supports", cxl_support); +		cxl_requested = cxl_control = calculate_cxl_control(); +	} + +	status = acpi_pci_osc_control_set(handle, &control, support, +					  &cxl_control, cxl_support);  	if (ACPI_SUCCESS(status)) {  		if (control)  			decode_osc_control(root, "OS now controls", control); +		if (cxl_control) +			decode_cxl_osc_control(root, "OS now controls", +					   cxl_control);  		if (acpi_gbl_FADT.boot_flags & ACPI_FADT_NO_ASPM) {  			/* @@ -504,13 +672,18 @@ static void negotiate_os_control(struct acpi_pci_root *root, int *no_aspm,  		*no_aspm = 1;  		/* _OSC is optional for PCI host bridges */ -		if ((status == AE_NOT_FOUND) && !is_pcie) +		if (status == AE_NOT_FOUND && !is_pcie(root))  			return;  		if (control) {  			decode_osc_control(root, "OS requested", requested);  			decode_osc_control(root, "platform willing to grant", control);  		} +		if (cxl_control) { +			decode_cxl_osc_control(root, "OS requested", cxl_requested); +			decode_cxl_osc_control(root, "platform willing to grant", +					   cxl_control); +		}  		dev_info(&device->dev, "_OSC: platform retains control of PCIe features (%s)\n",  			 acpi_format_exception(status)); @@ -527,7 +700,7 @@ static int acpi_pci_root_add(struct acpi_device *device,  	acpi_handle handle = device->handle;  	int no_aspm = 0;  	bool hotadd = system_state == SYSTEM_RUNNING; -	bool is_pcie; +	const char *acpi_hid;  	root = kzalloc(sizeof(struct acpi_pci_root), GFP_KERNEL);  	if (!root) @@ -585,8 +758,15 @@ static int acpi_pci_root_add(struct acpi_device *device,  	root->mcfg_addr = acpi_pci_root_get_mcfg_addr(handle); -	is_pcie = strcmp(acpi_device_hid(device), "PNP0A08") == 0; -	negotiate_os_control(root, &no_aspm, is_pcie); +	acpi_hid = acpi_device_hid(root->device); +	if (strcmp(acpi_hid, "PNP0A08") == 0) +		root->bridge_type = ACPI_BRIDGE_TYPE_PCIE; +	else if (strcmp(acpi_hid, "ACPI0016") == 0) +		root->bridge_type = ACPI_BRIDGE_TYPE_CXL; +	else +		dev_dbg(&device->dev, "Assuming non-PCIe host bridge\n"); + +	negotiate_os_control(root, &no_aspm);  	/*  	 * TBD: Need PCI interface for enumeration/configuration of roots.  | 
