Files
opensbi/platform/generic/eswin/hfp.c
T
Bo Gan 7bff4e529e platform: generic: eswin: Add eic770x_hsm and fix warm reset issues
During warm reset, my EIC770X/Hifive Premier P550 can sometimes
encounter memory corruption issue crashing Linux boot. Currently the
issue is mitigated by having a sbi_printf before writing to the reset
register. I analyzed the issue further since then. From the SoC
datasheet[1], it's recommended to implement power-down flow as:

  a. Designate a primary core, and let it broadcast requests to other
     cores to execute a CEASE insn. Primary core also notifies an
     "Externel Agent" to start monitoring.
  b. Primary core waits for other cores to CEASE before it CEASEs.
  c. "External Agent" waits for primary core to CEASE before resets
     the Core Complex.

It's possible that EIC770X can trigger undefined behavior if the core
complex is reset while the harts are actively running. The sbi_printf
in the reset handler effectively hides the problem by delaying the
reset -- by the time sbi_printf finishes, all other harts will have
already landed in the loop in sbi_hsm_hart_wait(), which parks the hart.
Without the sbi_printf, I confirmed that other harts haven't reached
sbi_hsm_hart_wait yet before current hart resets the SoC. (by debugging)

To safely reset, and inspired by the datasheet, the warm reset logic
in eic770x.c now use the current hart as both primary core and the
"External Agent", and other harts as secondary cores. It leverages
the HSM framework and a new eic770x_hsm device to CEASE other harts,
and wait for them to CEASE before resets the SoC. with the sbi_printf
before reset removed, and this logic in place, stress test shows that
the memory corruption issue no longer occurs.

The new eic770x_hsm device is only used for the reset-CEASE logic at
the moment, and may be extended to a fully functional HSM device in
the future.

[1] https://github.com/eswincomputing/EIC7700X-SoC-Technical-Reference-Manual

Fixes: e5797e0688 ("platform: generic: eswin: add EIC7700")
Signed-off-by: Bo Gan <ganboing@gmail.com>
Reviewed-by: Anup Patel <anup@brainfault.org>
Link: https://lore.kernel.org/r/20260605075708.96-3-ganboing@gmail.com
Signed-off-by: Anup Patel <anup@brainfault.org>
2026-06-15 10:38:09 +05:30

121 lines
2.9 KiB
C

/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2025 Bo Gan <ganboing@gmail.com>
*
*/
#include <sbi/riscv_io.h>
#include <sbi/sbi_string.h>
#include <sbi/sbi_system.h>
#include <sbi/sbi_hart.h>
#include <sbi/sbi_ecall_interface.h>
#include <sbi_utils/serial/uart8250.h>
#include <sbi_utils/hsm/fdt_hsm_sifive_inst.h>
#include <eswin/eic770x.h>
#include <eswin/hfp.h>
/* HFP -> HiFive Premier P550 */
#define HFP_MCU_UART_PORT 2
#define HFP_MCU_UART_BAUDRATE 115200
static unsigned long eic770x_sysclk_rate(void)
{
/* syscfg clock is a mux of 24Mhz xtal clock and spll0_fout3/divisor */
uint32_t syscfg_clk = readl_relaxed((void*)EIC770X_SYSCRG_SYSCLK);
if (EIC770X_SYSCLK_SEL(syscfg_clk))
return EIC770X_XTAL_CLK_RATE;
return EIC770X_SPLL0_OUT3_RATE / EIC770X_SYSCLK_DIV(syscfg_clk);
}
static void eic770x_enable_uart_clk(unsigned port)
{
uint32_t lsp_clk_en = readl_relaxed((void*)EIC770X_SYSCRG_LSPCLK0);
lsp_clk_en |= EIC770X_UART_CLK_BIT(port);
writel(lsp_clk_en, (void*)EIC770X_SYSCRG_LSPCLK0);
}
static void hfp_send_bmc_msg(uint8_t type, uint8_t cmd,
const uint8_t *data, uint8_t len)
{
unsigned long sysclk_rate;
struct uart8250_device uart_dev;
union {
struct hfp_bmc_message msg;
char as_char[sizeof(struct hfp_bmc_message)];
} xmit = {{
.header_magic = MAGIC_HEADER,
.type = type,
.cmd = cmd,
.data_len = len,
.tail_magic = MAGIC_TAIL,
}};
/**
* Re-initialize UART.
* S-mode OS may have changed the clock frequency of syscfg clock
* which is the clock of all low speed peripherals, including UARTs.
* S-mode OS may also have disabled the UART2 clock via clock gate.
* (lsp_clk_en0 bit 17-21 controls UART0-4). Thus, we re-calculate
* the clock rate, enable UART clock, and re-initialize UART.
*/
sysclk_rate = eic770x_sysclk_rate();
eic770x_enable_uart_clk(HFP_MCU_UART_PORT);
uart8250_device_init(&uart_dev,
EIC770X_UART(HFP_MCU_UART_PORT),
sysclk_rate,
HFP_MCU_UART_BAUDRATE,
EIC770X_UART_REG_SHIFT,
EIC770X_UART_REG_WIDTH,
0, 0);
sbi_memcpy(&xmit.msg.data, data, len);
hfp_bmc_checksum_msg(&xmit.msg);
for (unsigned int i = 0; i < sizeof(xmit.as_char); i++)
uart8250_device_putc(&uart_dev, xmit.as_char[i]);
}
static int hfp_system_reset_check(u32 type, u32 reason)
{
switch (type) {
case SBI_SRST_RESET_TYPE_COLD_REBOOT:
case SBI_SRST_RESET_TYPE_SHUTDOWN:
return 255;
default:
return 0;
}
}
static void hfp_system_reset(u32 type, u32 reason)
{
eic770x_cease_other_harts();
switch (type) {
case SBI_SRST_RESET_TYPE_SHUTDOWN:
hfp_send_bmc_msg(HFP_MSG_NOTIFY, HFP_CMD_POWER_OFF,
NULL, 0);
break;
case SBI_SRST_RESET_TYPE_COLD_REBOOT:
hfp_send_bmc_msg(HFP_MSG_NOTIFY, HFP_CMD_RESTART,
NULL, 0);
break;
}
sifive_cease();
}
static struct sbi_system_reset_device hfp_reset = {
.name = "hfp_reset",
.system_reset_check = hfp_system_reset_check,
.system_reset = hfp_system_reset,
};
const struct eic770x_board_override hfp_override = {
.reset_dev = &hfp_reset,
};