From c5a7e5b3dd9de415b13599491d3239be2ab47c18 Mon Sep 17 00:00:00 2001 From: Pragnesh Patel Date: Mon, 24 Aug 2020 20:38:55 +0530 Subject: [PATCH 01/21] cmd: irq: disable CMD_IRQ for riscv arch For RISC-V arch, no need for CMD_IRQ so disable the same. Signed-off-by: Pragnesh Patel Reviewed-by: Heinrich Schuchardt Reviewed-by: Rick Chen Reviewed-by: Bin Meng --- cmd/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/Kconfig b/cmd/Kconfig index 0c984d735d..30c358d7e7 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -2235,7 +2235,7 @@ config CMD_DIAG config CMD_IRQ bool "irq - Show information about interrupts" - depends on !ARM && !MIPS && !SH + depends on !ARM && !MIPS && !RISCV && !SH help This enables two commands: From d53a95ba5e4443a4c512b03ae955c0c0b4b710fa Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sat, 5 Sep 2020 12:37:49 +0200 Subject: [PATCH 02/21] doc: correct kflash settings for Maix One Dock The correct kflash parameter value for the Maix One Dock is "dan". See: https://github.com/sipeed/platform-kendryte210/blob/master/boards/sipeed-maix-one-dock.json#L22 Fixes: 137dc153fda9 ("doc: riscv: Update documentation for Sipeed MAIX boards") Signed-off-by: Heinrich Schuchardt --- doc/board/sipeed/maix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/board/sipeed/maix.rst b/doc/board/sipeed/maix.rst index efcde9aebf..0b3a58070c 100644 --- a/doc/board/sipeed/maix.rst +++ b/doc/board/sipeed/maix.rst @@ -59,7 +59,7 @@ Sipeed MAIX BiT sipeed_maix_bitm_defconfig bit first Sipeed MAIX BiT with Mic sipeed_maix_bitm_defconfig bit_mic first Sipeed MAIXDUINO sipeed_maix_bitm_defconfig maixduino first Sipeed MAIX GO goE second -Sipeed MAIX ONE DOCK goD first +Sipeed MAIX ONE DOCK dan first ======================== ========================== ========== ========== Flashing causes a reboot of the device. Parameter -t specifies that the serial From 08bff30d5d15ddeb1da82d112b8b98d1cac06889 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sat, 5 Sep 2020 12:46:46 +0200 Subject: [PATCH 03/21] doc/sipeed/maix: describe RESET and BOOT button In the boot flow description add the RESET and BOOT button as well as the function of the DTR and RTS lines of the serial interface. Signed-off-by: Heinrich Schuchardt --- doc/board/sipeed/maix.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/board/sipeed/maix.rst b/doc/board/sipeed/maix.rst index 0b3a58070c..3f791b42fa 100644 --- a/doc/board/sipeed/maix.rst +++ b/doc/board/sipeed/maix.rst @@ -285,11 +285,15 @@ Technical Details Boot Sequence ^^^^^^^^^^^^^ -1. ``RESET`` pin is deasserted. +1. ``RESET`` pin is deasserted. The pin is connected to the ``RESET`` button. It + can also be set to low via either the ``DTR`` or the ``RTS`` line of the + serial interface (depending on the board). 2. Both harts begin executing at ``0x00001000``. 3. Both harts jump to firmware at ``0x88000000``. 4. One hart is chosen as a boot hart. -5. Firmware reads value of pin ``IO_16`` (ISP). +5. Firmware reads the value of pin ``IO_16`` (ISP). This pin is connected to the + ``BOOT`` button. The pin can equally be set to low via either the ``DTR`` or + ``RTS`` line of the serial interface (depending on the board). * If the pin is low, enter ISP mode. This mode allows loading data to ram, writing it to flash, and booting from specific addresses. From f8c9660bfe17100a27ca4cf28f957a25cb420255 Mon Sep 17 00:00:00 2001 From: Bin Meng Date: Tue, 15 Sep 2020 16:05:06 +0800 Subject: [PATCH 04/21] ram: sifive: Check return value on clk_enable() The return value should be checked otherwise it's useless to assign the return value to 'ret'. Signed-off-by: Bin Meng --- drivers/ram/sifive/fu540_ddr.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/ram/sifive/fu540_ddr.c b/drivers/ram/sifive/fu540_ddr.c index 5ff88692a8..f5b2873b33 100644 --- a/drivers/ram/sifive/fu540_ddr.c +++ b/drivers/ram/sifive/fu540_ddr.c @@ -369,6 +369,11 @@ static int fu540_ddr_probe(struct udevice *dev) } ret = clk_enable(&priv->ddr_clk); + if (ret < 0) { + debug("Could not enable DDR clock\n"); + return ret; + } + priv->ctl = regmap_get_range(map, 0); priv->phy = regmap_get_range(map, 1); priv->physical_filter_ctrl = regmap_get_range(map, 2); From 9981a8009e17fd51b74a4a58ba436998ebbf81ed Mon Sep 17 00:00:00 2001 From: Bin Meng Date: Tue, 15 Sep 2020 16:05:07 +0800 Subject: [PATCH 05/21] ram: sifive: Remove regmap dependency The usage of regmap API in the SiFive RAM driver is not correct. The reg address should be obtained via dev_read_addr_index() API. Signed-off-by: Bin Meng --- drivers/ram/sifive/fu540_ddr.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/ram/sifive/fu540_ddr.c b/drivers/ram/sifive/fu540_ddr.c index f5b2873b33..60d4945f84 100644 --- a/drivers/ram/sifive/fu540_ddr.c +++ b/drivers/ram/sifive/fu540_ddr.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -339,17 +338,12 @@ static int fu540_ddr_probe(struct udevice *dev) priv->info.size = gd->ram_size; #if defined(CONFIG_SPL_BUILD) - struct regmap *map; int ret; u32 clock = 0; debug("FU540 DDR probe\n"); priv->dev = dev; - ret = regmap_init_mem(dev_ofnode(dev), &map); - if (ret) - return ret; - ret = clk_get_by_index(dev, 0, &priv->ddr_clk); if (ret) { debug("clk get failed %d\n", ret); @@ -374,9 +368,9 @@ static int fu540_ddr_probe(struct udevice *dev) return ret; } - priv->ctl = regmap_get_range(map, 0); - priv->phy = regmap_get_range(map, 1); - priv->physical_filter_ctrl = regmap_get_range(map, 2); + priv->ctl = (struct fu540_ddrctl *)dev_read_addr_index(dev, 0); + priv->phy = (struct fu540_ddrphy *)dev_read_addr_index(dev, 1); + priv->physical_filter_ctrl = (u32 *)dev_read_addr_index(dev, 2); return fu540_ddr_setup(dev); #endif From c33efafaf949ef11fc525cd5be018ea48c40898c Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 28 Sep 2020 10:52:21 -0400 Subject: [PATCH 06/21] riscv: Rework riscv timer driver to only support S-mode The riscv-timer driver currently serves as a shim for several riscv timer drivers. This is not too desirable because it bypasses the usual timer selection via the driver model. There is no easy way to specify an alternate timing driver, or have the tick rate depend on the cpu's configured frequency. The timer drivers also do not have device structs, and so have to rely on storing parameters in gd_t. Lastly, there is no initialization call, so driver init is done in the same function which reads the time. This can result in confusing error messages. To a user, it looks like the driver failed when trying to read the time, whereas it may have failed while initializing. This patch removes the shim functionality from the riscv-timer driver, and has it instead implement the former rdtime.c timer driver. This is because existing u-boot users who pass in a device tree (e.g. qemu) do not create a timer device for S-mode u-boot. The existing behavior of creating the riscv-timer device in the riscv cpu driver must be kept. The actual reading of the CSRs has been redone in the style of Linux's get_cycles64. Signed-off-by: Sean Anderson Reviewed-by: Bin Meng Reviewed-by: Rick Chen --- arch/riscv/Kconfig | 8 ------- arch/riscv/cpu/ax25/Kconfig | 2 +- arch/riscv/cpu/fu540/Kconfig | 2 +- arch/riscv/cpu/generic/Kconfig | 2 +- arch/riscv/lib/Makefile | 1 - arch/riscv/lib/rdtime.c | 38 --------------------------------- drivers/timer/Kconfig | 4 ++-- drivers/timer/riscv_timer.c | 39 +++++++++++++++++----------------- 8 files changed, 25 insertions(+), 71 deletions(-) delete mode 100644 arch/riscv/lib/rdtime.c diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index 009a545fcf..21e6690f4d 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -185,14 +185,6 @@ config ANDES_PLMT The Andes PLMT block holds memory-mapped mtime register associated with timer tick. -config RISCV_RDTIME - bool - default y if RISCV_SMODE || SPL_RISCV_SMODE - help - The provides the riscv_get_time() API that is implemented using the - standard rdtime instruction. This is the case for S-mode U-Boot, and - is useful for processors that support rdtime in M-mode too. - config SYS_MALLOC_F_LEN default 0x1000 diff --git a/arch/riscv/cpu/ax25/Kconfig b/arch/riscv/cpu/ax25/Kconfig index 8d8d71dcbf..5cb5bb51eb 100644 --- a/arch/riscv/cpu/ax25/Kconfig +++ b/arch/riscv/cpu/ax25/Kconfig @@ -3,7 +3,7 @@ config RISCV_NDS select ARCH_EARLY_INIT_R imply CPU imply CPU_RISCV - imply RISCV_TIMER + imply RISCV_TIMER if (RISCV_SMODE || SPL_RISCV_SMODE) imply ANDES_PLIC if (RISCV_MMODE || SPL_RISCV_MMODE) imply ANDES_PLMT if (RISCV_MMODE || SPL_RISCV_MMODE) imply SPL_CPU_SUPPORT diff --git a/arch/riscv/cpu/fu540/Kconfig b/arch/riscv/cpu/fu540/Kconfig index 53e19635c8..ac3f183342 100644 --- a/arch/riscv/cpu/fu540/Kconfig +++ b/arch/riscv/cpu/fu540/Kconfig @@ -10,7 +10,7 @@ config SIFIVE_FU540 select SPL_RAM if SPL imply CPU imply CPU_RISCV - imply RISCV_TIMER + imply RISCV_TIMER if (RISCV_SMODE || SPL_RISCV_SMODE) imply SIFIVE_CLINT if (RISCV_MMODE || SPL_RISCV_MMODE) imply CMD_CPU imply SPL_CPU_SUPPORT diff --git a/arch/riscv/cpu/generic/Kconfig b/arch/riscv/cpu/generic/Kconfig index b2cb155d6d..f4c2e2643c 100644 --- a/arch/riscv/cpu/generic/Kconfig +++ b/arch/riscv/cpu/generic/Kconfig @@ -7,7 +7,7 @@ config GENERIC_RISCV select ARCH_EARLY_INIT_R imply CPU imply CPU_RISCV - imply RISCV_TIMER + imply RISCV_TIMER if (RISCV_SMODE || SPL_RISCV_SMODE) imply SIFIVE_CLINT if (RISCV_MMODE || SPL_RISCV_MMODE) imply CMD_CPU imply SPL_CPU_SUPPORT diff --git a/arch/riscv/lib/Makefile b/arch/riscv/lib/Makefile index 6c503ff2b2..10ac5b06d3 100644 --- a/arch/riscv/lib/Makefile +++ b/arch/riscv/lib/Makefile @@ -15,7 +15,6 @@ obj-$(CONFIG_SIFIVE_CLINT) += sifive_clint.o obj-$(CONFIG_ANDES_PLIC) += andes_plic.o obj-$(CONFIG_ANDES_PLMT) += andes_plmt.o else -obj-$(CONFIG_RISCV_RDTIME) += rdtime.o obj-$(CONFIG_SBI) += sbi.o obj-$(CONFIG_SBI_IPI) += sbi_ipi.o endif diff --git a/arch/riscv/lib/rdtime.c b/arch/riscv/lib/rdtime.c deleted file mode 100644 index e128d7fce6..0000000000 --- a/arch/riscv/lib/rdtime.c +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Copyright (C) 2018, Anup Patel - * Copyright (C) 2018, Bin Meng - * - * The riscv_get_time() API implementation that is using the - * standard rdtime instruction. - */ - -#include - -/* Implement the API required by RISC-V timer driver */ -int riscv_get_time(u64 *time) -{ -#ifdef CONFIG_64BIT - u64 n; - - __asm__ __volatile__ ( - "rdtime %0" - : "=r" (n)); - - *time = n; -#else - u32 lo, hi, tmp; - - __asm__ __volatile__ ( - "1:\n" - "rdtimeh %0\n" - "rdtime %1\n" - "rdtimeh %2\n" - "bne %0, %2, 1b" - : "=&r" (hi), "=&r" (lo), "=&r" (tmp)); - - *time = ((u64)hi << 32) | lo; -#endif - - return 0; -} diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig index 637024445c..d40d313011 100644 --- a/drivers/timer/Kconfig +++ b/drivers/timer/Kconfig @@ -146,8 +146,8 @@ config RISCV_TIMER bool "RISC-V timer support" depends on TIMER && RISCV help - Select this to enable support for the timer as defined - by the RISC-V privileged architecture spec. + Select this to enable support for a generic RISC-V S-Mode timer + driver. config ROCKCHIP_TIMER bool "Rockchip timer support" diff --git a/drivers/timer/riscv_timer.c b/drivers/timer/riscv_timer.c index 9f9f070e0b..449fcfcfd5 100644 --- a/drivers/timer/riscv_timer.c +++ b/drivers/timer/riscv_timer.c @@ -1,36 +1,37 @@ // SPDX-License-Identifier: GPL-2.0+ /* + * Copyright (C) 2020, Sean Anderson * Copyright (C) 2018, Bin Meng + * Copyright (C) 2018, Anup Patel + * Copyright (C) 2012 Regents of the University of California * - * RISC-V privileged architecture defined generic timer driver + * RISC-V architecturally-defined generic timer driver * - * This driver relies on RISC-V platform codes to provide the essential API - * riscv_get_time() which is supposed to return the timer counter as defined - * by the RISC-V privileged architecture spec. - * - * This driver can be used in both M-mode and S-mode U-Boot. + * This driver provides generic timer support for S-mode U-Boot. */ #include #include #include #include -#include - -/** - * riscv_get_time() - get the timer counter - * - * Platform codes should provide this API in order to make this driver function. - * - * @time: the 64-bit timer count as defined by the RISC-V privileged - * architecture spec. - * @return: 0 on success, -ve on error. - */ -extern int riscv_get_time(u64 *time); +#include static int riscv_timer_get_count(struct udevice *dev, u64 *count) { - return riscv_get_time(count); + if (IS_ENABLED(CONFIG_64BIT)) { + *count = csr_read(CSR_TIME); + } else { + u32 hi, lo; + + do { + hi = csr_read(CSR_TIMEH); + lo = csr_read(CSR_TIME); + } while (hi != csr_read(CSR_TIMEH)); + + *count = ((u64)hi << 32) | lo; + } + + return 0; } static int riscv_timer_probe(struct udevice *dev) From 3576121687965ffe580fc44f5dd1d8e9ab434c5b Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 28 Sep 2020 10:52:22 -0400 Subject: [PATCH 07/21] timer: Add helper for drivers using timebase fallback This function is designed to be used when a timer used to be initialized by the cpu (e.g. RISC-V timers), but now is initialized by dm_timer_init. In such a case, the timer may prefer to use the clocks and clock-frequency properties, but should be able to fall back on using the cpu's timebase-frequency. Signed-off-by: Sean Anderson Reviewed-by: Simon Glass Reviewed-by: Bin Meng Reviewed-by: Rick Chen --- drivers/timer/timer-uclass.c | 31 +++++++++++++++++++++++++++++++ include/timer.h | 15 +++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/drivers/timer/timer-uclass.c b/drivers/timer/timer-uclass.c index 14dde950a1..e9802c8b43 100644 --- a/drivers/timer/timer-uclass.c +++ b/drivers/timer/timer-uclass.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -79,6 +80,36 @@ static int timer_post_probe(struct udevice *dev) return 0; } +/* + * TODO: should be CONFIG_IS_ENABLED(CPU), but the SPL config has _SUPPORT on + * the end... + */ +#if defined(CONFIG_CPU) || defined(CONFIG_SPL_CPU_SUPPORT) +int timer_timebase_fallback(struct udevice *dev) +{ + struct udevice *cpu; + struct cpu_platdata *cpu_plat; + struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev); + + /* Did we get our clock rate from the device tree? */ + if (uc_priv->clock_rate) + return 0; + + /* Fall back to timebase-frequency */ + dev_dbg(dev, "missing clocks or clock-frequency property; falling back on timebase-frequency\n"); + cpu = cpu_get_current_dev(); + if (!cpu) + return -ENODEV; + + cpu_plat = dev_get_parent_platdata(cpu); + if (!cpu_plat) + return -ENODEV; + + uc_priv->clock_rate = cpu_plat->timebase_freq; + return 0; +} +#endif + u64 timer_conv_64(u32 count) { /* increment tbh if tbl has rolled over */ diff --git a/include/timer.h b/include/timer.h index a49b500ce3..8b9fa51c53 100644 --- a/include/timer.h +++ b/include/timer.h @@ -15,6 +15,21 @@ */ int dm_timer_init(void); +/** + * timer_timebase_fallback() - Helper for timers using timebase fallback + * @dev: A timer partially-probed timer device + * + * This is a helper function designed for timers which need to fall back on the + * cpu's timebase. This function is designed to be called during the driver's + * probe(). If there is a clocks or clock-frequency property in the timer's + * binding, then it will be used. Otherwise, the timebase of the current cpu + * will be used. This is initialized by the cpu driver, and usually gotten from + * ``/cpus/timebase-frequency`` or ``/cpus/cpu@X/timebase-frequency``. + * + * Return: 0 if OK, or negative error code on failure + */ +int timer_timebase_fallback(struct udevice *dev); + /* * timer_conv_64 - convert 32-bit counter value to 64-bit * From 7616e3687e447b5a838f472afb5275fe6a841f5b Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 28 Sep 2020 10:52:23 -0400 Subject: [PATCH 08/21] timer: Add a test for timer_timebase_fallback To test this function, sandbox CPU must set cpu_platdata.timebase_freq on bind. It also needs to expose a method to set the current cpu. I also make some most members of cpu_sandbox_ops static. On the timer side, the device tree property sandbox,timebase-frequency-fallback controls whether sandbox_timer_probe falls back to time_timebase_fallback or to SANDBOX_TIMER_RATE. Signed-off-by: Sean Anderson Reviewed-by: Simon Glass --- arch/sandbox/dts/test.dts | 9 +++++++- arch/sandbox/include/asm/cpu.h | 11 ++++++++++ drivers/cpu/cpu_sandbox.c | 39 ++++++++++++++++++++++++++++------ drivers/timer/sandbox_timer.c | 4 +++- test/dm/timer.c | 27 ++++++++++++++++++++++- 5 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 arch/sandbox/include/asm/cpu.h diff --git a/arch/sandbox/dts/test.dts b/arch/sandbox/dts/test.dts index 9f45c48e4e..2f559265a5 100644 --- a/arch/sandbox/dts/test.dts +++ b/arch/sandbox/dts/test.dts @@ -533,7 +533,9 @@ }; cpus { + timebase-frequency = <2000000>; cpu-test1 { + timebase-frequency = <3000000>; compatible = "sandbox,cpu_sandbox"; u-boot,dm-pre-reloc; }; @@ -839,11 +841,16 @@ 0x58 8>; }; - timer { + timer@0 { compatible = "sandbox,timer"; clock-frequency = <1000000>; }; + timer@1 { + compatible = "sandbox,timer"; + sandbox,timebase-frequency-fallback; + }; + tpm2 { compatible = "sandbox,tpm2"; }; diff --git a/arch/sandbox/include/asm/cpu.h b/arch/sandbox/include/asm/cpu.h new file mode 100644 index 0000000000..c97ac7ba95 --- /dev/null +++ b/arch/sandbox/include/asm/cpu.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2020 Sean Anderson + */ + +#ifndef __SANDBOX_CPU_H +#define __SANDBOX_CPU_H + +void cpu_sandbox_set_current(const char *name); + +#endif /* __SANDBOX_CPU_H */ diff --git a/drivers/cpu/cpu_sandbox.c b/drivers/cpu/cpu_sandbox.c index caa26e50f2..4ba0d1b99e 100644 --- a/drivers/cpu/cpu_sandbox.c +++ b/drivers/cpu/cpu_sandbox.c @@ -8,14 +8,15 @@ #include #include -int cpu_sandbox_get_desc(const struct udevice *dev, char *buf, int size) +static int cpu_sandbox_get_desc(const struct udevice *dev, char *buf, int size) { snprintf(buf, size, "LEG Inc. SuperMegaUltraTurbo CPU No. 1"); return 0; } -int cpu_sandbox_get_info(const struct udevice *dev, struct cpu_info *info) +static int cpu_sandbox_get_info(const struct udevice *dev, + struct cpu_info *info) { info->cpu_freq = 42 * 42 * 42 * 42 * 42; info->features = 0x42424242; @@ -24,21 +25,29 @@ int cpu_sandbox_get_info(const struct udevice *dev, struct cpu_info *info) return 0; } -int cpu_sandbox_get_count(const struct udevice *dev) +static int cpu_sandbox_get_count(const struct udevice *dev) { return 42; } -int cpu_sandbox_get_vendor(const struct udevice *dev, char *buf, int size) +static int cpu_sandbox_get_vendor(const struct udevice *dev, char *buf, + int size) { snprintf(buf, size, "Languid Example Garbage Inc."); return 0; } -int cpu_sandbox_is_current(struct udevice *dev) +static const char *cpu_current = "cpu-test1"; + +void cpu_sandbox_set_current(const char *name) { - if (!strcmp(dev->name, "cpu-test1")) + cpu_current = name; +} + +static int cpu_sandbox_is_current(struct udevice *dev) +{ + if (!strcmp(dev->name, cpu_current)) return 1; return 0; @@ -52,7 +61,22 @@ static const struct cpu_ops cpu_sandbox_ops = { .is_current = cpu_sandbox_is_current, }; -int cpu_sandbox_probe(struct udevice *dev) +static int cpu_sandbox_bind(struct udevice *dev) +{ + int ret; + struct cpu_platdata *plat = dev_get_parent_platdata(dev); + + /* first examine the property in current cpu node */ + ret = dev_read_u32(dev, "timebase-frequency", &plat->timebase_freq); + /* if not found, then look at the parent /cpus node */ + if (ret) + ret = dev_read_u32(dev->parent, "timebase-frequency", + &plat->timebase_freq); + + return ret; +} + +static int cpu_sandbox_probe(struct udevice *dev) { return 0; } @@ -67,5 +91,6 @@ U_BOOT_DRIVER(cpu_sandbox) = { .id = UCLASS_CPU, .ops = &cpu_sandbox_ops, .of_match = cpu_sandbox_ids, + .bind = cpu_sandbox_bind, .probe = cpu_sandbox_probe, }; diff --git a/drivers/timer/sandbox_timer.c b/drivers/timer/sandbox_timer.c index 5228486082..6a503c2f15 100644 --- a/drivers/timer/sandbox_timer.c +++ b/drivers/timer/sandbox_timer.c @@ -40,7 +40,9 @@ static int sandbox_timer_probe(struct udevice *dev) { struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev); - if (!uc_priv->clock_rate) + if (dev_read_bool(dev, "sandbox,timebase-frequency-fallback")) + return timer_timebase_fallback(dev); + else if (!uc_priv->clock_rate) uc_priv->clock_rate = SANDBOX_TIMER_RATE; return 0; diff --git a/test/dm/timer.c b/test/dm/timer.c index 95dab97665..70043b9eee 100644 --- a/test/dm/timer.c +++ b/test/dm/timer.c @@ -7,8 +7,10 @@ #include #include #include +#include #include #include +#include /* * Basic test of the timer uclass. @@ -17,9 +19,32 @@ static int dm_test_timer_base(struct unit_test_state *uts) { struct udevice *dev; - ut_assertok(uclass_get_device(UCLASS_TIMER, 0, &dev)); + ut_assertok(uclass_get_device_by_name(UCLASS_TIMER, "timer@0", &dev)); ut_asserteq(1000000, timer_get_rate(dev)); return 0; } DM_TEST(dm_test_timer_base, UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT); + +/* + * Test of timebase fallback + */ +static int dm_test_timer_timebase_fallback(struct unit_test_state *uts) +{ + struct udevice *dev; + + cpu_sandbox_set_current("cpu-test1"); + ut_assertok(uclass_get_device_by_name(UCLASS_TIMER, "timer@1", &dev)); + ut_asserteq(3000000, timer_get_rate(dev)); + ut_assertok(device_remove(dev, DM_REMOVE_NORMAL)); + + cpu_sandbox_set_current("cpu-test2"); + ut_assertok(uclass_get_device_by_name(UCLASS_TIMER, "timer@1", &dev)); + ut_asserteq(2000000, timer_get_rate(dev)); + + cpu_sandbox_set_current("cpu-test1"); + + return 0; +} +DM_TEST(dm_test_timer_timebase_fallback, + UT_TESTF_SCAN_PDATA | UT_TESTF_SCAN_FDT); From e86463f8e3a5006b43985c474ac74d0caabd0fd4 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 28 Sep 2020 10:52:24 -0400 Subject: [PATCH 09/21] riscv: Rework Andes PLMT as a UCLASS_TIMER driver This converts the PLMT driver from the riscv-specific timer interface to be a DM-based UCLASS_TIMER driver. The clock-frequency/clocks properties are preferred over timebase-frequency for two reasons. First, properties which affect a device should be located near its binding in the device tree. Using timebase-frequency only really makes sense when the cpu itself is the timer device. This is the case when we read the time from a CSR, but not when there is a separate device. Second, it lets the device use the clock subsystem which adds flexibility. If the device is configured for a different clock speed, the timer can adjust itself. Signed-off-by: Sean Anderson Reviewed-by: Rick Chen --- arch/riscv/Kconfig | 4 --- arch/riscv/include/asm/global_data.h | 3 -- arch/riscv/include/asm/syscon.h | 4 +-- arch/riscv/lib/andes_plmt.c | 44 +++++++++++++--------------- 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index 21e6690f4d..d9155b9bab 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -177,10 +177,6 @@ config ANDES_PLIC config ANDES_PLMT bool depends on RISCV_MMODE || SPL_RISCV_MMODE - select REGMAP - select SYSCON - select SPL_REGMAP if SPL - select SPL_SYSCON if SPL help The Andes PLMT block holds memory-mapped mtime register associated with timer tick. diff --git a/arch/riscv/include/asm/global_data.h b/arch/riscv/include/asm/global_data.h index b711fcc44d..d3a0b1d221 100644 --- a/arch/riscv/include/asm/global_data.h +++ b/arch/riscv/include/asm/global_data.h @@ -24,9 +24,6 @@ struct arch_global_data { #ifdef CONFIG_ANDES_PLIC void __iomem *plic; /* plic base address */ #endif -#ifdef CONFIG_ANDES_PLMT - void __iomem *plmt; /* plmt base address */ -#endif #if CONFIG_IS_ENABLED(SMP) struct ipi_data ipi[CONFIG_NR_CPUS]; #endif diff --git a/arch/riscv/include/asm/syscon.h b/arch/riscv/include/asm/syscon.h index 26a008ca59..c3629e4b53 100644 --- a/arch/riscv/include/asm/syscon.h +++ b/arch/riscv/include/asm/syscon.h @@ -7,13 +7,13 @@ #define _ASM_SYSCON_H /* - * System controllers in a RISC-V system + * System controllers in a RISC-V system. These should only be used for + * identifying IPI controllers. Other devices should use DM to probe. */ enum { RISCV_NONE, RISCV_SYSCON_CLINT, /* Core Local Interruptor (CLINT) */ RISCV_SYSCON_PLIC, /* Platform Level Interrupt Controller (PLIC) */ - RISCV_SYSCON_PLMT, /* Platform Level Machine Timer (PLMT) */ }; #endif /* _ASM_SYSCON_H */ diff --git a/arch/riscv/lib/andes_plmt.c b/arch/riscv/lib/andes_plmt.c index a7e90ca992..a28c14c1eb 100644 --- a/arch/riscv/lib/andes_plmt.c +++ b/arch/riscv/lib/andes_plmt.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2019, Rick Chen + * Copyright (C) 2020, Sean Anderson * * U-Boot syscon driver for Andes's Platform Level Machine Timer (PLMT). * The PLMT block holds memory-mapped mtime register @@ -9,46 +10,43 @@ #include #include -#include -#include +#include #include -#include #include /* mtime register */ #define MTIME_REG(base) ((ulong)(base)) -DECLARE_GLOBAL_DATA_PTR; - -#define PLMT_BASE_GET(void) \ - do { \ - long *ret; \ - \ - if (!gd->arch.plmt) { \ - ret = syscon_get_first_range(RISCV_SYSCON_PLMT); \ - if (IS_ERR(ret)) \ - return PTR_ERR(ret); \ - gd->arch.plmt = ret; \ - } \ - } while (0) - -int riscv_get_time(u64 *time) +static int andes_plmt_get_count(struct udevice *dev, u64 *count) { - PLMT_BASE_GET(); - - *time = readq((void __iomem *)MTIME_REG(gd->arch.plmt)); + *count = readq((void __iomem *)MTIME_REG(dev->priv)); return 0; } +static const struct timer_ops andes_plmt_ops = { + .get_count = andes_plmt_get_count, +}; + +static int andes_plmt_probe(struct udevice *dev) +{ + dev->priv = dev_read_addr_ptr(dev); + if (!dev->priv) + return -EINVAL; + + return timer_timebase_fallback(dev); +} + static const struct udevice_id andes_plmt_ids[] = { - { .compatible = "riscv,plmt0", .data = RISCV_SYSCON_PLMT }, + { .compatible = "riscv,plmt0" }, { } }; U_BOOT_DRIVER(andes_plmt) = { .name = "andes_plmt", - .id = UCLASS_SYSCON, + .id = UCLASS_TIMER, .of_match = andes_plmt_ids, + .ops = &andes_plmt_ops, + .probe = andes_plmt_probe, .flags = DM_FLAG_PRE_RELOC, }; From 15943bb558d2fef6ae6d2713e252db17754d207d Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 28 Sep 2020 10:52:25 -0400 Subject: [PATCH 10/21] riscv: Clean up initialization in Andes PLIC This merges the PLIC initialization code from two functions into one. Signed-off-by: Sean Anderson Reviewed-by: Bin Meng --- arch/riscv/lib/andes_plic.c | 58 ++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/arch/riscv/lib/andes_plic.c b/arch/riscv/lib/andes_plic.c index c2a8fe4d9e..267d6a191b 100644 --- a/arch/riscv/lib/andes_plic.c +++ b/arch/riscv/lib/andes_plic.c @@ -41,53 +41,45 @@ static int enable_ipi(int hart) return 0; } -static int init_plic(void) +int riscv_init_ipi(void) { - struct udevice *dev; - ofnode node; int ret; + long *base = syscon_get_first_range(RISCV_SYSCON_PLIC); + ofnode node; + struct udevice *dev; u32 reg; + if (IS_ERR(base)) + return PTR_ERR(base); + gd->arch.plic = base; + ret = uclass_find_first_device(UCLASS_CPU, &dev); if (ret) return ret; + else if (!dev) + return -ENODEV; - if (dev) { - ofnode_for_each_subnode(node, dev_ofnode(dev->parent)) { - const char *device_type; + ofnode_for_each_subnode(node, dev_ofnode(dev->parent)) { + const char *device_type; - device_type = ofnode_read_string(node, "device_type"); - if (!device_type) - continue; + device_type = ofnode_read_string(node, "device_type"); + if (!device_type) + continue; - if (strcmp(device_type, "cpu")) - continue; + if (strcmp(device_type, "cpu")) + continue; - /* skip if hart is marked as not available */ - if (!ofnode_is_available(node)) - continue; + /* skip if hart is marked as not available */ + if (!ofnode_is_available(node)) + continue; - /* read hart ID of CPU */ - ret = ofnode_read_u32(node, "reg", ®); - if (ret == 0) - enable_ipi(reg); - } - - return 0; + /* read hart ID of CPU */ + ret = ofnode_read_u32(node, "reg", ®); + if (ret == 0) + enable_ipi(reg); } - return -ENODEV; -} - -int riscv_init_ipi(void) -{ - long *ret = syscon_get_first_range(RISCV_SYSCON_PLIC); - - if (IS_ERR(ret)) - return PTR_ERR(ret); - gd->arch.plic = ret; - - return init_plic(); + return 0; } int riscv_send_ipi(int hart) From e5ca9a752399c2701cb71527d198bfa78268580d Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 28 Sep 2020 10:52:26 -0400 Subject: [PATCH 11/21] riscv: Rework Sifive CLINT as UCLASS_TIMER driver This converts the clint driver from the riscv-specific interface to be a DM-based UCLASS_TIMER driver. In addition, the SiFive DDR driver previously implicitly depended on the CLINT to select REGMAP. Unlike Andes's PLMT/PLIC (which AFAIK never have anything pass it a dtb), the SiFive CLINT is part of the device tree passed in by qemu. This device tree doesn't have a clocks or clock-frequency property on clint, so we need to fall back on the timebase-frequency property. Perhaps in the future we can get a clock-frequency property added to the qemu dtb. Unlike with the Andes PLMT, the Sifive CLINT is also an IPI controller. RISCV_SYSCON_CLINT is retained for this purpose. Signed-off-by: Sean Anderson Reviewed-by: Pragnesh Patel --- arch/riscv/Kconfig | 4 --- arch/riscv/lib/sifive_clint.c | 66 +++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/arch/riscv/Kconfig b/arch/riscv/Kconfig index d9155b9bab..aaa3b833a5 100644 --- a/arch/riscv/Kconfig +++ b/arch/riscv/Kconfig @@ -155,10 +155,6 @@ config 64BIT config SIFIVE_CLINT bool depends on RISCV_MMODE || SPL_RISCV_MMODE - select REGMAP - select SYSCON - select SPL_REGMAP if SPL - select SPL_SYSCON if SPL help The SiFive CLINT block holds memory-mapped control and status registers associated with software and timer interrupts. diff --git a/arch/riscv/lib/sifive_clint.c b/arch/riscv/lib/sifive_clint.c index b9a2c649cc..c9704c596f 100644 --- a/arch/riscv/lib/sifive_clint.c +++ b/arch/riscv/lib/sifive_clint.c @@ -8,9 +8,9 @@ */ #include +#include #include -#include -#include +#include #include #include #include @@ -24,35 +24,19 @@ DECLARE_GLOBAL_DATA_PTR; -int riscv_get_time(u64 *time) -{ - /* ensure timer register base has a sane value */ - riscv_init_ipi(); - - *time = readq((void __iomem *)MTIME_REG(gd->arch.clint)); - - return 0; -} - -int riscv_set_timecmp(int hart, u64 cmp) -{ - /* ensure timer register base has a sane value */ - riscv_init_ipi(); - - writeq(cmp, (void __iomem *)MTIMECMP_REG(gd->arch.clint, hart)); - - return 0; -} - int riscv_init_ipi(void) { - if (!gd->arch.clint) { - long *ret = syscon_get_first_range(RISCV_SYSCON_CLINT); + int ret; + struct udevice *dev; - if (IS_ERR(ret)) - return PTR_ERR(ret); - gd->arch.clint = ret; - } + ret = uclass_get_device_by_driver(UCLASS_TIMER, + DM_GET_DRIVER(sifive_clint), &dev); + if (ret) + return ret; + + gd->arch.clint = dev_read_addr_ptr(dev); + if (!gd->arch.clint) + return -EINVAL; return 0; } @@ -78,14 +62,36 @@ int riscv_get_ipi(int hart, int *pending) return 0; } +static int sifive_clint_get_count(struct udevice *dev, u64 *count) +{ + *count = readq((void __iomem *)MTIME_REG(dev->priv)); + + return 0; +} + +static const struct timer_ops sifive_clint_ops = { + .get_count = sifive_clint_get_count, +}; + +static int sifive_clint_probe(struct udevice *dev) +{ + dev->priv = dev_read_addr_ptr(dev); + if (!dev->priv) + return -EINVAL; + + return timer_timebase_fallback(dev); +} + static const struct udevice_id sifive_clint_ids[] = { - { .compatible = "riscv,clint0", .data = RISCV_SYSCON_CLINT }, + { .compatible = "riscv,clint0" }, { } }; U_BOOT_DRIVER(sifive_clint) = { .name = "sifive_clint", - .id = UCLASS_SYSCON, + .id = UCLASS_TIMER, .of_match = sifive_clint_ids, + .probe = sifive_clint_probe, + .ops = &sifive_clint_ops, .flags = DM_FLAG_PRE_RELOC, }; From a952c3a4546ba1d6c5a487cae2e73760ecfd0c60 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 28 Sep 2020 10:52:27 -0400 Subject: [PATCH 12/21] riscv: clk: Add CLINT clock to kendryte clock driver Another "virtual" clock (in the sense that it isn't configurable). This could possibly be done as a clock in the device tree, but I think this is a bit cleaner. Signed-off-by: Sean Anderson --- drivers/clk/kendryte/clk.c | 4 ++++ include/dt-bindings/clock/k210-sysctl.h | 1 + 2 files changed, 5 insertions(+) diff --git a/drivers/clk/kendryte/clk.c b/drivers/clk/kendryte/clk.c index 981b3b7699..bb196961af 100644 --- a/drivers/clk/kendryte/clk.c +++ b/drivers/clk/kendryte/clk.c @@ -646,6 +646,10 @@ static int k210_clk_probe(struct udevice *dev) REGISTER_GATE(K210_CLK_RTC, "rtc", in0); #undef REGISTER_GATE + /* The MTIME register in CLINT runs at one 50th the CPU clock speed */ + clk_dm(K210_CLK_CLINT, + clk_register_fixed_factor(NULL, "clint", "cpu", 0, 1, 50)); + return 0; } diff --git a/include/dt-bindings/clock/k210-sysctl.h b/include/dt-bindings/clock/k210-sysctl.h index 0e3ed3fb9f..fe852bbd92 100644 --- a/include/dt-bindings/clock/k210-sysctl.h +++ b/include/dt-bindings/clock/k210-sysctl.h @@ -55,5 +55,6 @@ #define K210_CLK_OTP 43 #define K210_CLK_RTC 44 #define K210_CLK_ACLK 45 +#define K210_CLK_CLINT 46 #endif /* CLOCK_K210_SYSCTL_H */ From e89e8983dc6ae36e05694b0991dba78257241606 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 28 Sep 2020 10:52:28 -0400 Subject: [PATCH 13/21] riscv: Update Kendryte device tree for new CLINT driver The interrupt controller property is removed from the clint binding because the clint is not an interrupt-controller. That is, no other devices have an interrupt which is controlled by the clint. Signed-off-by: Sean Anderson Reviewed-by: Bin Meng --- arch/riscv/dts/k210.dtsi | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/riscv/dts/k210.dtsi b/arch/riscv/dts/k210.dtsi index 2546c7d4e0..84cff51c36 100644 --- a/arch/riscv/dts/k210.dtsi +++ b/arch/riscv/dts/k210.dtsi @@ -17,6 +17,8 @@ compatible = "kendryte,k210"; aliases { + cpu0 = &cpu0; + cpu1 = &cpu1; dma0 = &dmac0; gpio0 = &gpio0; gpio1 = &gpio1_0; @@ -126,14 +128,13 @@ read-only; }; - clint0: interrupt-controller@2000000 { + clint0: clint@2000000 { #interrupt-cells = <1>; compatible = "kendryte,k210-clint", "riscv,clint0"; reg = <0x2000000 0xC000>; - interrupt-controller; interrupts-extended = <&cpu0_intc 3>, <&cpu0_intc 7>, <&cpu1_intc 3>, <&cpu1_intc 7>; - clocks = <&sysclk K210_CLK_CPU>; + clocks = <&sysclk K210_CLK_CLINT>; }; plic0: interrupt-controller@C000000 { From 422c3c5edf41318a3cdb532111148f085bc33638 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 28 Sep 2020 10:52:29 -0400 Subject: [PATCH 14/21] riscv: Update SiFive device tree for new CLINT driver We currently do this in a u-boot specific dts, but hopefully we can get these bindings added in Linux in the future. Signed-off-by: Sean Anderson Reviewed-by: Pragnesh Patel Reviewed-by: Bin Meng --- arch/riscv/dts/fu540-c000-u-boot.dtsi | 8 ++++++-- arch/riscv/dts/hifive-unleashed-a00-u-boot.dtsi | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/arch/riscv/dts/fu540-c000-u-boot.dtsi b/arch/riscv/dts/fu540-c000-u-boot.dtsi index 5302677ee4..a06e1b11c6 100644 --- a/arch/riscv/dts/fu540-c000-u-boot.dtsi +++ b/arch/riscv/dts/fu540-c000-u-boot.dtsi @@ -55,9 +55,13 @@ reg = <0x0 0x10070000 0x0 0x1000>; fuse-count = <0x1000>; }; - clint@2000000 { + clint: clint@2000000 { compatible = "riscv,clint0"; - interrupts-extended = <&cpu0_intc 3 &cpu0_intc 7 &cpu1_intc 3 &cpu1_intc 7 &cpu2_intc 3 &cpu2_intc 7 &cpu3_intc 3 &cpu3_intc 7 &cpu4_intc 3 &cpu4_intc 7>; + interrupts-extended = <&cpu0_intc 3 &cpu0_intc 7 + &cpu1_intc 3 &cpu1_intc 7 + &cpu2_intc 3 &cpu2_intc 7 + &cpu3_intc 3 &cpu3_intc 7 + &cpu4_intc 3 &cpu4_intc 7>; reg = <0x0 0x2000000 0x0 0xc0000>; u-boot,dm-spl; }; diff --git a/arch/riscv/dts/hifive-unleashed-a00-u-boot.dtsi b/arch/riscv/dts/hifive-unleashed-a00-u-boot.dtsi index 5d0c928b29..1996149c95 100644 --- a/arch/riscv/dts/hifive-unleashed-a00-u-boot.dtsi +++ b/arch/riscv/dts/hifive-unleashed-a00-u-boot.dtsi @@ -34,6 +34,10 @@ }; +&clint { + clocks = <&rtcclk>; +}; + &qspi0 { u-boot,dm-spl; From c41045411bbb64eeda2d404b79723f8d2802351c Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 21 Sep 2020 07:51:35 -0400 Subject: [PATCH 15/21] Revert "riscv: Clear pending interrupts before enabling IPIs" Clearing MIP.MSIP is not guaranteed to do anything by the spec. In addition, most existing RISC-V hardware does nothing when this bit is set. The following commits "riscv: Use a valid bit to ignore already-pending IPIs" and "riscv: Clear pending IPIs on initialization" should implement the original intent of the reverted commit in a more robust manner. This reverts commit 9472630337e7c4ac442066b5a752aaa8c3b4d4a6. Signed-off-by: Sean Anderson Reviewed-by: Bin Meng Reviewed-by: Rick Chen --- arch/riscv/cpu/start.S | 2 -- 1 file changed, 2 deletions(-) diff --git a/arch/riscv/cpu/start.S b/arch/riscv/cpu/start.S index bf9fdf369b..e3222b1ea7 100644 --- a/arch/riscv/cpu/start.S +++ b/arch/riscv/cpu/start.S @@ -65,8 +65,6 @@ _start: #else li t0, SIE_SSIE #endif - /* Clear any pending IPIs */ - csrc MODE_PREFIX(ip), t0 csrs MODE_PREFIX(ie), t0 #endif From d4990a46485f2f6592ae29f2b822043dbdeae15d Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 21 Sep 2020 07:51:36 -0400 Subject: [PATCH 16/21] riscv: Match memory barriers between send_ipi_many and handle_ipi Without a matching barrier on the write side, the barrier in handle_ipi does nothing. It was entirely possible for the boot hart to write to addr, arg0, and arg1 *after* sending the IPI, because there was no barrier on the sending side. Fixes: 90ae281437 ("riscv: add option to wait for ack from secondary harts in smp functions") Signed-off-by: Sean Anderson Reviewed-by: Bin Meng Reviewed-by: Rick Chen Reviewed-by: Leo Liang --- arch/riscv/lib/smp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/arch/riscv/lib/smp.c b/arch/riscv/lib/smp.c index ac22136314..ab6d8bd7fa 100644 --- a/arch/riscv/lib/smp.c +++ b/arch/riscv/lib/smp.c @@ -54,6 +54,8 @@ static int send_ipi_many(struct ipi_data *ipi, int wait) gd->arch.ipi[reg].arg0 = ipi->arg0; gd->arch.ipi[reg].arg1 = ipi->arg1; + __smp_mb(); + ret = riscv_send_ipi(reg); if (ret) { pr_err("Cannot send IPI to hart %d\n", reg); From f760c9a1fd485beae7612e39576e5fbf77c5d96b Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 21 Sep 2020 07:51:37 -0400 Subject: [PATCH 17/21] riscv: Use a valid bit to ignore already-pending IPIs Some IPIs may already be pending when U-Boot is started. This could be a problem if a secondary hart tries to handle an IPI before the boot hart has initialized the IPI device. To be specific, the Kendryte K210 ROM-based bootloader does not clear IPIs before passing control to U-Boot. Without this patch, the secondary hart jumps to address 0x0 as soon as it enters secondary_hart_loop, and then hangs in its trap handler. This commit introduces a valid bit so secondary harts know when and IPI originates from U-Boot, and it is safe to use the IPI API. The valid bit is initialized to 0 by board_init_f_init_reserve. Before this, secondary harts wait in wait_for_gd_init. Signed-off-by: Sean Anderson Reviewed-by: Bin Meng Reviewed-by: Rick Chen Reviewed-by: Leo Liang --- arch/riscv/include/asm/smp.h | 7 +++++++ arch/riscv/lib/smp.c | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/arch/riscv/include/asm/smp.h b/arch/riscv/include/asm/smp.h index 1b428856b2..2dae0800ce 100644 --- a/arch/riscv/include/asm/smp.h +++ b/arch/riscv/include/asm/smp.h @@ -18,14 +18,21 @@ * IPI data structure. The hart ID is inserted by the hart handling the IPI and * calling the function. * + * @valid is used to determine whether a sent IPI originated from U-Boot. It is + * initialized to zero by board_init_f_alloc_reserve. When U-Boot sends its + * first IPI, it is set to 1. This prevents already-pending IPIs not sent by + * U-Boot from being taken. + * * @addr: Address of function * @arg0: First argument of function * @arg1: Second argument of function + * @valid: Whether this IPI is valid */ struct ipi_data { ulong addr; ulong arg0; ulong arg1; + unsigned int valid; }; /** diff --git a/arch/riscv/lib/smp.c b/arch/riscv/lib/smp.c index ab6d8bd7fa..8f33ce1fe3 100644 --- a/arch/riscv/lib/smp.c +++ b/arch/riscv/lib/smp.c @@ -54,7 +54,13 @@ static int send_ipi_many(struct ipi_data *ipi, int wait) gd->arch.ipi[reg].arg0 = ipi->arg0; gd->arch.ipi[reg].arg1 = ipi->arg1; - __smp_mb(); + /* + * Ensure valid only becomes set when the IPI parameters are + * set. An IPI may already be pending on other harts, so we + * need a way to signal that the IPI device has been + * initialized, and that it is ok to call the function. + */ + __smp_store_release(&gd->arch.ipi[reg].valid, 1); ret = riscv_send_ipi(reg); if (ret) { @@ -83,7 +89,13 @@ void handle_ipi(ulong hart) if (hart >= CONFIG_NR_CPUS) return; - __smp_mb(); + /* + * If valid is not set, then U-Boot has not requested the IPI. The + * IPI device may not be initialized, so all we can do is wait for + * U-Boot to initialize it and send an IPI + */ + if (!__smp_load_acquire(&gd->arch.ipi[hart].valid)) + return; smp_function = (void (*)(ulong, ulong, ulong))gd->arch.ipi[hart].addr; invalidate_icache_all(); From 768502e2a7f283a715dd1f62e304393a88422545 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 21 Sep 2020 07:51:38 -0400 Subject: [PATCH 18/21] riscv: Clear pending IPIs on initialization Even though we no longer call smp_function if an IPI was not sent by U-Boot, we still need to clear any IPIs which were pending from the execution environment. Otherwise, secondary harts will busy-wait in secondary_hart_loop, instead of relaxing. Along with the previous commit ("riscv: Use a valid bit to ignore already-pending IPIs"), this fixes SMP booting on the Kendryte K210. Signed-off-by: Sean Anderson Reviewed-by: Bin Meng Reviewed-by: Rick Chen --- arch/riscv/cpu/cpu.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/arch/riscv/cpu/cpu.c b/arch/riscv/cpu/cpu.c index bfa2d4a426..85592f5bee 100644 --- a/arch/riscv/cpu/cpu.c +++ b/arch/riscv/cpu/cpu.c @@ -72,6 +72,17 @@ static int riscv_cpu_probe(void) return 0; } +/* + * This is called on secondary harts just after the IPI is init'd. Currently + * there's nothing to do, since we just need to clear any existing IPIs, and + * that is handled by the sending of an ipi itself. + */ +#if CONFIG_IS_ENABLED(SMP) +static void dummy_pending_ipi_clear(ulong hart, ulong arg0, ulong arg1) +{ +} +#endif + int arch_cpu_init_dm(void) { int ret; @@ -111,6 +122,15 @@ int arch_cpu_init_dm(void) ret = riscv_init_ipi(); if (ret) return ret; + + /* + * Clear all pending IPIs on secondary harts. We don't do anything on + * the boot hart, since we never send an IPI to ourselves, and no + * interrupts are enabled + */ + ret = smp_call_function((ulong)dummy_pending_ipi_clear, 0, 0, 0); + if (ret) + return ret; #endif return 0; From 309995b315b9ffef999810af212cccb7a50004bc Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 21 Sep 2020 07:51:39 -0400 Subject: [PATCH 19/21] riscv: Consolidate fences into AMOs for available_harts_lock We can reduce the number of instructions needed to use available_harts_lock by using the aq and rl suffixes for AMOs. Signed-off-by: Sean Anderson Reviewed-by: Bin Meng Reviewed-by: Rick Chen --- arch/riscv/cpu/start.S | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/arch/riscv/cpu/start.S b/arch/riscv/cpu/start.S index e3222b1ea7..66ca1c7020 100644 --- a/arch/riscv/cpu/start.S +++ b/arch/riscv/cpu/start.S @@ -125,14 +125,12 @@ call_board_init_f_0: #ifndef CONFIG_XIP la t0, available_harts_lock - fence rw, w - amoswap.w zero, zero, 0(t0) + amoswap.w.rl zero, zero, 0(t0) wait_for_gd_init: la t0, available_harts_lock li t1, 1 -1: amoswap.w t1, t1, 0(t0) - fence r, rw +1: amoswap.w.aq t1, t1, 0(t0) bnez t1, 1b /* register available harts in the available_harts mask */ @@ -142,8 +140,7 @@ wait_for_gd_init: or t2, t2, t1 SREG t2, GD_AVAILABLE_HARTS(gp) - fence rw, w - amoswap.w zero, zero, 0(t0) + amoswap.w.rl zero, zero, 0(t0) /* * Continue on hart lottery winner, others branch to From 85768134b40794a108a2dafb4a565b712e359c46 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 21 Sep 2020 07:51:40 -0400 Subject: [PATCH 20/21] riscv: Ensure gp is NULL or points to valid data This ensures constructs like `if (gd & gd->...) { ... }` work when accessing the global data pointer. Without this change, it was possible for a very early trap to cause _exit_trap to directly or indirectly (through printf) to read arbitrary memory. This could cause a second trap, preventing show_regs from being printed. printf (and specifically puts) uses gd to determine what function to print with. These functions in turn use gd to find the serial device, etc. However, before accessing gd, puts first checks to see if it is non-NULL. This indicates an existing (perhaps undocumented) assumption that either gd is NULL or it is completely valid. Before this patch, gd either points to unexpected data (because it retains the value it did from the prior-stage) or points to uninitialized data (because it has not yet been initialized by board_init_f_init_reserve) until the hart has acquired available_harts_lock. This can cause two problems, depending on the value of gd->flags. If GD_FLG_SERIAL_READY is unset, then some garbage data will be printed to stdout, but there will not be a second trap. However, if GD_FLG_SERIAL_READY is set, then puts will try to print with serial_puts, which will likely cause a second trap. After this patch, gd is zero up until either a hart has set it in wait_for_gd_init, or until it is set by arch_init_gd. This prevents its usage before its data is initialized because both handle_trap and puts ensure that gd is nonzero before using it. After gd has been set, it is OK to access it because its data has been cleared (and so flags is valid). XIP cannot use locks because flash is not writable. This leaves it vulnerable to the same class of bugs regarding already-pending IPIs as before this series. Fixing that would require finding another method of synchronization, which is outside the scope of this series. Fixes: 7c6ca03eae ("riscv: additional crash information") Signed-off-by: Sean Anderson Reviewed-by: Bin Meng Reviewed-by: Rick Chen --- arch/riscv/cpu/start.S | 28 +++++++++++++++++++++++++--- arch/riscv/lib/interrupts.c | 3 ++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/arch/riscv/cpu/start.S b/arch/riscv/cpu/start.S index 66ca1c7020..eb852538ca 100644 --- a/arch/riscv/cpu/start.S +++ b/arch/riscv/cpu/start.S @@ -47,6 +47,13 @@ _start: mv tp, a0 mv s1, a1 + /* + * Set the global data pointer to a known value in case we get a very + * early trap. The global data pointer will be set its actual value only + * after it has been initialized. + */ + mv gp, zero + la t0, trap_entry csrw MODE_PREFIX(tvec), t0 @@ -85,10 +92,10 @@ call_board_init_f_0: jal board_init_f_alloc_reserve /* - * Set global data pointer here for all harts, uninitialized at this - * point. + * Save global data pointer for later. We don't set it here because it + * is not initialized yet. */ - mv gp, a0 + mv s0, a0 /* setup stack */ #if CONFIG_IS_ENABLED(SMP) @@ -109,6 +116,14 @@ call_board_init_f_0: amoswap.w s2, t1, 0(t0) bnez s2, wait_for_gd_init #else + /* + * FIXME: gp is set before it is initialized. If an XIP U-Boot ever + * encounters a pending IPI on boot it is liable to jump to whatever + * memory happens to be in ipi_data.addr on boot. It may also run into + * problems if it encounters an exception too early (because printf/puts + * accesses gd). + */ + mv gp, s0 bnez tp, secondary_hart_loop #endif @@ -133,6 +148,13 @@ wait_for_gd_init: 1: amoswap.w.aq t1, t1, 0(t0) bnez t1, 1b + /* + * Set the global data pointer only when gd_t has been initialized. + * This was already set by arch_setup_gd on the boot hart, but all other + * harts' global data pointers gets set here. + */ + mv gp, s0 + /* register available harts in the available_harts mask */ li t1, 1 sll t1, t1, tp diff --git a/arch/riscv/lib/interrupts.c b/arch/riscv/lib/interrupts.c index cd47e64487..ad870e98d8 100644 --- a/arch/riscv/lib/interrupts.c +++ b/arch/riscv/lib/interrupts.c @@ -78,7 +78,8 @@ static void _exit_trap(ulong code, ulong epc, ulong tval, struct pt_regs *regs) printf("EPC: " REG_FMT " RA: " REG_FMT " TVAL: " REG_FMT "\n", epc, regs->ra, tval); - if (gd->flags & GD_FLG_RELOC) + /* Print relocation adjustments, but only if gd is initialized */ + if (gd && gd->flags & GD_FLG_RELOC) printf("EPC: " REG_FMT " RA: " REG_FMT " reloc adjusted\n\n", epc - gd->reloc_off, regs->ra - gd->reloc_off); From 924de3216e9efdf1cdc71b8632099213aac03f2c Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Mon, 21 Sep 2020 07:51:41 -0400 Subject: [PATCH 21/21] riscv: Add some comments to start.S This adds comments regarding the ordering and purpose of certain instructions as I understand them. Signed-off-by: Sean Anderson Reviewed-by: Bin Meng Reviewed-by: Rick Chen Reviewed-by: Leo Liang --- arch/riscv/cpu/start.S | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/arch/riscv/cpu/start.S b/arch/riscv/cpu/start.S index eb852538ca..bbc737ed9a 100644 --- a/arch/riscv/cpu/start.S +++ b/arch/riscv/cpu/start.S @@ -43,7 +43,10 @@ _start: csrr a0, CSR_MHARTID #endif - /* save hart id and dtb pointer */ + /* + * Save hart id and dtb pointer. The thread pointer register is not + * modified by C code. It is used by secondary_hart_loop. + */ mv tp, a0 mv s1, a1 @@ -54,10 +57,18 @@ _start: */ mv gp, zero + /* + * Set the trap handler. This must happen after initializing gp because + * the handler may use it. + */ la t0, trap_entry csrw MODE_PREFIX(tvec), t0 - /* mask all interrupts */ + /* + * Mask all interrupts. Interrupts are disabled globally (in m/sstatus) + * for U-Boot, but we will need to read m/sip to determine if we get an + * IPI + */ csrw MODE_PREFIX(ie), zero #if CONFIG_IS_ENABLED(SMP) @@ -412,6 +423,10 @@ secondary_hart_relocate: mv gp, a2 #endif +/* + * Interrupts are disabled globally, but they can still be read from m/sip. The + * wfi function will wake us up if we get an IPI, even if we do not trap. + */ secondary_hart_loop: wfi