PCI: pci-bridge-emul: Add support for PCIe extended capabilities
authorRussell King <rmk+kernel@armlinux.org.uk>
Tue, 22 Feb 2022 15:50:20 +0000 (16:50 +0100)
committerLorenzo Pieralisi <lorenzo.pieralisi@arm.com>
Tue, 22 Feb 2022 16:04:19 +0000 (16:04 +0000)
Add support for PCIe extended capabilities, which we just redirect to the
emulating driver.

[pali: Fix writing new value with W1C bits]
Link: https://lore.kernel.org/r/20220222155030.988-3-pali@kernel.org
Signed-off-by: Russell King <rmk+kernel@armlinux.org.uk>
Signed-off-by: Pali Rohár <pali@kernel.org>
Signed-off-by: Marek Behún <kabel@kernel.org>
Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com>
drivers/pci/pci-bridge-emul.c
drivers/pci/pci-bridge-emul.h

index a956408..c4b9837 100644 (file)
@@ -437,10 +437,16 @@ int pci_bridge_emul_conf_read(struct pci_bridge_emul *bridge, int where,
                read_op = bridge->ops->read_pcie;
                cfgspace = (__le32 *) &bridge->pcie_conf;
                behavior = bridge->pcie_cap_regs_behavior;
-       } else {
-               /* Beyond our PCIe space */
+       } else if (reg < PCI_CFG_SPACE_SIZE) {
+               /* Rest of PCI space not implemented */
                *value = 0;
                return PCIBIOS_SUCCESSFUL;
+       } else {
+               /* PCIe extended capability space */
+               reg -= PCI_CFG_SPACE_SIZE;
+               read_op = bridge->ops->read_ext;
+               cfgspace = NULL;
+               behavior = NULL;
        }
 
        if (read_op)
@@ -448,15 +454,20 @@ int pci_bridge_emul_conf_read(struct pci_bridge_emul *bridge, int where,
        else
                ret = PCI_BRIDGE_EMUL_NOT_HANDLED;
 
-       if (ret == PCI_BRIDGE_EMUL_NOT_HANDLED)
-               *value = le32_to_cpu(cfgspace[reg / 4]);
+       if (ret == PCI_BRIDGE_EMUL_NOT_HANDLED) {
+               if (cfgspace)
+                       *value = le32_to_cpu(cfgspace[reg / 4]);
+               else
+                       *value = 0;
+       }
 
        /*
         * Make sure we never return any reserved bit with a value
         * different from 0.
         */
-       *value &= behavior[reg / 4].ro | behavior[reg / 4].rw |
-                 behavior[reg / 4].w1c;
+       if (behavior)
+               *value &= behavior[reg / 4].ro | behavior[reg / 4].rw |
+                         behavior[reg / 4].w1c;
 
        if (size == 1)
                *value = (*value >> (8 * (where & 3))) & 0xff;
@@ -502,8 +513,15 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where,
                write_op = bridge->ops->write_pcie;
                cfgspace = (__le32 *) &bridge->pcie_conf;
                behavior = bridge->pcie_cap_regs_behavior;
-       } else {
+       } else if (reg < PCI_CFG_SPACE_SIZE) {
+               /* Rest of PCI space not implemented */
                return PCIBIOS_SUCCESSFUL;
+       } else {
+               /* PCIe extended capability space */
+               reg -= PCI_CFG_SPACE_SIZE;
+               write_op = bridge->ops->write_ext;
+               cfgspace = NULL;
+               behavior = NULL;
        }
 
        shift = (where & 0x3) * 8;
@@ -517,29 +535,38 @@ int pci_bridge_emul_conf_write(struct pci_bridge_emul *bridge, int where,
        else
                return PCIBIOS_BAD_REGISTER_NUMBER;
 
-       /* Keep all bits, except the RW bits */
-       new = old & (~mask | ~behavior[reg / 4].rw);
+       if (behavior) {
+               /* Keep all bits, except the RW bits */
+               new = old & (~mask | ~behavior[reg / 4].rw);
 
-       /* Update the value of the RW bits */
-       new |= (value << shift) & (behavior[reg / 4].rw & mask);
+               /* Update the value of the RW bits */
+               new |= (value << shift) & (behavior[reg / 4].rw & mask);
 
-       /* Clear the W1C bits */
-       new &= ~((value << shift) & (behavior[reg / 4].w1c & mask));
+               /* Clear the W1C bits */
+               new &= ~((value << shift) & (behavior[reg / 4].w1c & mask));
+       } else {
+               new = old & ~mask;
+               new |= (value << shift) & mask;
+       }
 
-       /* Save the new value with the cleared W1C bits into the cfgspace */
-       cfgspace[reg / 4] = cpu_to_le32(new);
+       if (cfgspace) {
+               /* Save the new value with the cleared W1C bits into the cfgspace */
+               cfgspace[reg / 4] = cpu_to_le32(new);
+       }
 
-       /*
-        * Clear the W1C bits not specified by the write mask, so that the
-        * write_op() does not clear them.
-        */
-       new &= ~(behavior[reg / 4].w1c & ~mask);
+       if (behavior) {
+               /*
+                * Clear the W1C bits not specified by the write mask, so that the
+                * write_op() does not clear them.
+                */
+               new &= ~(behavior[reg / 4].w1c & ~mask);
 
-       /*
-        * Set the W1C bits specified by the write mask, so that write_op()
-        * knows about that they are to be cleared.
-        */
-       new |= (value << shift) & (behavior[reg / 4].w1c & mask);
+               /*
+                * Set the W1C bits specified by the write mask, so that write_op()
+                * knows about that they are to be cleared.
+                */
+               new |= (value << shift) & (behavior[reg / 4].w1c & mask);
+       }
 
        if (write_op)
                write_op(bridge, reg, old, new, mask);
index 4953274..6b5f75b 100644 (file)
@@ -90,6 +90,14 @@ struct pci_bridge_emul_ops {
         */
        pci_bridge_emul_read_status_t (*read_pcie)(struct pci_bridge_emul *bridge,
                                                   int reg, u32 *value);
+
+       /*
+        * Same as ->read_base(), except it is for reading from the
+        * PCIe extended capability configuration space.
+        */
+       pci_bridge_emul_read_status_t (*read_ext)(struct pci_bridge_emul *bridge,
+                                                 int reg, u32 *value);
+
        /*
         * Called when writing to the regular PCI bridge configuration
         * space. old is the current value, new is the new value being
@@ -105,6 +113,13 @@ struct pci_bridge_emul_ops {
         */
        void (*write_pcie)(struct pci_bridge_emul *bridge, int reg,
                           u32 old, u32 new, u32 mask);
+
+       /*
+        * Same as ->write_base(), except it is for writing from the
+        * PCIe extended capability configuration space.
+        */
+       void (*write_ext)(struct pci_bridge_emul *bridge, int reg,
+                         u32 old, u32 new, u32 mask);
 };
 
 struct pci_bridge_reg_behavior;