aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRussell King <rmk+kernel@arm.linux.org.uk>2015-09-23 13:17:32 -0400
committerBjorn Helgaas <bhelgaas@google.com>2015-10-08 12:22:42 -0400
commit79e3f6ce167ff429084bfdd839e30d7983011108 (patch)
tree93ddb6d358ef95d2a694d442a73a74c06f1f4024
parent58c19a140de555b2bef41b8bf95439c36d555836 (diff)
PCI: mvebu: Use exact config access size; don't read/modify/write
The idea that you can arbitarily read 32-bits from PCI configuration space, modify a sub-field (like the command register) and write it back without consequence is deeply flawed. Status registers (such as the status register, PCIe device status register, etc) contain status bits which are read, write-one-to-clear. What this means is that reading 32-bits from the command register, modifying the command register, and then writing it back has the effect of clearing any status bits that were indicating at that time. Same for the PCIe device control register clearing bits in the PCIe device status register. Since the Armada chips support byte, 16-bit and 32-bit accesses to the registers (unless otherwise stated) and the PCI configuration data register does not specify otherwise, it seems logical that the chip can indeed generate the proper configuration access cycles down to byte level. Testing with an ASM1062 PCIe to SATA mini-PCIe card on Armada 388. PCIe capability at 0x80, DevCtl at 0x88, DevSta at 0x8a. Before: /# setpci -s 1:0.0 0x88.l - DevSta: CorrErr+ 00012810 /# setpci -s 1:0.0 0x88.w=0x2810 - Write DevCtl only /# setpci -s 1:0.0 0x88.l - CorrErr cleared - FAIL 00002810 After: /# setpci -s 1:0.0 0x88.l - DevSta: CorrErr+ 00012810 /# setpci -s 1:0.0 0x88.w=0x2810 - check DevCtl only write /# setpci -s 1:0.0 0x88.l - CorErr remains set 00012810 /# setpci -s 1:0.0 0x88.w=0x281f - check DevCtl write works /# setpci -s 1:0.0 0x88.l - devctl field updated 0001281f /# setpci -s 1:0.0 0x8a.w=0xffff - clear DevSta /# setpci -s 1:0.0 0x88.l - CorrErr now cleared 0000281f /# setpci -s 1:0.0 0x88.w=0x2810 - restore DevCtl /# setpci -s 1:0.0 0x88.l - check 00002810 [bhelgaas: changelog] Tested-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> (Armada XP GP) Tested-by: Andrew Lunn <andrew@lunn.ch> (Kirkwood DIR665) Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> Signed-off-by: Bjorn Helgaas <bhelgaas@google.com> Reviewed-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
-rw-r--r--drivers/pci/host/pci-mvebu.c43
1 files changed, 26 insertions, 17 deletions
diff --git a/drivers/pci/host/pci-mvebu.c b/drivers/pci/host/pci-mvebu.c
index b6a096bc9422..0d9f3eae4315 100644
--- a/drivers/pci/host/pci-mvebu.c
+++ b/drivers/pci/host/pci-mvebu.c
@@ -254,15 +254,22 @@ static int mvebu_pcie_hw_rd_conf(struct mvebu_pcie_port *port,
254 struct pci_bus *bus, 254 struct pci_bus *bus,
255 u32 devfn, int where, int size, u32 *val) 255 u32 devfn, int where, int size, u32 *val)
256{ 256{
257 void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
258
257 mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where), 259 mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where),
258 PCIE_CONF_ADDR_OFF); 260 PCIE_CONF_ADDR_OFF);
259 261
260 *val = mvebu_readl(port, PCIE_CONF_DATA_OFF); 262 switch (size) {
261 263 case 1:
262 if (size == 1) 264 *val = readb_relaxed(conf_data + (where & 3));
263 *val = (*val >> (8 * (where & 3))) & 0xff; 265 break;
264 else if (size == 2) 266 case 2:
265 *val = (*val >> (8 * (where & 3))) & 0xffff; 267 *val = readw_relaxed(conf_data + (where & 2));
268 break;
269 case 4:
270 *val = readl_relaxed(conf_data);
271 break;
272 }
266 273
267 return PCIBIOS_SUCCESSFUL; 274 return PCIBIOS_SUCCESSFUL;
268} 275}
@@ -271,22 +278,24 @@ static int mvebu_pcie_hw_wr_conf(struct mvebu_pcie_port *port,
271 struct pci_bus *bus, 278 struct pci_bus *bus,
272 u32 devfn, int where, int size, u32 val) 279 u32 devfn, int where, int size, u32 val)
273{ 280{
274 u32 _val, shift = 8 * (where & 3); 281 void __iomem *conf_data = port->base + PCIE_CONF_DATA_OFF;
275 282
276 mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where), 283 mvebu_writel(port, PCIE_CONF_ADDR(bus->number, devfn, where),
277 PCIE_CONF_ADDR_OFF); 284 PCIE_CONF_ADDR_OFF);
278 _val = mvebu_readl(port, PCIE_CONF_DATA_OFF);
279 285
280 if (size == 4) 286 switch (size) {
281 _val = val; 287 case 1:
282 else if (size == 2) 288 writeb(val, conf_data + (where & 3));
283 _val = (_val & ~(0xffff << shift)) | ((val & 0xffff) << shift); 289 break;
284 else if (size == 1) 290 case 2:
285 _val = (_val & ~(0xff << shift)) | ((val & 0xff) << shift); 291 writew(val, conf_data + (where & 2));
286 else 292 break;
293 case 4:
294 writel(val, conf_data);
295 break;
296 default:
287 return PCIBIOS_BAD_REGISTER_NUMBER; 297 return PCIBIOS_BAD_REGISTER_NUMBER;
288 298 }
289 mvebu_writel(port, _val, PCIE_CONF_DATA_OFF);
290 299
291 return PCIBIOS_SUCCESSFUL; 300 return PCIBIOS_SUCCESSFUL;
292} 301}