i2c-imc-skylake: close F2 with the register Intel documents

The open question was how to know whether the memory controller is polling
the DIMM thermal sensors, which is what killed the Broadwell-E attempt's
predecessor and what this driver had no answer for.

Intel documents it. The Xeon Processor Scalable Family datasheet volume 2
(reference 614073, section 3.1.9) puts SMBCNTL on the iMC channel function,
device 10 and 12 function 0, at offsets 0xe88 and 0xe98. SMB_TSOD_POLL_EN
is documented as mutually exclusive with SPD command access, so it answers
the question directly. The driver now reads it on every channel at probe
and refuses to bind if it is set.

This is deliberately weaker than the Broadwell-E driver, which switched the
polling off for the duration of a transfer. Reading and declining is a
smaller claim on hardware the driver does not own and needs no write to a
firmware-configured register. It covers the memory controller only; SMM and
a BMC are still out of reach, which is what the opt-in is for.

On the test system all four channels read 0xac000000: polling disabled,
TSOD_PRESENT empty, which also explains why no jc42 ever appeared at
0x18-0x1f on these DIMMs. Offset 0xe0, where the Broadwell driver has its
TSODCNTL, reads zero on all seven PCU functions here: that layout does not
transfer.

Two more measurements, both recorded in the cover letter:

  - the SMBus interface Intel documents, at 0xe80-0xe9c on the iMC, does not
    reach the bus on this platform. Issuing SMBCMD with CMD_TRIGGER sets
    SMB_RDO and clears SMB_BUSY, but returns 0x00 for a populated DIMM at
    0x50, a second one at 0x52 and an empty slot at 0x57 alike, with SMB_SBE
    never set. The path this driver uses returns the correct SPD contents
    and a NACK for the empty slot. The datasheet does not describe PCU
    function 5 at all, so the choice is now stated with data rather than
    left for a reviewer to ask about;
  - writes are issued on the bus: a byte write to an unpopulated address
    returns -ENXIO with the engine reporting the address NACK. This matters
    because SMB_DIS_WRT is set in the documented SMBCNTL and is SMM-writable
    only, so it evidently governs that interface and not this one.

Engine registers sampled every 2 s for an hour with the driver unloaded:
1791 samples, 0 changes.

Add tools/probe-imc-documented.sh, which produced the comparison above.
Smoke test with the new probe check: 15 checks, 0 failures.
This commit is contained in:
Simone
2026-07-26 21:34:47 +02:00
parent 88a8b7eb27
commit b021fe4087
4 changed files with 252 additions and 10 deletions
+61
View File
@@ -57,6 +57,18 @@
/* PCI id of the Sky Lake-E PCU function carrying the SMBus engine */
#define PCU_DEVICE 0x2085
/*
* The iMC channel function, device 10 and 12 function 0, carries the SMBus
* control register documented in the Intel Xeon Processor Scalable Family
* datasheet volume 2 (reference 614073) section 3.1.9. Only SMBCNTL is read
* here, and only to find out whether the hardware is polling the DIMM thermal
* sensors on its own.
*/
#define IMC_CHANNEL_DEVICE 0x2040
#define IMC_SMBCNTL(ch) (0xE88 + (ch) * 0x10)
#define SMBCNTL_TSOD_POLL_EN BIT(8)
#define SMBCNTL_TSOD_PRESENT GENMASK(7, 0)
/* per-channel register offsets in the function's config space */
#define CH0_CTRL 0xB4
#define CH0_DATA 0x9C
@@ -524,6 +536,51 @@ static int imc_resume(struct device *dev)
static DEFINE_SIMPLE_DEV_PM_OPS(imc_pm_ops, imc_suspend, imc_resume);
/*
* Refuse to share the engine with the memory controller's own thermal
* throttling. SMBCNTL.TSOD_POLL_EN tells us directly whether the iMC is
* issuing SMBus transactions of its own; Intel documents that SPD command
* access and TSOD polling are mutually exclusive, so a set bit means the
* engine is not ours to drive. This does not cover SMM or a BMC, which
* remain the reason for the allow_unsafe_access gate.
*/
static int imc_check_firmware_polling(struct pci_dev *pdev)
{
struct pci_dev *imc = NULL;
unsigned int found = 0;
int i;
while ((imc = pci_get_device(PCI_VENDOR_ID_INTEL, IMC_CHANNEL_DEVICE,
imc))) {
found++;
for (i = 0; i < ARRAY_SIZE(imc_chans); i++) {
u32 cntl;
if (pci_read_config_dword(imc, IMC_SMBCNTL(i), &cntl))
continue;
if (cntl & SMBCNTL_TSOD_POLL_EN) {
dev_err(&pdev->dev,
"%s ch%d has TSOD polling enabled (SMBCNTL 0x%08x); the memory controller is using the engine\n",
pci_name(imc), i, cntl);
pci_dev_put(imc);
return -EBUSY;
}
dev_dbg(&pdev->dev,
"%s ch%d SMBCNTL 0x%08x, TSOD present mask 0x%02lx\n",
pci_name(imc), i, cntl,
cntl & SMBCNTL_TSOD_PRESENT);
}
}
if (!found)
dev_warn(&pdev->dev,
"no iMC channel function found; cannot verify that TSOD polling is disabled\n");
return 0;
}
static int imc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct imc_smbus *s;
@@ -535,6 +592,10 @@ static int imc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
return -ENODEV;
}
ret = imc_check_firmware_polling(pdev);
if (ret)
return ret;
s = devm_kzalloc(&pdev->dev, sizeof(*s), GFP_KERNEL);
if (!s)
return -ENOMEM;