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 <mr.nuke.me@gmail.com> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
This commit is contained in:
parent
87fcf12e84
commit
f873c51a15
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user