The driver reached the PCU function's config space by walking the ACPI MCFG
table and mapping the ECAM page itself. That is one leaf driver deciding
config-space policy, and no other driver in mainline does it, so it was the
single most likely reason for a NAK.
Move that decision where it belongs: the upstream series is now two patches,
a PCI/x86 accessor that forces the memory-mapped path with the core's
locking, and this driver on top of it. Reviewers on each side now get
something they can act on, and get_maintainer.pl selects the PCI and x86
people by itself.
Correctness work in the same pass:
- the completion bits are latched, so a command the engine never accepted
was indistinguishable from a completed one and returned the previous
transaction's data as valid. Require a positive sign that the engine
acted on the command;
- compare the command registers after every transfer against what the
driver left in them and fail with -EAGAIN on a mismatch. The original
iMC driver had this check and it is what makes an unarbitrated system
detectable instead of silently wrong;
- use the i2c core's suspended-adapter mechanism rather than a private
flag and -EHOSTDOWN;
- use devm_mutex_init(), drop the unnecessary pci_enable_device(), and
return -ENODEV rather than -EPERM from the default-off gate, which was
printing a driver-core error on every boot;
- mask GO when restoring a saved command word.
Stop attributing the dropped CF8/CFC write to System Management Mode. The
only evidence was a boot log line naming the access method, which says
nothing about who filters the write. tools/probe-cf8-write.sh is there to
settle it with an MSR_SMI_COUNT measurement.
Add Documentation/i2c/busses/i2c-imc-skylake.rst, which every other PC SMBus
driver has and which was explicitly asked for on the 2020 iMC thread, the
tools/ scripts for the hardware measurements the submission still owes, and
compat/pci-mmcfg-compat.h so the driver still builds out-of-tree against a
kernel without the new accessor while the source stays byte-identical to the
submitted patch.
205 lines
7.5 KiB
Diff
205 lines
7.5 KiB
Diff
From 2bde3ce8de136654c563b0bc0b05847293bf15e5 Mon Sep 17 00:00:00 2001
|
|
From: Simone Chifari <[email protected]>
|
|
Date: Sun, 26 Jul 2026 19:24:49 +0200
|
|
Subject: [RFC PATCH v4 1/2] PCI: allow config access forced through the
|
|
memory-mapped path
|
|
|
|
On x86, raw_pci_read() and raw_pci_write() dispatch the first 256 bytes
|
|
of config space to raw_pci_ops, which is CF8/CFC whenever configuration
|
|
type 1 is usable. raw_pci_ext_ops, the memory-mapped (ECAM) path, is
|
|
only consulted from offset 0x100 upwards.
|
|
|
|
A platform that filters or drops CF8/CFC accesses to one function
|
|
therefore makes that function's low config space unreachable through the
|
|
normal accessors, even though the ECAM window reaches it. This is
|
|
observable on Intel Skylake-X boards: a dword written to the iMC SMBus
|
|
command registers at offsets 0x9C-0xB8 of the PCU function 8086:2085
|
|
does not take effect, the GO bit is never consumed and no transaction is
|
|
issued, while the same write through ECAM works. Everything else that
|
|
reads or writes that function's low config space, including setpci and
|
|
sysfs, is equally affected.
|
|
|
|
Add pci_mmcfg_read_config() and pci_mmcfg_write_config(). They take
|
|
pci_lock and honour pci_cfg_access_lock() like the other config
|
|
accessors, on top of a weak arch_pci_mmcfg_access() hook. x86
|
|
implements the hook over the existing pci_mmcfg_list, so no MCFG table
|
|
is re-parsed and no window that the architecture has already mapped is
|
|
mapped a second time. Architectures with no memory-mapped config space
|
|
keep the default, which reports -ENXIO.
|
|
|
|
These do not replace pci_read_config_dword() and friends. They are
|
|
meant for a device documented to be unreachable otherwise, and callers
|
|
are expected to justify the use in a comment.
|
|
|
|
Signed-off-by: Simone Chifari <[email protected]>
|
|
---
|
|
arch/x86/pci/mmconfig-shared.c | 28 +++++++++++
|
|
drivers/pci/access.c | 89 ++++++++++++++++++++++++++++++++++
|
|
include/linux/pci.h | 11 +++++
|
|
3 files changed, 128 insertions(+)
|
|
|
|
diff --git a/arch/x86/pci/mmconfig-shared.c b/arch/x86/pci/mmconfig-shared.c
|
|
index acdb8dcae..715fdcdae 100644
|
|
--- a/arch/x86/pci/mmconfig-shared.c
|
|
+++ b/arch/x86/pci/mmconfig-shared.c
|
|
@@ -779,6 +779,34 @@ static int __init pci_mmcfg_late_insert_resources(void)
|
|
*/
|
|
late_initcall(pci_mmcfg_late_insert_resources);
|
|
|
|
+/*
|
|
+ * raw_pci_read()/raw_pci_write() dispatch the first 256 config bytes to
|
|
+ * raw_pci_ops, which is CF8/CFC whenever configuration type 1 is usable.
|
|
+ * A platform that filters CF8/CFC for one function therefore makes that
|
|
+ * function's low config space unreachable through the normal accessors,
|
|
+ * even though the ECAM window reaches it. Give the PCI core a way to
|
|
+ * select the ECAM path explicitly for such a device.
|
|
+ *
|
|
+ * See the pci_mmcfg_read_config() kerneldoc for the usage restrictions.
|
|
+ */
|
|
+int arch_pci_mmcfg_access(struct pci_dev *dev, int where, int size, u32 *val,
|
|
+ bool write)
|
|
+{
|
|
+ unsigned int seg = pci_domain_nr(dev->bus);
|
|
+ unsigned int bus = dev->bus->number;
|
|
+ int ret;
|
|
+
|
|
+ if (!(pci_probe & PCI_PROBE_MMCONF) || pci_mmcfg_arch_init_failed)
|
|
+ return -ENXIO;
|
|
+
|
|
+ if (write)
|
|
+ ret = pci_mmcfg.write(seg, bus, dev->devfn, where, size, *val);
|
|
+ else
|
|
+ ret = pci_mmcfg.read(seg, bus, dev->devfn, where, size, val);
|
|
+
|
|
+ return ret < 0 ? ret : 0;
|
|
+}
|
|
+
|
|
/* Add ECAM information for host bridges */
|
|
int pci_mmconfig_insert(struct device *dev, u16 seg, u8 start, u8 end,
|
|
phys_addr_t addr)
|
|
diff --git a/drivers/pci/access.c b/drivers/pci/access.c
|
|
index b123da16b..e058b9f2b 100644
|
|
--- a/drivers/pci/access.c
|
|
+++ b/drivers/pci/access.c
|
|
@@ -277,6 +277,95 @@ PCI_USER_WRITE_CONFIG(byte, u8)
|
|
PCI_USER_WRITE_CONFIG(word, u16)
|
|
PCI_USER_WRITE_CONFIG(dword, u32)
|
|
|
|
+/*
|
|
+ * Architecture hook for config access forced through the memory-mapped
|
|
+ * (ECAM) path. Called with @pci_lock held and interrupts disabled.
|
|
+ * Architectures without a memory-mapped config space leave the default,
|
|
+ * which reports that the path does not exist.
|
|
+ */
|
|
+int __weak arch_pci_mmcfg_access(struct pci_dev *dev, int where, int size,
|
|
+ u32 *val, bool write)
|
|
+{
|
|
+ return -ENXIO;
|
|
+}
|
|
+
|
|
+static int pci_mmcfg_access(struct pci_dev *dev, int where, int size, u32 *val,
|
|
+ bool write)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ if (size != 1 && size != 2 && size != 4)
|
|
+ return -EINVAL;
|
|
+ if (where & (size - 1))
|
|
+ return -EINVAL;
|
|
+ if (where + size > dev->cfg_size)
|
|
+ return -EINVAL;
|
|
+ if (pci_dev_is_disconnected(dev))
|
|
+ return -ENODEV;
|
|
+
|
|
+ raw_spin_lock_irq(&pci_lock);
|
|
+ if (unlikely(dev->block_cfg_access))
|
|
+ pci_wait_cfg(dev);
|
|
+ ret = arch_pci_mmcfg_access(dev, where, size, val, write);
|
|
+ raw_spin_unlock_irq(&pci_lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * pci_mmcfg_read_config - read config space through the ECAM path
|
|
+ * @dev: PCI device
|
|
+ * @where: byte offset in config space, naturally aligned for @size
|
|
+ * @size: 1, 2 or 4 bytes
|
|
+ * @val: where to store the value read
|
|
+ *
|
|
+ * Read config space of @dev through the memory-mapped (ECAM) path
|
|
+ * regardless of the access method the platform selected for the first 256
|
|
+ * config bytes. On x86 that method is CF8/CFC, and some platforms filter
|
|
+ * or trap those accesses for individual functions, which makes the
|
|
+ * registers of such a function unreachable through the normal accessors.
|
|
+ *
|
|
+ * Drivers should use pci_read_config_dword() and friends. This is only
|
|
+ * for a device documented to be unreachable otherwise, and callers must
|
|
+ * be able to justify why in a comment.
|
|
+ *
|
|
+ * Must be called from a context that may sleep.
|
|
+ *
|
|
+ * Return: 0 on success, -ENXIO when the platform has no ECAM path,
|
|
+ * otherwise a negative errno. @val is set to all ones on error.
|
|
+ */
|
|
+int pci_mmcfg_read_config(struct pci_dev *dev, int where, int size, u32 *val)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ *val = 0;
|
|
+ ret = pci_mmcfg_access(dev, where, size, val, false);
|
|
+ if (ret)
|
|
+ *val = ~0;
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pci_mmcfg_read_config);
|
|
+
|
|
+/**
|
|
+ * pci_mmcfg_write_config - write config space through the ECAM path
|
|
+ * @dev: PCI device
|
|
+ * @where: byte offset in config space, naturally aligned for @size
|
|
+ * @size: 1, 2 or 4 bytes
|
|
+ * @val: value to write
|
|
+ *
|
|
+ * Write counterpart of pci_mmcfg_read_config(). The same restrictions
|
|
+ * apply; see that function.
|
|
+ *
|
|
+ * Return: 0 on success, -ENXIO when the platform has no ECAM path,
|
|
+ * otherwise a negative errno.
|
|
+ */
|
|
+int pci_mmcfg_write_config(struct pci_dev *dev, int where, int size, u32 val)
|
|
+{
|
|
+ return pci_mmcfg_access(dev, where, size, &val, true);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(pci_mmcfg_write_config);
|
|
+
|
|
/**
|
|
* pci_cfg_access_lock - Lock PCI config reads/writes
|
|
* @dev: pci device struct
|
|
diff --git a/include/linux/pci.h b/include/linux/pci.h
|
|
index 2c4454583..942b20dcc 100644
|
|
--- a/include/linux/pci.h
|
|
+++ b/include/linux/pci.h
|
|
@@ -1412,6 +1412,17 @@ int pci_user_write_config_byte(struct pci_dev *dev, int where, u8 val);
|
|
int pci_user_write_config_word(struct pci_dev *dev, int where, u16 val);
|
|
int pci_user_write_config_dword(struct pci_dev *dev, int where, u32 val);
|
|
|
|
+/*
|
|
+ * Config access forced through the memory-mapped (ECAM) path. Only for
|
|
+ * devices whose registers are unreachable with the access method the
|
|
+ * platform selected for the first 256 config bytes. Both return -ENXIO on
|
|
+ * an architecture or platform that has no such path.
|
|
+ */
|
|
+int arch_pci_mmcfg_access(struct pci_dev *dev, int where, int size, u32 *val,
|
|
+ bool write);
|
|
+int pci_mmcfg_read_config(struct pci_dev *dev, int where, int size, u32 *val);
|
|
+int pci_mmcfg_write_config(struct pci_dev *dev, int where, int size, u32 val);
|
|
+
|
|
int __must_check pci_enable_device(struct pci_dev *dev);
|
|
int __must_check pci_enable_device_mem(struct pci_dev *dev);
|
|
int __must_check pci_reenable_device(struct pci_dev *);
|
|
--
|
|
2.43.0
|
|
|