diff options
author | Stefan Agner <stefan@agner.ch> | 2018-11-28 14:08:40 +0100 |
---|---|---|
committer | Stefan Agner <stefan@agner.ch> | 2018-12-04 16:14:17 +0100 |
commit | 4958d8aeb74309bfc399f354f479d7a6ebcac013 (patch) | |
tree | 2c85001f980f1eca4cefa23b012a04746c89fbb0 /drivers/pci | |
parent | 3c9d8e9ac39e015ee510184df69dd90c38f104b7 (diff) |
pci: imx6: support kernels built in Thumb-2 mode
Add a fault handler which handles immediate reads in Thumb-2
mode. Install the appropriate handler depending on which mode
the kernel has been built. This avoids an "Unhandled fault:
external abort on non-linefetch (0x1008) at 0xf0a80000"
during boot on a device with a PCIe switch connected.
Link: https://lore.kernel.org/linux-pci/20181126161645.8177-1-stefan@agner.ch/
Signed-off-by: Stefan Agner <stefan@agner.ch>
Diffstat (limited to 'drivers/pci')
-rw-r--r-- | drivers/pci/controller/dwc/pci-imx6.c | 59 |
1 files changed, 58 insertions, 1 deletions
diff --git a/drivers/pci/controller/dwc/pci-imx6.c b/drivers/pci/controller/dwc/pci-imx6.c index df4e3dfa6a99..768937d7751b 100644 --- a/drivers/pci/controller/dwc/pci-imx6.c +++ b/drivers/pci/controller/dwc/pci-imx6.c @@ -27,6 +27,7 @@ #include <linux/types.h> #include <linux/interrupt.h> #include <linux/reset.h> +#include <asm/opcodes.h> #include "pcie-designware.h" @@ -292,6 +293,59 @@ static int imx6q_pcie_abort_handler(unsigned long addr, return 1; } +static int imx6q_pcie_abort_handler_thumb2(unsigned long addr, + unsigned int fsr, struct pt_regs *regs) +{ + unsigned long pc = instruction_pointer(regs); + unsigned long instr; + + if (user_mode(regs)) + return 1; + + instr = __mem_to_opcode_thumb32(*(unsigned long *)pc); + + if (__opcode_is_thumb32(instr)) { + /* Load word/byte and halfword immediate offset */ + if ((instr & 0xff100000UL) == 0xf8100000UL) { + int reg = (instr >> 12) & 0xf; + unsigned long val; + + if ((instr & 0x00700000UL) == 0x00100000UL) + val = 0xff; + else if ((instr & 0x00700000UL) == 0x00300000UL) + val = 0xffff; + else + val = 0xffffffffUL; + + regs->uregs[reg] = val; + regs->ARM_pc += 4; + return 0; + } + } else { + instr = __mem_to_opcode_thumb16(*(unsigned long *)pc); + + /* Load word/byte and halfword immediate offset */ + if (((instr & 0xe800) == 0x6800) || + ((instr & 0xf800) == 0x8800)) { + int reg = instr & 0x7; + unsigned long val; + + if (instr & 0x1000) + val = 0xff; + else if (instr & 0x8000) + val = 0xffff; + else + val = 0xffffffffUL; + + regs->uregs[reg] = val; + regs->ARM_pc += 2; + return 0; + } + } + + return 1; +} + static void imx6_pcie_assert_core_reset(struct imx6_pcie *imx6_pcie) { struct device *dev = imx6_pcie->pci->dev; @@ -874,6 +928,8 @@ static struct platform_driver imx6_pcie_driver = { static int __init imx6_pcie_init(void) { + bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL); + /* * Since probe() can be deferred we need to make sure that * hook_fault_code is not called after __init memory is freed @@ -881,7 +937,8 @@ static int __init imx6_pcie_init(void) * we can install the handler here without risking it * accessing some uninitialized driver state. */ - hook_fault_code(8, imx6q_pcie_abort_handler, SIGBUS, 0, + hook_fault_code(8, thumb2 ? imx6q_pcie_abort_handler_thumb2 : + imx6q_pcie_abort_handler, SIGBUS, 0, "external abort on non-linefetch"); return platform_driver_register(&imx6_pcie_driver); |