Skip to content

Commit 6e36203

Browse files
Marek VasutLorenzo Pieralisi
authored andcommitted
PCI: rcar: Use PCI_SET_ERROR_RESPONSE after read which triggered an exception
In case the controller is transitioning to L1 in rcar_pcie_config_access(), any read/write access to PCIECDR triggers asynchronous external abort. This is because the transition to L1 link state must be manually finished by the driver. The PCIe IP can transition back from L1 state to L0 on its own. The current asynchronous external abort hook implementation restarts the instruction which finally triggered the fault, which can be a different instruction than the read/write instruction which started the faulting access. Usually the instruction which finally triggers the fault is one which has some data dependency on the result of the read/write. In case of read, the read value after fixup is undefined, while a read value of faulting read should be PCI_ERROR_RESPONSE. It is possible to enforce the fault using 'isb' instruction placed right after the read/write instruction which started the faulting access. Add custom register accessors which perform the read/write followed immediately by 'isb'. This way, the fault always happens on the 'isb' and in case of read, which is located one instruction before the 'isb', it is now possible to fix up the return value of the read in the asynchronous external abort hook and make that read return PCI_ERROR_RESPONSE. Link: https://lore.kernel.org/r/20220312212349.781799-2-marek.vasut@gmail.com Tested-by: Geert Uytterhoeven <geert+renesas@glider.be> Signed-off-by: Marek Vasut <marek.vasut+renesas@gmail.com> Signed-off-by: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Reviewed-by: Arnd Bergmann <arnd@arndb.de> Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be> Cc: Arnd Bergmann <arnd@arndb.de> Cc: Bjorn Helgaas <bhelgaas@google.com> Cc: Geert Uytterhoeven <geert+renesas@glider.be> Cc: Krzysztof Wilczyński <kw@linux.com> Cc: Lorenzo Pieralisi <lorenzo.pieralisi@arm.com> Cc: Wolfram Sang <wsa@the-dreams.de> Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> Cc: linux-renesas-soc@vger.kernel.org
1 parent 84b5761 commit 6e36203

1 file changed

Lines changed: 52 additions & 4 deletions

File tree

drivers/pci/controller/pcie-rcar-host.c

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,54 @@ static u32 rcar_read_conf(struct rcar_pcie *pcie, int where)
114114
return val >> shift;
115115
}
116116

117+
#ifdef CONFIG_ARM
118+
#define __rcar_pci_rw_reg_workaround(instr) \
119+
" .arch armv7-a\n" \
120+
"1: " instr " %1, [%2]\n" \
121+
"2: isb\n" \
122+
"3: .pushsection .text.fixup,\"ax\"\n" \
123+
" .align 2\n" \
124+
"4: mov %0, #" __stringify(PCIBIOS_SET_FAILED) "\n" \
125+
" b 3b\n" \
126+
" .popsection\n" \
127+
" .pushsection __ex_table,\"a\"\n" \
128+
" .align 3\n" \
129+
" .long 1b, 4b\n" \
130+
" .long 2b, 4b\n" \
131+
" .popsection\n"
132+
#endif
133+
134+
static int rcar_pci_write_reg_workaround(struct rcar_pcie *pcie, u32 val,
135+
unsigned int reg)
136+
{
137+
int error = PCIBIOS_SUCCESSFUL;
138+
#ifdef CONFIG_ARM
139+
asm volatile(
140+
__rcar_pci_rw_reg_workaround("str")
141+
: "+r"(error):"r"(val), "r"(pcie->base + reg) : "memory");
142+
#else
143+
rcar_pci_write_reg(pcie, val, reg);
144+
#endif
145+
return error;
146+
}
147+
148+
static int rcar_pci_read_reg_workaround(struct rcar_pcie *pcie, u32 *val,
149+
unsigned int reg)
150+
{
151+
int error = PCIBIOS_SUCCESSFUL;
152+
#ifdef CONFIG_ARM
153+
asm volatile(
154+
__rcar_pci_rw_reg_workaround("ldr")
155+
: "+r"(error), "=r"(*val) : "r"(pcie->base + reg) : "memory");
156+
157+
if (error != PCIBIOS_SUCCESSFUL)
158+
PCI_SET_ERROR_RESPONSE(val);
159+
#else
160+
*val = rcar_pci_read_reg(pcie, reg);
161+
#endif
162+
return error;
163+
}
164+
117165
/* Serialization is provided by 'pci_lock' in drivers/pci/access.c */
118166
static int rcar_pcie_config_access(struct rcar_pcie_host *host,
119167
unsigned char access_type, struct pci_bus *bus,
@@ -185,14 +233,14 @@ static int rcar_pcie_config_access(struct rcar_pcie_host *host,
185233
return PCIBIOS_DEVICE_NOT_FOUND;
186234

187235
if (access_type == RCAR_PCI_ACCESS_READ)
188-
*data = rcar_pci_read_reg(pcie, PCIECDR);
236+
ret = rcar_pci_read_reg_workaround(pcie, data, PCIECDR);
189237
else
190-
rcar_pci_write_reg(pcie, *data, PCIECDR);
238+
ret = rcar_pci_write_reg_workaround(pcie, *data, PCIECDR);
191239

192240
/* Disable the configuration access */
193241
rcar_pci_write_reg(pcie, 0, PCIECCTLR);
194242

195-
return PCIBIOS_SUCCESSFUL;
243+
return ret;
196244
}
197245

198246
static int rcar_pcie_read_conf(struct pci_bus *bus, unsigned int devfn,
@@ -1097,7 +1145,7 @@ static struct platform_driver rcar_pcie_driver = {
10971145
static int rcar_pcie_aarch32_abort_handler(unsigned long addr,
10981146
unsigned int fsr, struct pt_regs *regs)
10991147
{
1100-
return !!rcar_pcie_wakeup(pcie_dev, pcie_base);
1148+
return !fixup_exception(regs);
11011149
}
11021150

11031151
static const struct of_device_id rcar_pcie_abort_handler_of_match[] __initconst = {

0 commit comments

Comments
 (0)