Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f873c51a authored by Alexandru Gagniuc's avatar Alexandru Gagniuc Committed by Bjorn Helgaas
Browse files

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: default avatarAlexandru Gagniuc <mr.nuke.me@gmail.com>
Signed-off-by: default avatarBjorn Helgaas <bhelgaas@google.com>
parent 87fcf12e
Loading
Loading
Loading
Loading
+63 −0
Original line number Diff line number Diff line
@@ -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);
+114 −0
Original line number Diff line number Diff line
@@ -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);
+48 −0
Original line number Diff line number Diff line
@@ -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