Some memory-mapped PCI host bridges only decode 32-bit accesses to
their configuration space and either drop or corrupt neighbouring
bytes on a narrower access. Because such hardware only ever reliably
commits a full 32-bit transaction, the only safe way to perform a
narrower write is to synthesize it in software: read the containing
word, patch in the requested bits, and write the whole word back.

Add pci_generic_mmap_write_config32(), a counterpart to
pci_generic_mmap_write_config(), that does exactly this: it
transparently promotes 8-bit and 16-bit writes to a 32-bit
read-modify-write of the word containing the requested offset,
leaving 32-bit writes untouched. This mirrors Linux's
pci_generic_config_write32() (drivers/pci/access.c), used for the
same class of host bridge.

Signed-off-by: Pranav Sanwal <[email protected]>
---
 drivers/pci/pci-uclass.c | 42 ++++++++++++++++++++++++++++++++++++++++
 include/pci.h            | 26 +++++++++++++++++++++++++
 2 files changed, 68 insertions(+)

diff --git a/drivers/pci/pci-uclass.c b/drivers/pci/pci-uclass.c
index 4bdd1f7477f..f4ce630118f 100644
--- a/drivers/pci/pci-uclass.c
+++ b/drivers/pci/pci-uclass.c
@@ -618,6 +618,48 @@ int pci_generic_mmap_write_config(
        }
 }
 
+int pci_generic_mmap_write_config32(
+       const struct udevice *bus,
+       int (*addr_f)(const struct udevice *bus, pci_dev_t bdf, uint offset,
+                     void **addrp),
+       pci_dev_t bdf,
+       uint offset,
+       ulong value,
+       enum pci_size_t size)
+{
+       u32 shift, mask, tmp;
+       void *address;
+
+       if (addr_f(bus, bdf, ALIGN_DOWN(offset, 4), &address) < 0) {
+               debug("%s: failed to get config address for offset 0x%x\n",
+                     __func__, offset);
+               return 0;
+       }
+
+       switch (size) {
+       case PCI_SIZE_32:
+               writel(value, address);
+               return 0;
+       case PCI_SIZE_8:
+       case PCI_SIZE_16:
+               /*
+                * Some host bridges only decode 32-bit accesses to their
+                * config space and silently corrupt neighbouring bytes on a
+                * narrower write. Widen the access to a 32-bit
+                * read-modify-write of the word containing the requested
+                * offset.
+                */
+               shift = (offset % 4) * BITS_PER_BYTE;
+               mask = pci_get_ff(size) << shift;
+               tmp = readl(address) & ~mask;
+               tmp |= (value << shift) & mask;
+               writel(tmp, address);
+               return 0;
+       default:
+               return -EINVAL;
+       }
+}
+
 int pci_generic_mmap_read_config(
        const struct udevice *bus,
        int (*addr_f)(const struct udevice *bus, pci_dev_t bdf, uint offset,
diff --git a/include/pci.h b/include/pci.h
index 4b0facd6dcf..108d97a5d54 100644
--- a/include/pci.h
+++ b/include/pci.h
@@ -1151,6 +1151,32 @@ int pci_generic_mmap_write_config(
        ulong value,
        enum pci_size_t size);
 
+/**
+ * pci_generic_mmap_write_config32() - Generic helper for writing to
+ * memory-mapped PCI configuration space that only supports 32-bit accesses.
+ * @bus: Pointer to the PCI bus
+ * @addr_f: Callback for calculating the config space address
+ * @bdf: Identifies the PCI device to access
+ * @offset: The offset into the device's configuration space
+ * @value: The value to write
+ * @size: Indicates the size of access to perform
+ *
+ * Like pci_generic_mmap_write_config(), but for host bridges whose
+ * configuration space does not decode 8-bit or 16-bit accesses correctly.
+ * Accesses narrower than 32 bits are promoted to a 32-bit
+ * read-modify-write of the word containing @offset.
+ *
+ * Return: 0 on success, else -EINVAL
+ */
+int pci_generic_mmap_write_config32(
+       const struct udevice *bus,
+       int (*addr_f)(const struct udevice *bus, pci_dev_t bdf, uint offset,
+                     void **addrp),
+       pci_dev_t bdf,
+       uint offset,
+       ulong value,
+       enum pci_size_t size);
+
 /**
  * pci_generic_mmap_read_config() - Generic helper for reading from
  * memory-mapped PCI configuration space.
-- 
2.43.7

Reply via email to