From fb26228bfc4ce3951544848555c0278e2832e618 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Fri, 22 Mar 2019 13:27:21 -0500 Subject: [PATCH 1/6] PCI: rpadlpar: Fix leaked device_node references in add/remove paths The find_dlpar_node() helper returns a device node with its reference incremented. Both the add and remove paths use this helper for find the appropriate node, but fail to release the reference when done. Annotate the find_dlpar_node() helper with a comment about the incremented reference count and call of_node_put() on the obtained device_node in the add and remove paths. Also, fixup a reference leak in the find_vio_slot() helper where we fail to call of_node_put() on the vdevice node after we iterate over its children. Signed-off-by: Tyrel Datwyler Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/rpadlpar_core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/pci/hotplug/rpadlpar_core.c b/drivers/pci/hotplug/rpadlpar_core.c index e2356a9c7088..182f9e3443ee 100644 --- a/drivers/pci/hotplug/rpadlpar_core.c +++ b/drivers/pci/hotplug/rpadlpar_core.c @@ -51,6 +51,7 @@ static struct device_node *find_vio_slot_node(char *drc_name) if (rc == 0) break; } + of_node_put(parent); return dn; } @@ -71,6 +72,7 @@ static struct device_node *find_php_slot_pci_node(char *drc_name, return np; } +/* Returns a device_node with its reference count incremented */ static struct device_node *find_dlpar_node(char *drc_name, int *node_type) { struct device_node *dn; @@ -306,6 +308,7 @@ int dlpar_add_slot(char *drc_name) rc = dlpar_add_phb(drc_name, dn); break; } + of_node_put(dn); printk(KERN_INFO "%s: slot %s added\n", DLPAR_MODULE_NAME, drc_name); exit: @@ -439,6 +442,7 @@ int dlpar_remove_slot(char *drc_name) rc = dlpar_remove_pci_slot(drc_name, dn); break; } + of_node_put(dn); vm_unmap_aliases(); printk(KERN_INFO "%s: slot %s removed\n", DLPAR_MODULE_NAME, drc_name); From 91800660bbe9d4ae7791f0e646e5736180f01fc0 Mon Sep 17 00:00:00 2001 From: Tyrel Datwyler Date: Fri, 22 Mar 2019 13:27:22 -0500 Subject: [PATCH 2/6] PCI: rpaphp: Get/put device node reference during slot alloc/dealloc When allocating the slot structure we store a pointer to the associated device_node. We really should be incrementing the reference count, so add an of_node_get() during slot alloc and an of_node_put() during slot dealloc. Signed-off-by: Tyrel Datwyler Signed-off-by: Bjorn Helgaas --- drivers/pci/hotplug/rpaphp_slot.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/pci/hotplug/rpaphp_slot.c b/drivers/pci/hotplug/rpaphp_slot.c index 5282aa3e33c5..93b4a945c55d 100644 --- a/drivers/pci/hotplug/rpaphp_slot.c +++ b/drivers/pci/hotplug/rpaphp_slot.c @@ -21,6 +21,7 @@ /* free up the memory used by a slot */ void dealloc_slot_struct(struct slot *slot) { + of_node_put(slot->dn); kfree(slot->name); kfree(slot); } @@ -36,7 +37,7 @@ struct slot *alloc_slot_struct(struct device_node *dn, slot->name = kstrdup(drc_name, GFP_KERNEL); if (!slot->name) goto error_slot; - slot->dn = dn; + slot->dn = of_node_get(dn); slot->index = drc_index; slot->power_domain = power_domain; slot->hotplug_slot.ops = &rpaphp_hotplug_slot_ops; From e77704501ca00ecb3f2f6439b3eb0d8afbe3a1bf Mon Sep 17 00:00:00 2001 From: Alexandru Gagniuc Date: Fri, 8 Feb 2019 10:24:11 -0600 Subject: [PATCH 3/6] PCI/ACPI: Do not export pci_get_hp_params() pci_get_hp_params() is only used within drivers/pci, and there is no reason to make it available outside of the PCI core, so stop exporting it. Link: https://lore.kernel.org/lkml/20190208162414.3996-2-mr.nuke.me@gmail.com Signed-off-by: Alexandru Gagniuc Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-acpi.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index e1949f7efd9c..b25e5fa9d1c9 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -366,7 +366,6 @@ int pci_get_hp_params(struct pci_dev *dev, struct hotplug_params *hpp) } return -ENODEV; } -EXPORT_SYMBOL_GPL(pci_get_hp_params); /** * pciehp_is_native - Check whether a hotplug port is handled by the OS From 87fcf12e846a5028c14d21a94a0712fd1ad5bad0 Mon Sep 17 00:00:00 2001 From: Alexandru Gagniuc Date: Fri, 19 Apr 2019 14:27:36 -0500 Subject: [PATCH 4/6] PCI/ACPI: Remove the need for 'struct hotplug_params' We used to first parse all the _HPP and _HPX tables before using the information to program registers of PCIe devices. Up through HPX Type 2, there was only one structure of each type, so we could cheat and store it on the stack. With HPX Type 3 we get an arbitrary number of entries, so the above model doesn't scale that well. Instead of parsing all tables at once, parse and program each entry separately. For _HPP and _HPX Types 0 through 2, this is functionally equivalent. The change enables the upcoming _HPX Type 3 to integrate more easily. Link: https://lore.kernel.org/lkml/20190208162414.3996-3-mr.nuke.me@gmail.com Signed-off-by: Alexandru Gagniuc [bhelgaas: fix build errors] Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-acpi.c | 108 +++++++++++++++++++----------------- drivers/pci/probe.c | 16 ++---- include/linux/pci_hotplug.h | 18 +++--- 3 files changed, 72 insertions(+), 70 deletions(-) diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index b25e5fa9d1c9..95f4f86d2f34 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -119,7 +119,7 @@ phys_addr_t acpi_pci_root_get_mcfg_addr(acpi_handle handle) } static acpi_status decode_type0_hpx_record(union acpi_object *record, - struct hotplug_params *hpx) + struct hpp_type0 *hpx0) { int i; union acpi_object *fields = record->package.elements; @@ -132,12 +132,11 @@ static acpi_status decode_type0_hpx_record(union acpi_object *record, for (i = 2; i < 6; i++) if (fields[i].type != ACPI_TYPE_INTEGER) return AE_ERROR; - hpx->t0 = &hpx->type0_data; - hpx->t0->revision = revision; - hpx->t0->cache_line_size = fields[2].integer.value; - hpx->t0->latency_timer = fields[3].integer.value; - hpx->t0->enable_serr = fields[4].integer.value; - hpx->t0->enable_perr = fields[5].integer.value; + hpx0->revision = revision; + hpx0->cache_line_size = fields[2].integer.value; + hpx0->latency_timer = fields[3].integer.value; + hpx0->enable_serr = fields[4].integer.value; + hpx0->enable_perr = fields[5].integer.value; break; default: printk(KERN_WARNING @@ -149,7 +148,7 @@ static acpi_status decode_type0_hpx_record(union acpi_object *record, } static acpi_status decode_type1_hpx_record(union acpi_object *record, - struct hotplug_params *hpx) + struct hpp_type1 *hpx1) { int i; union acpi_object *fields = record->package.elements; @@ -162,11 +161,10 @@ static acpi_status decode_type1_hpx_record(union acpi_object *record, for (i = 2; i < 5; i++) if (fields[i].type != ACPI_TYPE_INTEGER) return AE_ERROR; - hpx->t1 = &hpx->type1_data; - hpx->t1->revision = revision; - hpx->t1->max_mem_read = fields[2].integer.value; - hpx->t1->avg_max_split = fields[3].integer.value; - hpx->t1->tot_max_split = fields[4].integer.value; + hpx1->revision = revision; + hpx1->max_mem_read = fields[2].integer.value; + hpx1->avg_max_split = fields[3].integer.value; + hpx1->tot_max_split = fields[4].integer.value; break; default: printk(KERN_WARNING @@ -178,7 +176,7 @@ static acpi_status decode_type1_hpx_record(union acpi_object *record, } static acpi_status decode_type2_hpx_record(union acpi_object *record, - struct hotplug_params *hpx) + struct hpp_type2 *hpx2) { int i; union acpi_object *fields = record->package.elements; @@ -191,24 +189,23 @@ static acpi_status decode_type2_hpx_record(union acpi_object *record, for (i = 2; i < 18; i++) if (fields[i].type != ACPI_TYPE_INTEGER) return AE_ERROR; - hpx->t2 = &hpx->type2_data; - hpx->t2->revision = revision; - hpx->t2->unc_err_mask_and = fields[2].integer.value; - hpx->t2->unc_err_mask_or = fields[3].integer.value; - hpx->t2->unc_err_sever_and = fields[4].integer.value; - hpx->t2->unc_err_sever_or = fields[5].integer.value; - hpx->t2->cor_err_mask_and = fields[6].integer.value; - hpx->t2->cor_err_mask_or = fields[7].integer.value; - hpx->t2->adv_err_cap_and = fields[8].integer.value; - hpx->t2->adv_err_cap_or = fields[9].integer.value; - hpx->t2->pci_exp_devctl_and = fields[10].integer.value; - hpx->t2->pci_exp_devctl_or = fields[11].integer.value; - hpx->t2->pci_exp_lnkctl_and = fields[12].integer.value; - hpx->t2->pci_exp_lnkctl_or = fields[13].integer.value; - hpx->t2->sec_unc_err_sever_and = fields[14].integer.value; - hpx->t2->sec_unc_err_sever_or = fields[15].integer.value; - hpx->t2->sec_unc_err_mask_and = fields[16].integer.value; - hpx->t2->sec_unc_err_mask_or = fields[17].integer.value; + hpx2->revision = revision; + hpx2->unc_err_mask_and = fields[2].integer.value; + hpx2->unc_err_mask_or = fields[3].integer.value; + hpx2->unc_err_sever_and = fields[4].integer.value; + hpx2->unc_err_sever_or = fields[5].integer.value; + hpx2->cor_err_mask_and = fields[6].integer.value; + hpx2->cor_err_mask_or = fields[7].integer.value; + hpx2->adv_err_cap_and = fields[8].integer.value; + hpx2->adv_err_cap_or = fields[9].integer.value; + hpx2->pci_exp_devctl_and = fields[10].integer.value; + hpx2->pci_exp_devctl_or = fields[11].integer.value; + hpx2->pci_exp_lnkctl_and = fields[12].integer.value; + hpx2->pci_exp_lnkctl_or = fields[13].integer.value; + hpx2->sec_unc_err_sever_and = fields[14].integer.value; + hpx2->sec_unc_err_sever_or = fields[15].integer.value; + hpx2->sec_unc_err_mask_and = fields[16].integer.value; + hpx2->sec_unc_err_mask_or = fields[17].integer.value; break; default: printk(KERN_WARNING @@ -219,17 +216,18 @@ static acpi_status decode_type2_hpx_record(union acpi_object *record, return AE_OK; } -static acpi_status acpi_run_hpx(acpi_handle handle, struct hotplug_params *hpx) +static acpi_status acpi_run_hpx(struct pci_dev *dev, acpi_handle handle, + const struct hotplug_program_ops *hp_ops) { acpi_status status; struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL}; union acpi_object *package, *record, *fields; + struct hpp_type0 hpx0; + struct hpp_type1 hpx1; + struct hpp_type2 hpx2; u32 type; int i; - /* Clear the return buffer with zeros */ - memset(hpx, 0, sizeof(struct hotplug_params)); - status = acpi_evaluate_object(handle, "_HPX", NULL, &buffer); if (ACPI_FAILURE(status)) return status; @@ -257,19 +255,25 @@ static acpi_status acpi_run_hpx(acpi_handle handle, struct hotplug_params *hpx) type = fields[0].integer.value; switch (type) { case 0: - status = decode_type0_hpx_record(record, hpx); + memset(&hpx0, 0, sizeof(hpx0)); + status = decode_type0_hpx_record(record, &hpx0); if (ACPI_FAILURE(status)) goto exit; + hp_ops->program_type0(dev, &hpx0); break; case 1: - status = decode_type1_hpx_record(record, hpx); + memset(&hpx1, 0, sizeof(hpx1)); + status = decode_type1_hpx_record(record, &hpx1); if (ACPI_FAILURE(status)) goto exit; + hp_ops->program_type1(dev, &hpx1); break; case 2: - status = decode_type2_hpx_record(record, hpx); + memset(&hpx2, 0, sizeof(hpx2)); + status = decode_type2_hpx_record(record, &hpx2); if (ACPI_FAILURE(status)) goto exit; + hp_ops->program_type2(dev, &hpx2); break; default: printk(KERN_ERR "%s: Type %d record not supported\n", @@ -283,14 +287,16 @@ static acpi_status acpi_run_hpx(acpi_handle handle, struct hotplug_params *hpx) return status; } -static acpi_status acpi_run_hpp(acpi_handle handle, struct hotplug_params *hpp) +static acpi_status acpi_run_hpp(struct pci_dev *dev, acpi_handle handle, + const struct hotplug_program_ops *hp_ops) { acpi_status status; struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *package, *fields; + struct hpp_type0 hpp0; int i; - memset(hpp, 0, sizeof(struct hotplug_params)); + memset(&hpp0, 0, sizeof(hpp0)); status = acpi_evaluate_object(handle, "_HPP", NULL, &buffer); if (ACPI_FAILURE(status)) @@ -311,12 +317,13 @@ static acpi_status acpi_run_hpp(acpi_handle handle, struct hotplug_params *hpp) } } - hpp->t0 = &hpp->type0_data; - hpp->t0->revision = 1; - hpp->t0->cache_line_size = fields[0].integer.value; - hpp->t0->latency_timer = fields[1].integer.value; - hpp->t0->enable_serr = fields[2].integer.value; - hpp->t0->enable_perr = fields[3].integer.value; + hpp0.revision = 1; + hpp0.cache_line_size = fields[0].integer.value; + hpp0.latency_timer = fields[1].integer.value; + hpp0.enable_serr = fields[2].integer.value; + hpp0.enable_perr = fields[3].integer.value; + + hp_ops->program_type0(dev, &hpp0); exit: kfree(buffer.pointer); @@ -328,7 +335,8 @@ static acpi_status acpi_run_hpp(acpi_handle handle, struct hotplug_params *hpp) * @dev - the pci_dev for which we want parameters * @hpp - allocated by the caller */ -int pci_get_hp_params(struct pci_dev *dev, struct hotplug_params *hpp) +int pci_acpi_program_hp_params(struct pci_dev *dev, + const struct hotplug_program_ops *hp_ops) { acpi_status status; acpi_handle handle, phandle; @@ -351,10 +359,10 @@ int pci_get_hp_params(struct pci_dev *dev, struct hotplug_params *hpp) * this pci dev. */ while (handle) { - status = acpi_run_hpx(handle, hpp); + status = acpi_run_hpx(dev, handle, hp_ops); if (ACPI_SUCCESS(status)) return 0; - status = acpi_run_hpp(handle, hpp); + status = acpi_run_hpp(dev, handle, hp_ops); if (ACPI_SUCCESS(status)) return 0; if (acpi_is_root_bridge(handle)) diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index 2ec0df04e0dc..dce5ae39d0d8 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2206,8 +2206,11 @@ static void pci_configure_serr(struct pci_dev *dev) static void pci_configure_device(struct pci_dev *dev) { - struct hotplug_params hpp; - int ret; + static const struct hotplug_program_ops hp_ops = { + .program_type0 = program_hpp_type0, + .program_type1 = program_hpp_type1, + .program_type2 = program_hpp_type2, + }; pci_configure_mps(dev); pci_configure_extended_tags(dev, NULL); @@ -2216,14 +2219,7 @@ static void pci_configure_device(struct pci_dev *dev) pci_configure_eetlp_prefix(dev); pci_configure_serr(dev); - memset(&hpp, 0, sizeof(hpp)); - ret = pci_get_hp_params(dev, &hpp); - if (ret) - return; - - program_hpp_type2(dev, hpp.t2); - program_hpp_type1(dev, hpp.t1); - program_hpp_type0(dev, hpp.t0); + pci_acpi_program_hp_params(dev, &hp_ops); } static void pci_release_capabilities(struct pci_dev *dev) diff --git a/include/linux/pci_hotplug.h b/include/linux/pci_hotplug.h index 7acc9f91e72b..2c1e12b7ac00 100644 --- a/include/linux/pci_hotplug.h +++ b/include/linux/pci_hotplug.h @@ -124,26 +124,24 @@ struct hpp_type2 { u32 sec_unc_err_mask_or; }; -struct hotplug_params { - struct hpp_type0 *t0; /* Type0: NULL if not available */ - struct hpp_type1 *t1; /* Type1: NULL if not available */ - struct hpp_type2 *t2; /* Type2: NULL if not available */ - struct hpp_type0 type0_data; - struct hpp_type1 type1_data; - struct hpp_type2 type2_data; +struct hotplug_program_ops { + void (*program_type0)(struct pci_dev *dev, struct hpp_type0 *hpp); + void (*program_type1)(struct pci_dev *dev, struct hpp_type1 *hpp); + void (*program_type2)(struct pci_dev *dev, struct hpp_type2 *hpp); }; #ifdef CONFIG_ACPI #include -int pci_get_hp_params(struct pci_dev *dev, struct hotplug_params *hpp); +int pci_acpi_program_hp_params(struct pci_dev *dev, + const struct hotplug_program_ops *hp_ops); bool pciehp_is_native(struct pci_dev *bridge); int acpi_get_hp_hw_control_from_firmware(struct pci_dev *bridge); bool shpchp_is_native(struct pci_dev *bridge); int acpi_pci_check_ejectable(struct pci_bus *pbus, acpi_handle handle); int acpi_pci_detect_ejectable(acpi_handle handle); #else -static inline int pci_get_hp_params(struct pci_dev *dev, - struct hotplug_params *hpp) +static inline int pci_acpi_program_hp_params(struct pci_dev *dev, + const struct hotplug_program_ops *hp_ops) { return -ENODEV; } From f873c51a155aaa6dafdc00fa7fda3754f2f9f794 Mon Sep 17 00:00:00 2001 From: Alexandru Gagniuc Date: Fri, 8 Feb 2019 10:24:13 -0600 Subject: [PATCH 5/6] PCI/ACPI: Implement _HPX Type 3 Setting Record The _HPX Type 3 Setting Record is intended to be more generic and allow configuration of settings not possible with Type 2 records. For example, firmware could ensure that the completion timeout value is set accordingly throughout the PCI tree. Implement support for _HPX Type 3 Setting Records, which were added in the ACPI 6.3 spec. Link: https://lore.kernel.org/lkml/20190208162414.3996-4-mr.nuke.me@gmail.com Signed-off-by: Alexandru Gagniuc Signed-off-by: Bjorn Helgaas --- drivers/pci/pci-acpi.c | 63 ++++++++++++++++++++ drivers/pci/probe.c | 114 ++++++++++++++++++++++++++++++++++++ include/linux/pci_hotplug.h | 48 +++++++++++++++ 3 files changed, 225 insertions(+) diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 95f4f86d2f34..03e02dd6c1d9 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -216,6 +216,64 @@ static acpi_status decode_type2_hpx_record(union acpi_object *record, return AE_OK; } +static void parse_hpx3_register(struct hpx_type3 *hpx3_reg, + union acpi_object *reg_fields) +{ + hpx3_reg->device_type = reg_fields[0].integer.value; + hpx3_reg->function_type = reg_fields[1].integer.value; + hpx3_reg->config_space_location = reg_fields[2].integer.value; + hpx3_reg->pci_exp_cap_id = reg_fields[3].integer.value; + hpx3_reg->pci_exp_cap_ver = reg_fields[4].integer.value; + hpx3_reg->pci_exp_vendor_id = reg_fields[5].integer.value; + hpx3_reg->dvsec_id = reg_fields[6].integer.value; + hpx3_reg->dvsec_rev = reg_fields[7].integer.value; + hpx3_reg->match_offset = reg_fields[8].integer.value; + hpx3_reg->match_mask_and = reg_fields[9].integer.value; + hpx3_reg->match_value = reg_fields[10].integer.value; + hpx3_reg->reg_offset = reg_fields[11].integer.value; + hpx3_reg->reg_mask_and = reg_fields[12].integer.value; + hpx3_reg->reg_mask_or = reg_fields[13].integer.value; +} + +static acpi_status program_type3_hpx_record(struct pci_dev *dev, + union acpi_object *record, + const struct hotplug_program_ops *hp_ops) +{ + union acpi_object *fields = record->package.elements; + u32 desc_count, expected_length, revision; + union acpi_object *reg_fields; + struct hpx_type3 hpx3; + int i; + + revision = fields[1].integer.value; + switch (revision) { + case 1: + desc_count = fields[2].integer.value; + expected_length = 3 + desc_count * 14; + + if (record->package.count != expected_length) + return AE_ERROR; + + for (i = 2; i < expected_length; i++) + if (fields[i].type != ACPI_TYPE_INTEGER) + return AE_ERROR; + + for (i = 0; i < desc_count; i++) { + reg_fields = fields + 3 + i * 14; + parse_hpx3_register(&hpx3, reg_fields); + hp_ops->program_type3(dev, &hpx3); + } + + break; + default: + printk(KERN_WARNING + "%s: Type 3 Revision %d record not supported\n", + __func__, revision); + return AE_ERROR; + } + return AE_OK; +} + static acpi_status acpi_run_hpx(struct pci_dev *dev, acpi_handle handle, const struct hotplug_program_ops *hp_ops) { @@ -275,6 +333,11 @@ static acpi_status acpi_run_hpx(struct pci_dev *dev, acpi_handle handle, goto exit; hp_ops->program_type2(dev, &hpx2); break; + case 3: + status = program_type3_hpx_record(dev, record, hp_ops); + if (ACPI_FAILURE(status)) + goto exit; + break; default: printk(KERN_ERR "%s: Type %d record not supported\n", __func__, type); diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index dce5ae39d0d8..ea6c1762d5c8 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -2026,6 +2026,119 @@ static void program_hpp_type2(struct pci_dev *dev, struct hpp_type2 *hpp) */ } +static u16 hpx3_device_type(struct pci_dev *dev) +{ + u16 pcie_type = pci_pcie_type(dev); + const int pcie_to_hpx3_type[] = { + [PCI_EXP_TYPE_ENDPOINT] = HPX_TYPE_ENDPOINT, + [PCI_EXP_TYPE_LEG_END] = HPX_TYPE_LEG_END, + [PCI_EXP_TYPE_RC_END] = HPX_TYPE_RC_END, + [PCI_EXP_TYPE_RC_EC] = HPX_TYPE_RC_EC, + [PCI_EXP_TYPE_ROOT_PORT] = HPX_TYPE_ROOT_PORT, + [PCI_EXP_TYPE_UPSTREAM] = HPX_TYPE_UPSTREAM, + [PCI_EXP_TYPE_DOWNSTREAM] = HPX_TYPE_DOWNSTREAM, + [PCI_EXP_TYPE_PCI_BRIDGE] = HPX_TYPE_PCI_BRIDGE, + [PCI_EXP_TYPE_PCIE_BRIDGE] = HPX_TYPE_PCIE_BRIDGE, + }; + + if (pcie_type >= ARRAY_SIZE(pcie_to_hpx3_type)) + return 0; + + return pcie_to_hpx3_type[pcie_type]; +} + +static u8 hpx3_function_type(struct pci_dev *dev) +{ + if (dev->is_virtfn) + return HPX_FN_SRIOV_VIRT; + else if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_SRIOV) > 0) + return HPX_FN_SRIOV_PHYS; + else + return HPX_FN_NORMAL; +} + +static bool hpx3_cap_ver_matches(u8 pcie_cap_id, u8 hpx3_cap_id) +{ + u8 cap_ver = hpx3_cap_id & 0xf; + + if ((hpx3_cap_id & BIT(4)) && cap_ver >= pcie_cap_id) + return true; + else if (cap_ver == pcie_cap_id) + return true; + + return false; +} + +static void program_hpx_type3_register(struct pci_dev *dev, + const struct hpx_type3 *reg) +{ + u32 match_reg, write_reg, header, orig_value; + u16 pos; + + if (!(hpx3_device_type(dev) & reg->device_type)) + return; + + if (!(hpx3_function_type(dev) & reg->function_type)) + return; + + switch (reg->config_space_location) { + case HPX_CFG_PCICFG: + pos = 0; + break; + case HPX_CFG_PCIE_CAP: + pos = pci_find_capability(dev, reg->pci_exp_cap_id); + if (pos == 0) + return; + + break; + case HPX_CFG_PCIE_CAP_EXT: + pos = pci_find_ext_capability(dev, reg->pci_exp_cap_id); + if (pos == 0) + return; + + pci_read_config_dword(dev, pos, &header); + if (!hpx3_cap_ver_matches(PCI_EXT_CAP_VER(header), + reg->pci_exp_cap_ver)) + return; + + break; + case HPX_CFG_VEND_CAP: /* Fall through */ + case HPX_CFG_DVSEC: /* Fall through */ + default: + pci_warn(dev, "Encountered _HPX type 3 with unsupported config space location"); + return; + } + + pci_read_config_dword(dev, pos + reg->match_offset, &match_reg); + + if ((match_reg & reg->match_mask_and) != reg->match_value) + return; + + pci_read_config_dword(dev, pos + reg->reg_offset, &write_reg); + orig_value = write_reg; + write_reg &= reg->reg_mask_and; + write_reg |= reg->reg_mask_or; + + if (orig_value == write_reg) + return; + + pci_write_config_dword(dev, pos + reg->reg_offset, write_reg); + + pci_dbg(dev, "Applied _HPX3 at [0x%x]: 0x%08x -> 0x%08x", + pos, orig_value, write_reg); +} + +static void program_hpx_type3(struct pci_dev *dev, struct hpx_type3 *hpx3) +{ + if (!hpx3) + return; + + if (!pci_is_pcie(dev)) + return; + + program_hpx_type3_register(dev, hpx3); +} + int pci_configure_extended_tags(struct pci_dev *dev, void *ign) { struct pci_host_bridge *host; @@ -2210,6 +2323,7 @@ static void pci_configure_device(struct pci_dev *dev) .program_type0 = program_hpp_type0, .program_type1 = program_hpp_type1, .program_type2 = program_hpp_type2, + .program_type3 = program_hpx_type3, }; pci_configure_mps(dev); diff --git a/include/linux/pci_hotplug.h b/include/linux/pci_hotplug.h index 2c1e12b7ac00..f694eb2ca978 100644 --- a/include/linux/pci_hotplug.h +++ b/include/linux/pci_hotplug.h @@ -124,10 +124,58 @@ struct hpp_type2 { u32 sec_unc_err_mask_or; }; +/* + * _HPX PCI Express Setting Record (Type 3) + */ +struct hpx_type3 { + u16 device_type; + u16 function_type; + u16 config_space_location; + u16 pci_exp_cap_id; + u16 pci_exp_cap_ver; + u16 pci_exp_vendor_id; + u16 dvsec_id; + u16 dvsec_rev; + u16 match_offset; + u32 match_mask_and; + u32 match_value; + u16 reg_offset; + u32 reg_mask_and; + u32 reg_mask_or; +}; + struct hotplug_program_ops { void (*program_type0)(struct pci_dev *dev, struct hpp_type0 *hpp); void (*program_type1)(struct pci_dev *dev, struct hpp_type1 *hpp); void (*program_type2)(struct pci_dev *dev, struct hpp_type2 *hpp); + void (*program_type3)(struct pci_dev *dev, struct hpx_type3 *hpp); +}; + +enum hpx_type3_dev_type { + HPX_TYPE_ENDPOINT = BIT(0), + HPX_TYPE_LEG_END = BIT(1), + HPX_TYPE_RC_END = BIT(2), + HPX_TYPE_RC_EC = BIT(3), + HPX_TYPE_ROOT_PORT = BIT(4), + HPX_TYPE_UPSTREAM = BIT(5), + HPX_TYPE_DOWNSTREAM = BIT(6), + HPX_TYPE_PCI_BRIDGE = BIT(7), + HPX_TYPE_PCIE_BRIDGE = BIT(8), +}; + +enum hpx_type3_fn_type { + HPX_FN_NORMAL = BIT(0), + HPX_FN_SRIOV_PHYS = BIT(1), + HPX_FN_SRIOV_VIRT = BIT(2), +}; + +enum hpx_type3_cfg_loc { + HPX_CFG_PCICFG = 0, + HPX_CFG_PCIE_CAP = 1, + HPX_CFG_PCIE_CAP_EXT = 2, + HPX_CFG_VEND_CAP = 3, + HPX_CFG_DVSEC = 4, + HPX_CFG_MAX, }; #ifdef CONFIG_ACPI From ba11edc65065ffe38508aa76fdf7a7db15ad8161 Mon Sep 17 00:00:00 2001 From: Alexandru Gagniuc Date: Fri, 15 Mar 2019 14:29:40 -0500 Subject: [PATCH 6/6] PCI/ACPI: Advertise _HPX Type 3 support via _OSC _OSC now has a way to inform firmware that OS has the capability to interpret _HPX Type 3 setting records. This was added by the following PCI Firmware Specification ECN: ECN: _HPX and PCIe Completion Timeout related _OSC Enhancements Date: September 12, 2018 Affected Document: PCI Firmware Specification, Rev. 3.2 Signed-off-by: Alexandru Gagniuc Signed-off-by: Bjorn Helgaas --- drivers/acpi/pci_root.c | 2 ++ include/linux/acpi.h | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 707aafc7c2aa..c36781a9b493 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -145,6 +145,7 @@ static struct pci_osc_bit_struct pci_osc_support_bit[] = { { OSC_PCI_CLOCK_PM_SUPPORT, "ClockPM" }, { OSC_PCI_SEGMENT_GROUPS_SUPPORT, "Segments" }, { OSC_PCI_MSI_SUPPORT, "MSI" }, + { OSC_PCI_HPX_TYPE_3_SUPPORT, "HPX-Type3" }, }; static struct pci_osc_bit_struct pci_osc_control_bit[] = { @@ -446,6 +447,7 @@ static void negotiate_os_control(struct acpi_pci_root *root, int *no_aspm, * PCI domains, so we indicate this in _OSC support capabilities. */ support = OSC_PCI_SEGMENT_GROUPS_SUPPORT; + support |= OSC_PCI_HPX_TYPE_3_SUPPORT; if (pci_ext_cfg_avail()) support |= OSC_PCI_EXT_CONFIG_SUPPORT; if (pcie_aspm_support_enabled()) diff --git a/include/linux/acpi.h b/include/linux/acpi.h index d5dcebd7aad3..b84bcd705fe1 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -513,7 +513,8 @@ extern bool osc_pc_lpi_support_confirmed; #define OSC_PCI_CLOCK_PM_SUPPORT 0x00000004 #define OSC_PCI_SEGMENT_GROUPS_SUPPORT 0x00000008 #define OSC_PCI_MSI_SUPPORT 0x00000010 -#define OSC_PCI_SUPPORT_MASKS 0x0000001f +#define OSC_PCI_HPX_TYPE_3_SUPPORT 0x00000100 +#define OSC_PCI_SUPPORT_MASKS 0x0000011f /* PCI Host Bridge _OSC: Capabilities DWORD 3: Control Field */ #define OSC_PCI_EXPRESS_NATIVE_HP_CONTROL 0x00000001