From 5f5e4890d57a8af5da72c9d73a4efa9bad43a7a3 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 27 Sep 2018 16:57:05 -0500 Subject: ACPI / property: Allow multiple property compatible _DSD entries It is possible to have _DSD entries where the data is compatible with device properties format but are using different GUID for various reasons. In addition to that there can be many such _DSD entries for a single device such as for PCIe root port used to host a Thunderbolt hierarchy: Scope (\_SB.PCI0.RP21) { Name (_DSD, Package () { ToUUID ("6211e2c0-58a3-4af3-90e1-927a4e0c55a4"), Package () { Package () {"HotPlugSupportInD3", 1} }, ToUUID ("efcc06cc-73ac-4bc3-bff0-76143807c389"), Package () { Package () {"ExternalFacingPort", 1}, Package () {"UID", 0 } } }) } More information about these new _DSD entries can be found in: https://docs.microsoft.com/en-us/windows-hardware/drivers/pci/dsd-for-pcie-root-ports To make these available for drivers via unified device property APIs, modify ACPI property core so that it supports multiple _DSD entries organized in a linked list. We also store GUID of each _DSD entry in struct acpi_device_properties in case there is need to differentiate between entries. The supported GUIDs are then listed in prp_guids array. Signed-off-by: Mika Westerberg Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki Acked-by: Sakari Ailus --- drivers/acpi/property.c | 94 ++++++++++++++++++++++++++++++++++-------------- drivers/acpi/x86/apple.c | 2 +- 2 files changed, 69 insertions(+), 27 deletions(-) (limited to 'drivers/acpi') diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index 693cf05b0cc4..90ba9371bae6 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c @@ -24,11 +24,12 @@ static int acpi_data_get_property_array(const struct acpi_device_data *data, acpi_object_type type, const union acpi_object **obj); -/* ACPI _DSD device properties GUID: daffd814-6eba-4d8c-8a91-bc9bbf4aa301 */ -static const guid_t prp_guid = +static const guid_t prp_guids[] = { + /* ACPI _DSD device properties GUID: daffd814-6eba-4d8c-8a91-bc9bbf4aa301 */ GUID_INIT(0xdaffd814, 0x6eba, 0x4d8c, - 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01); -/* ACPI _DSD data subnodes GUID: dbb8e3e6-5886-4ba6-8795-1319f52a966b */ + 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01), +}; + static const guid_t ads_guid = GUID_INIT(0xdbb8e3e6, 0x5886, 0x4ba6, 0x87, 0x95, 0x13, 0x19, 0xf5, 0x2a, 0x96, 0x6b); @@ -56,6 +57,7 @@ static bool acpi_nondev_subnode_extract(const union acpi_object *desc, dn->name = link->package.elements[0].string.pointer; dn->fwnode.ops = &acpi_data_fwnode_ops; dn->parent = parent; + INIT_LIST_HEAD(&dn->data.properties); INIT_LIST_HEAD(&dn->data.subnodes); result = acpi_extract_properties(desc, &dn->data); @@ -288,6 +290,35 @@ static void acpi_init_of_compatible(struct acpi_device *adev) adev->flags.of_compatible_ok = 1; } +static bool acpi_is_property_guid(const guid_t *guid) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(prp_guids); i++) { + if (guid_equal(guid, &prp_guids[i])) + return true; + } + + return false; +} + +struct acpi_device_properties * +acpi_data_add_props(struct acpi_device_data *data, const guid_t *guid, + const union acpi_object *properties) +{ + struct acpi_device_properties *props; + + props = kzalloc(sizeof(*props), GFP_KERNEL); + if (props) { + INIT_LIST_HEAD(&props->list); + props->guid = guid; + props->properties = properties; + list_add_tail(&props->list, &data->properties); + } + + return props; +} + static bool acpi_extract_properties(const union acpi_object *desc, struct acpi_device_data *data) { @@ -312,7 +343,7 @@ static bool acpi_extract_properties(const union acpi_object *desc, properties->type != ACPI_TYPE_PACKAGE) break; - if (!guid_equal((guid_t *)guid->buffer.pointer, &prp_guid)) + if (!acpi_is_property_guid((guid_t *)guid->buffer.pointer)) continue; /* @@ -320,13 +351,13 @@ static bool acpi_extract_properties(const union acpi_object *desc, * package immediately following it. */ if (!acpi_properties_format_valid(properties)) - break; + continue; - data->properties = properties; - return true; + acpi_data_add_props(data, (const guid_t *)guid->buffer.pointer, + properties); } - return false; + return !list_empty(&data->properties); } void acpi_init_properties(struct acpi_device *adev) @@ -336,6 +367,7 @@ void acpi_init_properties(struct acpi_device *adev) acpi_status status; bool acpi_of = false; + INIT_LIST_HEAD(&adev->data.properties); INIT_LIST_HEAD(&adev->data.subnodes); if (!adev->handle) @@ -398,11 +430,16 @@ static void acpi_destroy_nondev_subnodes(struct list_head *list) void acpi_free_properties(struct acpi_device *adev) { + struct acpi_device_properties *props, *tmp; + acpi_destroy_nondev_subnodes(&adev->data.subnodes); ACPI_FREE((void *)adev->data.pointer); adev->data.of_compatible = NULL; adev->data.pointer = NULL; - adev->data.properties = NULL; + list_for_each_entry_safe(props, tmp, &adev->data.properties, list) { + list_del(&props->list); + kfree(props); + } } /** @@ -427,32 +464,37 @@ static int acpi_data_get_property(const struct acpi_device_data *data, const char *name, acpi_object_type type, const union acpi_object **obj) { - const union acpi_object *properties; - int i; + const struct acpi_device_properties *props; if (!data || !name) return -EINVAL; - if (!data->pointer || !data->properties) + if (!data->pointer || list_empty(&data->properties)) return -EINVAL; - properties = data->properties; - for (i = 0; i < properties->package.count; i++) { - const union acpi_object *propname, *propvalue; - const union acpi_object *property; + list_for_each_entry(props, &data->properties, list) { + const union acpi_object *properties; + unsigned int i; - property = &properties->package.elements[i]; + properties = props->properties; + for (i = 0; i < properties->package.count; i++) { + const union acpi_object *propname, *propvalue; + const union acpi_object *property; - propname = &property->package.elements[0]; - propvalue = &property->package.elements[1]; + property = &properties->package.elements[i]; - if (!strcmp(name, propname->string.pointer)) { - if (type != ACPI_TYPE_ANY && propvalue->type != type) - return -EPROTO; - if (obj) - *obj = propvalue; + propname = &property->package.elements[0]; + propvalue = &property->package.elements[1]; - return 0; + if (!strcmp(name, propname->string.pointer)) { + if (type != ACPI_TYPE_ANY && + propvalue->type != type) + return -EPROTO; + if (obj) + *obj = propvalue; + + return 0; + } } } return -EINVAL; diff --git a/drivers/acpi/x86/apple.c b/drivers/acpi/x86/apple.c index 51b4cf9f25da..130df1c8ed7d 100644 --- a/drivers/acpi/x86/apple.c +++ b/drivers/acpi/x86/apple.c @@ -132,8 +132,8 @@ void acpi_extract_apple_properties(struct acpi_device *adev) } WARN_ON(free_space != (void *)newprops + newsize); - adev->data.properties = newprops; adev->data.pointer = newprops; + acpi_data_add_props(&adev->data, &apple_prp_guid, newprops); out_free: ACPI_FREE(props); -- cgit v1.2.3 From 26ad34d510a87fc65caeb48fa85cce58d2477a88 Mon Sep 17 00:00:00 2001 From: Mika Westerberg Date: Thu, 27 Sep 2018 16:57:14 -0500 Subject: PCI / ACPI: Whitelist D3 for more PCIe hotplug ports In order to have better power management for Thunderbolt PCIe chains, Windows enables power management for native PCIe hotplug ports if there is the following ACPI _DSD attached to the root port: Name (_DSD, Package () { ToUUID ("6211e2c0-58a3-4af3-90e1-927a4e0c55a4"), Package () { Package () {"HotPlugSupportInD3", 1} } }) This is also documented in: https://docs.microsoft.com/en-us/windows-hardware/drivers/pci/dsd-for-pcie-root-ports#identifying-pcie-root-ports-supporting-hot-plug-in-d3 Do the same in Linux by introducing new firmware PM callback (->bridge_d3()) and then implement it for ACPI based systems so that the above property is checked. There is one catch, though. The initial pci_dev->bridge_d3 is set before the root port has ACPI companion bound (the device is not added to the PCI bus either) so we need to look up the ACPI companion manually in that case in acpi_pci_bridge_d3(). Signed-off-by: Mika Westerberg Signed-off-by: Bjorn Helgaas Reviewed-by: Rafael J. Wysocki --- drivers/acpi/property.c | 3 +++ drivers/pci/pci-acpi.c | 41 +++++++++++++++++++++++++++++++++++++++++ drivers/pci/pci.c | 9 +++++++++ drivers/pci/pci.h | 3 +++ 4 files changed, 56 insertions(+) (limited to 'drivers/acpi') diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index 90ba9371bae6..8c7c4583b52d 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c @@ -28,6 +28,9 @@ static const guid_t prp_guids[] = { /* ACPI _DSD device properties GUID: daffd814-6eba-4d8c-8a91-bc9bbf4aa301 */ GUID_INIT(0xdaffd814, 0x6eba, 0x4d8c, 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01), + /* Hotplug in D3 GUID: 6211e2c0-58a3-4af3-90e1-927a4e0c55a4 */ + GUID_INIT(0x6211e2c0, 0x58a3, 0x4af3, + 0x90, 0xe1, 0x92, 0x7a, 0x4e, 0x0c, 0x55, 0xa4), }; static const guid_t ads_guid = diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index f8436d1c4d45..c8d0549580f4 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -519,6 +519,46 @@ static pci_power_t acpi_pci_choose_state(struct pci_dev *pdev) return PCI_POWER_ERROR; } +static struct acpi_device *acpi_pci_find_companion(struct device *dev); + +static bool acpi_pci_bridge_d3(struct pci_dev *dev) +{ + const struct fwnode_handle *fwnode; + struct acpi_device *adev; + struct pci_dev *root; + u8 val; + + if (!dev->is_hotplug_bridge) + return false; + + /* + * Look for a special _DSD property for the root port and if it + * is set we know the hierarchy behind it supports D3 just fine. + */ + root = pci_find_pcie_root_port(dev); + if (!root) + return false; + + adev = ACPI_COMPANION(&root->dev); + if (root == dev) { + /* + * It is possible that the ACPI companion is not yet bound + * for the root port so look it up manually here. + */ + if (!adev && !pci_dev_is_added(root)) + adev = acpi_pci_find_companion(&root->dev); + } + + if (!adev) + return false; + + fwnode = acpi_fwnode_handle(adev); + if (fwnode_property_read_u8(fwnode, "HotPlugSupportInD3", &val)) + return false; + + return val == 1; +} + static bool acpi_pci_power_manageable(struct pci_dev *dev) { struct acpi_device *adev = ACPI_COMPANION(&dev->dev); @@ -635,6 +675,7 @@ static bool acpi_pci_need_resume(struct pci_dev *dev) } static const struct pci_platform_pm_ops acpi_pci_platform_pm = { + .bridge_d3 = acpi_pci_bridge_d3, .is_manageable = acpi_pci_power_manageable, .set_state = acpi_pci_set_power_state, .get_state = acpi_pci_get_power_state, diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c index 4a1b1f76dc92..e6fcf11f5dcc 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c @@ -793,6 +793,11 @@ static inline bool platform_pci_need_resume(struct pci_dev *dev) return pci_platform_pm ? pci_platform_pm->need_resume(dev) : false; } +static inline bool platform_pci_bridge_d3(struct pci_dev *dev) +{ + return pci_platform_pm ? pci_platform_pm->bridge_d3(dev) : false; +} + /** * pci_raw_set_power_state - Use PCI PM registers to set the power state of * given PCI device @@ -2518,6 +2523,10 @@ bool pci_bridge_d3_possible(struct pci_dev *bridge) if (bridge->is_thunderbolt) return true; + /* Platform might know better if the bridge supports D3 */ + if (platform_pci_bridge_d3(bridge)) + return true; + /* * Hotplug ports handled natively by the OS were not validated * by vendors for runtime D3 at least until 2018 because there diff --git a/drivers/pci/pci.h b/drivers/pci/pci.h index eb3125decffe..672ba4d1659e 100644 --- a/drivers/pci/pci.h +++ b/drivers/pci/pci.h @@ -40,6 +40,8 @@ int pci_bus_error_reset(struct pci_dev *dev); /** * struct pci_platform_pm_ops - Firmware PM callbacks * + * @bridge_d3: Does the bridge allow entering into D3 + * * @is_manageable: returns 'true' if given device is power manageable by the * platform firmware * @@ -61,6 +63,7 @@ int pci_bus_error_reset(struct pci_dev *dev); * these callbacks are mandatory. */ struct pci_platform_pm_ops { + bool (*bridge_d3)(struct pci_dev *dev); bool (*is_manageable)(struct pci_dev *dev); int (*set_state)(struct pci_dev *dev, pci_power_t state); pci_power_t (*get_state)(struct pci_dev *dev); -- cgit v1.2.3