From 5993af454fca84d1401d12eabc3c714b6b5dd953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Beh=C3=BAn?= Date: Thu, 11 Apr 2024 11:11:47 +0200 Subject: [PATCH] fix(plat/marvell/a3k): reset GIC before resetting via CM3 secure coprocessor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add code that acknowledges all SoC interrupts and resets the Generic Interrupt Controller before resetting the SoC via the Cortex-M3 secure coprocessor. Recall that Turris MOX has a HW bug wherein a SoC reset initiated by writing the magic value to the North Bridge Warm Reset register may randomly freeze the board. Back in 2021 we introduced the CM3_SYSTEM_RESET build option for the Armada 3700 platform, which, when enabled, adds code to the PSCI reset handler so that the SoC reset is done by requesting the firmware in the Cortex-M3 secure coprocessor to do it, instead of writing the Warm Reset register. The secure coprocessor firmware tried various things to put the board into a state where the SoC reset circuit would work correctly. This managed to fix the issue for some boards, but not for all of them. Another considered method to overcome this issue was to reset all the SoC peripheral controllers one by one by writing to specific registers, instead of triggering the SoC reset circuit via the Warm Reset register. This method was not used because until now, there was one peripheral that I could not find a way how to reset properly: the Generic Interrupt Controller (GIC). After 3 years I have finally found a way how to reset the GIC, and it needs to be done by the main processor, before the secure coprocessor resets the main processor. Change-Id: Icc23251ef97738b6b48af514d5118440ec21cdd7 Signed-off-by: Marek BehĂșn --- .../armada/a3k/common/cm3_system_reset.c | 132 ++++++++++++++++++ .../armada/a3k/common/include/a3700_pm.h | 2 + plat/marvell/armada/a3k/common/plat_pm.c | 2 +- 3 files changed, 135 insertions(+), 1 deletion(-) diff --git a/plat/marvell/armada/a3k/common/cm3_system_reset.c b/plat/marvell/armada/a3k/common/cm3_system_reset.c index f105d5990..030a614c0 100644 --- a/plat/marvell/armada/a3k/common/cm3_system_reset.c +++ b/plat/marvell/armada/a3k/common/cm3_system_reset.c @@ -8,11 +8,22 @@ #include #include +#include +#include #include #include +#include +#include +#include #include +/* IO Decoder Error Interrupt Status Registers */ +#define MVEBU_DEC_WIN_REGS_BASE(p) (MVEBU_REGS_BASE + 0xC000 + \ + (p) * 0x100) +#define MVEBU_DEC_WIN_ERR_INT_STS_REG(p) (MVEBU_DEC_WIN_REGS_BASE(p) + \ + 0xF8) + /* Cortex-M3 Secure Processor Mailbox Registers */ #define MVEBU_RWTM_PARAM0_REG (MVEBU_RWTM_REG_BASE) #define MVEBU_RWTM_CMD_REG (MVEBU_RWTM_REG_BASE + 0x40) @@ -23,6 +34,122 @@ #define MVEBU_RWTM_REBOOT_CMD 0x0009 #define MVEBU_RWTM_REBOOT_MAGIC 0xDEADBEEF +static inline uint32_t a3700_gicd_read(uint32_t reg) +{ + return mmio_read_32(PLAT_MARVELL_GICD_BASE + reg); +} + +static inline void a3700_gicd_write(uint32_t reg, uint32_t value) +{ + mmio_write_32(PLAT_MARVELL_GICD_BASE + reg, value); +} + +static void a3700_gicd_ctlr_clear_bits(uint32_t bits) +{ + uint32_t val; + + val = a3700_gicd_read(GICD_CTLR); + if ((val & bits) != 0U) { + a3700_gicd_write(GICD_CTLR, val & ~bits); + mdelay(1); + + if ((a3700_gicd_read(GICD_CTLR) & GICD_CTLR_RWP_BIT) != 0U) { + ERROR("could not clear bits 0x%x in GIC distributor control\n", + bits); + } + } +} + +static void a3700_gic_dist_disable_irqs(void) +{ + int i; + + for (i = 32; i < 224; i += 32) { + a3700_gicd_write(GICD_ICENABLER + (i >> 3), GENMASK_32(31, 0)); + } +} + +static inline uintptr_t a3700_rdist_base(unsigned int proc) +{ + return PLAT_MARVELL_GICR_BASE + (proc << GICR_V3_PCPUBASE_SHIFT); +} + +static inline uint32_t a3700_gicr_read(unsigned int proc, uint32_t reg) +{ + return mmio_read_32(a3700_rdist_base(proc) + reg); +} + +static inline void a3700_gicr_write(unsigned int proc, uint32_t reg, + uint32_t value) +{ + mmio_write_32(a3700_rdist_base(proc) + reg, value); +} + +static void a3700_gic_redist_disable_irqs(unsigned int proc) +{ + a3700_gicr_write(proc, GICR_ICENABLER0, GENMASK_32(31, 0)); + mdelay(1); + + if ((a3700_gicr_read(proc, GICR_CTLR) & GICR_CTLR_RWP_BIT) != 0U) { + ERROR("could not disable core %u PPIs & SGIs\n", proc); + } +} + +static void a3700_gic_redist_mark_asleep(unsigned int proc) +{ + a3700_gicr_write(proc, GICR_WAKER, + a3700_gicr_read(proc, GICR_WAKER) | WAKER_PS_BIT); + mdelay(1); + + if ((a3700_gicr_read(proc, GICR_WAKER) & WAKER_CA_BIT) == 0U) { + ERROR("could not mark core %u redistributor asleep\n", proc); + } +} + +static void a3700_io_addr_dec_ack_err_irq(void) +{ + unsigned int periph; + + for (periph = 0; periph < 16; ++periph) { + /* periph 6 does not exist */ + if (periph == 6) + continue; + + mmio_write_32(MVEBU_DEC_WIN_ERR_INT_STS_REG(periph), + GENMASK_32(1, 0)); + } +} + +static void a3700_gic_reset(void) +{ + a3700_gic_redist_disable_irqs(0); + a3700_gic_redist_disable_irqs(1); + + a3700_gic_redist_mark_asleep(0); + a3700_gic_redist_mark_asleep(1); + + a3700_io_addr_dec_ack_err_irq(); + + a3700_pm_ack_irq(); + + a3700_gic_dist_disable_irqs(); + + a3700_gicd_ctlr_clear_bits(CTLR_ENABLE_G0_BIT | CTLR_ENABLE_G1NS_BIT | + CTLR_ENABLE_G1S_BIT); + + /* Clearing ARE_S and ARE_NS bits is undefined in the specification, but + * works if the previous operations are successful. We need to do it in + * order to put GIC into the same state it was in just after reset. If + * this is successful, the rWTM firmware in the secure coprocessor will + * reset all other peripherals one by one, load new firmware and boot + * it, all without triggering the true warm reset via the WARM_RESET + * register (which may hang the board). + */ + + a3700_gicd_ctlr_clear_bits(CTLR_ARE_S_BIT); + a3700_gicd_ctlr_clear_bits(CTLR_ARE_NS_BIT); +} + static inline bool rwtm_completed(void) { return (mmio_read_32(MVEBU_RWTM_HOST_INT_RESET_REG) & @@ -43,6 +170,11 @@ void cm3_system_reset(void) { int tries = 5; + /* Put GIC into the same state it was just after reset. This is needed + * for the reset issue workaround to work. + */ + a3700_gic_reset(); + for (; tries > 0; --tries) { mmio_clrbits_32(MVEBU_RWTM_HOST_INT_RESET_REG, MVEBU_RWTM_HOST_INT_SP_COMPLETE); diff --git a/plat/marvell/armada/a3k/common/include/a3700_pm.h b/plat/marvell/armada/a3k/common/include/a3700_pm.h index 44dbb9f7d..1be82b2b3 100644 --- a/plat/marvell/armada/a3k/common/include/a3700_pm.h +++ b/plat/marvell/armada/a3k/common/include/a3700_pm.h @@ -48,6 +48,8 @@ struct pm_wake_up_src_config { struct pm_wake_up_src_config *mv_wake_up_src_config_get(void); +void a3700_pm_ack_irq(void); + void cm3_system_reset(void); #endif /* A3700_PM_H */ diff --git a/plat/marvell/armada/a3k/common/plat_pm.c b/plat/marvell/armada/a3k/common/plat_pm.c index e2d15abf9..d573b79fc 100644 --- a/plat/marvell/armada/a3k/common/plat_pm.c +++ b/plat/marvell/armada/a3k/common/plat_pm.c @@ -197,7 +197,7 @@ void marvell_psci_arch_init(int die_index) { } -static void a3700_pm_ack_irq(void) +void a3700_pm_ack_irq(void) { uint32_t reg;