Merge tag 'efi-next-2022-09-14' of https://source.denx.de/u-boot/custodians/u-boot-efi into next
Pull request for efi next
UEFI:
Implement a command eficonfig to maintain Load Options and boot order via
menus.
diff --git a/MAINTAINERS b/MAINTAINERS
index 1ebcd36..25a11a0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -795,6 +795,13 @@
F: arch/m68k/
F: doc/arch/m68k.rst
+CYCLIC
+M: Stefan Roese <sr@denx.de>
+S: Maintained
+F: cmd/cyclic.c
+F: common/cyclic.c
+F: include/cyclic.h
+
DFU
M: Lukasz Majewski <lukma@denx.de>
S: Maintained
diff --git a/board/Marvell/octeon_nic23/board.c b/board/Marvell/octeon_nic23/board.c
index 3e2c544..08b1aa4 100644
--- a/board/Marvell/octeon_nic23/board.c
+++ b/board/Marvell/octeon_nic23/board.c
@@ -3,8 +3,10 @@
* Copyright (C) 2021-2022 Stefan Roese <sr@denx.de>
*/
+#include <cyclic.h>
#include <dm.h>
#include <ram.h>
+#include <time.h>
#include <asm/gpio.h>
#include <mach/octeon_ddr.h>
@@ -15,11 +17,90 @@
#include <mach/cvmx-helper-cfg.h>
#include <mach/cvmx-helper-util.h>
#include <mach/cvmx-bgxx-defs.h>
+#include <mach/cvmx-dtx-defs.h>
#include "board_ddr.h"
+/**
+ * cvmx_spem#_cfg_rd
+ *
+ * This register allows read access to the configuration in the PCIe core.
+ *
+ */
+union cvmx_spemx_cfg_rd {
+ u64 u64;
+ struct cvmx_spemx_cfg_rd_s {
+ u64 data : 32;
+ u64 addr : 32;
+ } s;
+ struct cvmx_spemx_cfg_rd_s cn73xx;
+};
+
+/**
+ * cvmx_spem#_cfg_wr
+ *
+ * This register allows write access to the configuration in the PCIe core.
+ *
+ */
+union cvmx_spemx_cfg_wr {
+ u64 u64;
+ struct cvmx_spemx_cfg_wr_s {
+ u64 data : 32;
+ u64 addr : 32;
+ } s;
+ struct cvmx_spemx_cfg_wr_s cn73xx;
+};
+
+/**
+ * cvmx_spem#_flr_pf_stopreq
+ *
+ * PF function level reset stop outbound requests register.
+ * Hardware automatically sets the STOPREQ bit for the PF when it enters a
+ * function level reset (FLR). Software is responsible for clearing the STOPREQ
+ * bit but must not do so prior to hardware taking down the FLR, which could be
+ * as long as 100ms. It may be appropriate for software to wait longer before clearing
+ * STOPREQ, software may need to drain deep DPI queues for example.
+ * Whenever SPEM receives a PF or child VF request mastered by CNXXXX over S2M (i.e. P or NP),
+ * when STOPREQ is set for the function, SPEM will discard the outgoing request
+ * before sending it to the PCIe core. If a NP, SPEM will schedule an immediate
+ * SWI_RSP_ERROR completion for the request - no timeout is required.
+ * In both cases, SPEM()_DBG_PF()_INFO[P()_BMD_E] will be set and a error
+ * interrupt is generated.
+ *
+ * STOPREQ mimics the behavior of PCIEEP()_CFG001[ME] for outbound requests that will
+ * master the PCIe bus (P and NP).
+ *
+ * STOPREQ will have no effect on completions returned by CNXXXX over the S2M,
+ * nor on M2S traffic.
+ *
+ * When a PF()_STOPREQ is set, none of the associated
+ * PEM()_FLR_PF()_VF_STOPREQ[VF_STOPREQ] will be set.
+ *
+ * STOPREQ is reset when the MAC is reset, and is not reset after a chip soft reset.
+ */
+union cvmx_spemx_flr_pf_stopreq {
+ u64 u64;
+ struct cvmx_spemx_flr_pf_stopreq_s {
+ u64 reserved_3_63 : 61;
+ u64 pf2_stopreq : 1;
+ u64 pf1_stopreq : 1;
+ u64 pf0_stopreq : 1;
+ } s;
+ struct cvmx_spemx_flr_pf_stopreq_s cn73xx;
+};
+
+#define CVMX_SPEMX_CFG_WR(offset) 0x00011800C0000028ull
+#define CVMX_SPEMX_CFG_RD(offset) 0x00011800C0000030ull
+#define CVMX_SPEMX_FLR_PF_STOPREQ(offset) 0x00011800C0000218ull
+
+#define DTX_SELECT_LTSSM 0x0
+#define DTX_SELECT_LTSSM_ENA 0x3ff
+#define LTSSM_L0 0x11
+
#define NIC23_DEF_DRAM_FREQ 800
+static u32 pci_cfgspace_reg0[2] = { 0, 0 };
+
static u8 octeon_nic23_cfg0_spd_values[512] = {
OCTEON_NIC23_CFG0_SPD_VALUES
};
@@ -145,8 +226,118 @@
cvmx_qlm_measure_clock(4), cvmx_qlm_measure_clock(5));
}
+/**
+ * If there is a PF FLR then the PCI EEPROM is not re-read. In this case
+ * we need to re-program the vendor and device ID immediately after hardware
+ * completes FLR.
+ *
+ * PCI spec requires FLR to be completed within 100ms. The user who triggered
+ * FLR expects hardware to finish FLR within 100ms, otherwise the user will
+ * end up reading DEVICE_ID incorrectly from the reset value.
+ * CN23XX exits FLR at any point between 66 and 99ms, so U-Boot has to wait
+ * 99ms to let hardware finish its part, then finish reprogramming the
+ * correct device ID before the end of 100ms.
+ *
+ * Note: this solution only works properly when there is no other activity
+ * within U-Boot for 100ms from the time FLR is triggered.
+ *
+ * This function gets called every 100usec. If FLR happens during any
+ * other activity like bootloader/image update then it is possible that
+ * this function does not get called for more than the FLR period which will
+ * cause the PF device ID restore to happen after whoever initiated the FLR to
+ * read the incorrect device ID 0x9700 (reset value) instead of 0x9702
+ * (restored value).
+ */
+static void octeon_board_restore_pf(void *ctx)
+{
+ union cvmx_spemx_flr_pf_stopreq stopreq;
+ static bool start_initialized[2] = {false, false};
+ bool pf0_flag, pf1_flag;
+ u64 ltssm_bits;
+ const u64 pf_flr_wait_usecs = 99700;
+ u64 elapsed_usecs;
+ union cvmx_spemx_cfg_wr cfg_wr;
+ union cvmx_spemx_cfg_rd cfg_rd;
+ static u64 start_us[2];
+ int pf_num;
+
+ csr_wr(CVMX_DTX_SPEM_SELX(0), DTX_SELECT_LTSSM);
+ csr_rd(CVMX_DTX_SPEM_SELX(0));
+ csr_wr(CVMX_DTX_SPEM_ENAX(0), DTX_SELECT_LTSSM_ENA);
+ csr_rd(CVMX_DTX_SPEM_ENAX(0));
+ ltssm_bits = csr_rd(CVMX_DTX_SPEM_DATX(0));
+ if (((ltssm_bits >> 3) & 0x3f) != LTSSM_L0)
+ return;
+
+ stopreq.u64 = csr_rd(CVMX_SPEMX_FLR_PF_STOPREQ(0));
+ pf0_flag = stopreq.s.pf0_stopreq;
+ pf1_flag = stopreq.s.pf1_stopreq;
+ /* See if PF interrupt happened */
+ if (!(pf0_flag || pf1_flag))
+ return;
+
+ if (pf0_flag && !start_initialized[0]) {
+ start_initialized[0] = true;
+ start_us[0] = get_timer_us(0);
+ }
+
+ /* Store programmed PCIe DevID SPEM0 PF0 */
+ if (pf0_flag && !pci_cfgspace_reg0[0]) {
+ cfg_rd.s.addr = (0 << 24) | 0x0;
+ csr_wr(CVMX_SPEMX_CFG_RD(0), cfg_rd.u64);
+ cfg_rd.u64 = csr_rd(CVMX_SPEMX_CFG_RD(0));
+ pci_cfgspace_reg0[0] = cfg_rd.s.data;
+ }
+
+ if (pf1_flag && !start_initialized[1]) {
+ start_initialized[1] = true;
+ start_us[1] = get_timer_us(0);
+ }
+
+ /* Store programmed PCIe DevID SPEM0 PF1 */
+ if (pf1_flag && !pci_cfgspace_reg0[1]) {
+ cfg_rd.s.addr = (1 << 24) | 0x0;
+ csr_wr(CVMX_SPEMX_CFG_RD(0), cfg_rd.u64);
+ cfg_rd.u64 = csr_rd(CVMX_SPEMX_CFG_RD(0));
+ pci_cfgspace_reg0[1] = cfg_rd.s.data;
+ }
+
+ /* For PF, rewrite pci config space reg 0 */
+ for (pf_num = 0; pf_num < 2; pf_num++) {
+ if (!start_initialized[pf_num])
+ continue;
+
+ elapsed_usecs = get_timer_us(0) - start_us[pf_num];
+
+ if (elapsed_usecs > pf_flr_wait_usecs) {
+ /* Here, our measured FLR duration has passed;
+ * check if device ID has been reset,
+ * which indicates FLR completion (per MA team).
+ */
+ cfg_rd.s.addr = (pf_num << 24) | 0x0;
+ csr_wr(CVMX_SPEMX_CFG_RD(0), cfg_rd.u64);
+ cfg_rd.u64 = csr_rd(CVMX_SPEMX_CFG_RD(0));
+ /* if DevID has NOT been reset, FLR is not yet
+ * complete
+ */
+ if (cfg_rd.s.data != pci_cfgspace_reg0[pf_num]) {
+ stopreq.s.pf0_stopreq = (pf_num == 0) ? 1 : 0;
+ stopreq.s.pf1_stopreq = (pf_num == 1) ? 1 : 0;
+ csr_wr(CVMX_SPEMX_FLR_PF_STOPREQ(0), stopreq.u64);
+
+ cfg_wr.u64 = 0;
+ cfg_wr.s.addr = (pf_num << 24) | 0;
+ cfg_wr.s.data = pci_cfgspace_reg0[pf_num];
+ csr_wr(CVMX_SPEMX_CFG_WR(0), cfg_wr.u64);
+ start_initialized[pf_num] = false;
+ }
+ }
+ }
+}
+
int board_late_init(void)
{
+ struct cyclic_info *cyclic;
struct gpio_desc gpio = {};
ofnode node;
@@ -164,6 +355,12 @@
board_configure_qlms();
+ /* Register cyclic function for PCIe FLR fixup */
+ cyclic = cyclic_register(octeon_board_restore_pf, 100,
+ "pcie_flr_fix", NULL);
+ if (!cyclic)
+ printf("Registering of cyclic function failed\n");
+
return 0;
}
diff --git a/boot/image-board.c b/boot/image-board.c
index 4e4d1c1..1be0a35 100644
--- a/boot/image-board.c
+++ b/boot/image-board.c
@@ -16,6 +16,7 @@
#include <fpga.h>
#include <image.h>
#include <init.h>
+#include <log.h>
#include <mapmem.h>
#include <rtc.h>
#include <watchdog.h>
@@ -24,7 +25,6 @@
DECLARE_GLOBAL_DATA_PTR;
-#if CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)
/**
* image_get_ramdisk - get and verify ramdisk image
* @rd_addr: ramdisk image start address
@@ -83,7 +83,6 @@
return rd_hdr;
}
-#endif
/*****************************************************************************/
/* Shared dual-format routines */
@@ -174,29 +173,29 @@
if (to == from)
return;
-#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
- if (to > from) {
- from += len;
- to += len;
- }
- while (len > 0) {
- size_t tail = (len > chunksz) ? chunksz : len;
-
- WATCHDOG_RESET();
+ if (IS_ENABLED(CONFIG_HW_WATCHDOG) || IS_ENABLED(CONFIG_WATCHDOG)) {
if (to > from) {
- to -= tail;
- from -= tail;
+ from += len;
+ to += len;
}
- memmove(to, from, tail);
- if (to < from) {
- to += tail;
- from += tail;
+ while (len > 0) {
+ size_t tail = (len > chunksz) ? chunksz : len;
+
+ WATCHDOG_RESET();
+ if (to > from) {
+ to -= tail;
+ from -= tail;
+ }
+ memmove(to, from, tail);
+ if (to < from) {
+ to += tail;
+ from += tail;
+ }
+ len -= tail;
}
- len -= tail;
+ } else {
+ memmove(to, from, len);
}
-#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
- memmove(to, from, len);
-#endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
}
/**
@@ -314,7 +313,7 @@
* select_ramdisk() - Select and locate the ramdisk to use
*
* @images: pointer to the bootm images structure
- * @select: name of ramdisk to select, or NULL for any
+ * @select: name of ramdisk to select, or hex address, NULL for any
* @arch: expected ramdisk architecture
* @rd_datap: pointer to a ulong variable, will hold ramdisk pointer
* @rd_lenp: pointer to a ulong variable, will hold ramdisk length
@@ -324,13 +323,17 @@
static int select_ramdisk(bootm_headers_t *images, const char *select, u8 arch,
ulong *rd_datap, ulong *rd_lenp)
{
+ const char *fit_uname_config;
+ const char *fit_uname_ramdisk;
+ bool done_select = !select;
+ bool done = false;
+ int rd_noffset;
ulong rd_addr;
char *buf;
-#if CONFIG_IS_ENABLED(FIT)
- const char *fit_uname_config = images->fit_uname_cfg;
- const char *fit_uname_ramdisk = NULL;
- int rd_noffset;
+ if (CONFIG_IS_ENABLED(FIT)) {
+ fit_uname_config = images->fit_uname_cfg;
+ fit_uname_ramdisk = NULL;
if (select) {
ulong default_addr;
@@ -345,48 +348,47 @@
else
default_addr = image_load_addr;
- if (fit_parse_conf(select, default_addr,
- &rd_addr, &fit_uname_config)) {
+ if (fit_parse_conf(select, default_addr, &rd_addr,
+ &fit_uname_config)) {
debug("* ramdisk: config '%s' from image at 0x%08lx\n",
fit_uname_config, rd_addr);
+ done_select = true;
} else if (fit_parse_subimage(select, default_addr,
&rd_addr,
&fit_uname_ramdisk)) {
debug("* ramdisk: subimage '%s' from image at 0x%08lx\n",
fit_uname_ramdisk, rd_addr);
- } else
-#endif
- {
- rd_addr = hextoul(select, NULL);
- debug("* ramdisk: cmdline image address = 0x%08lx\n",
- rd_addr);
+ done_select = true;
}
-#if CONFIG_IS_ENABLED(FIT)
- } else {
- /* use FIT configuration provided in first bootm
- * command argument. If the property is not defined,
- * quit silently (with -ENOPKG)
- */
- rd_addr = map_to_sysmem(images->fit_hdr_os);
- rd_noffset = fit_get_node_from_config(images,
- FIT_RAMDISK_PROP,
- rd_addr);
- if (rd_noffset == -ENOENT)
- return -ENOPKG;
- else if (rd_noffset < 0)
- return rd_noffset;
}
-#endif
-
- /*
- * Check if there is an initrd image at the
- * address provided in the second bootm argument
- * check image type, for FIT images get FIT node.
+ }
+ if (!done_select) {
+ rd_addr = hextoul(select, NULL);
+ debug("* ramdisk: cmdline image address = 0x%08lx\n", rd_addr);
+ }
+ if (CONFIG_IS_ENABLED(FIT) && !select) {
+ /* use FIT configuration provided in first bootm
+ * command argument. If the property is not defined,
+ * quit silently (with -ENOPKG)
*/
- buf = map_sysmem(rd_addr, 0);
- switch (genimg_get_format(buf)) {
-#if CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)
- case IMAGE_FORMAT_LEGACY: {
+ rd_addr = map_to_sysmem(images->fit_hdr_os);
+ rd_noffset = fit_get_node_from_config(images, FIT_RAMDISK_PROP,
+ rd_addr);
+ if (rd_noffset == -ENOENT)
+ return -ENOPKG;
+ else if (rd_noffset < 0)
+ return rd_noffset;
+ }
+
+ /*
+ * Check if there is an initrd image at the
+ * address provided in the second bootm argument
+ * check image type, for FIT images get FIT node.
+ */
+ buf = map_sysmem(rd_addr, 0);
+ switch (genimg_get_format(buf)) {
+ case IMAGE_FORMAT_LEGACY:
+ if (CONFIG_IS_ENABLED(LEGACY_IMAGE_FORMAT)) {
const image_header_t *rd_hdr;
printf("## Loading init Ramdisk from Legacy Image at %08lx ...\n",
@@ -401,15 +403,15 @@
*rd_datap = image_get_data(rd_hdr);
*rd_lenp = image_get_data_size(rd_hdr);
- break;
+ done = true;
}
-#endif
-#if CONFIG_IS_ENABLED(FIT)
- case IMAGE_FORMAT_FIT:
- rd_noffset = fit_image_load(images,
- rd_addr, &fit_uname_ramdisk,
- &fit_uname_config, arch,
- IH_TYPE_RAMDISK,
+ break;
+ case IMAGE_FORMAT_FIT:
+ if (CONFIG_IS_ENABLED(FIT)) {
+ rd_noffset = fit_image_load(images, rd_addr,
+ &fit_uname_ramdisk,
+ &fit_uname_config,
+ arch, IH_TYPE_RAMDISK,
BOOTSTAGE_ID_FIT_RD_START,
FIT_LOAD_OPTIONAL_NON_ZERO,
rd_datap, rd_lenp);
@@ -419,29 +421,41 @@
images->fit_hdr_rd = map_sysmem(rd_addr, 0);
images->fit_uname_rd = fit_uname_ramdisk;
images->fit_noffset_rd = rd_noffset;
- break;
-#endif
-#ifdef CONFIG_ANDROID_BOOT_IMAGE
- case IMAGE_FORMAT_ANDROID:
- android_image_get_ramdisk((void *)images->os.start,
- rd_datap, rd_lenp);
- break;
-#endif
- default:
- if (IS_ENABLED(CONFIG_SUPPORT_RAW_INITRD)) {
- char *end = NULL;
+ done = true;
+ }
+ break;
+ case IMAGE_FORMAT_ANDROID:
+ if (IS_ENABLED(CONFIG_ANDROID_BOOT_IMAGE)) {
+ void *ptr = map_sysmem(images->os.start, 0);
+ int ret;
+
+ ret = android_image_get_ramdisk(ptr, rd_datap, rd_lenp);
+ unmap_sysmem(ptr);
+ if (ret)
+ return ret;
+ done = true;
+ }
+ break;
+ }
+
+ if (!done) {
+ if (IS_ENABLED(CONFIG_SUPPORT_RAW_INITRD)) {
+ char *end = NULL;
- if (select)
- end = strchr(select, ':');
- if (end) {
- *rd_lenp = hextoul(++end, NULL);
- *rd_datap = rd_addr;
- break;
- }
+ if (select)
+ end = strchr(select, ':');
+ if (end) {
+ *rd_lenp = hextoul(++end, NULL);
+ *rd_datap = rd_addr;
+ done = true;
}
+ }
+
+ if (!done) {
puts("Wrong Ramdisk Image Format\n");
return -EINVAL;
}
+ }
return 0;
}
@@ -538,7 +552,6 @@
return 0;
}
-#if defined(CONFIG_LMB)
/**
* boot_ramdisk_high - relocate init ramdisk
* @lmb: pointer to lmb handle, will be used for memory mgmt
@@ -632,7 +645,6 @@
error:
return -1;
}
-#endif
int boot_get_setup(bootm_headers_t *images, u8 arch,
ulong *setup_start, ulong *setup_len)
@@ -826,15 +838,13 @@
return 0;
}
-#if defined(CONFIG_LMB)
-#ifdef CONFIG_SYS_BOOT_GET_CMDLINE
/**
* boot_get_cmdline - allocate and initialize kernel cmdline
* @lmb: pointer to lmb handle, will be used for memory mgmt
* @cmd_start: pointer to a ulong variable, will hold cmdline start
* @cmd_end: pointer to a ulong variable, will hold cmdline end
*
- * boot_get_cmdline() allocates space for kernel command line below
+ * This allocates space for kernel command line below
* BOOTMAPSZ + env_get_bootm_low() address. If "bootargs" U-Boot environment
* variable is present its contents is copied to allocated kernel
* command line.
@@ -845,10 +855,19 @@
*/
int boot_get_cmdline(struct lmb *lmb, ulong *cmd_start, ulong *cmd_end)
{
+ int barg;
char *cmdline;
char *s;
- cmdline = (char *)(ulong)lmb_alloc_base(lmb, CONFIG_SYS_BARGSIZE, 0xf,
+ /*
+ * Help the compiler detect that this function is only called when
+ * CONFIG_SYS_BOOT_GET_CMDLINE is enabled
+ */
+ if (!IS_ENABLED(CONFIG_SYS_BOOT_GET_CMDLINE))
+ return 0;
+
+ barg = IF_ENABLED_INT(CONFIG_SYS_BOOT_GET_CMDLINE, CONFIG_SYS_BARGSIZE);
+ cmdline = (char *)(ulong)lmb_alloc_base(lmb, barg, 0xf,
env_get_bootm_mapsize() + env_get_bootm_low());
if (!cmdline)
return -1;
@@ -894,22 +913,22 @@
debug("## kernel board info at 0x%08lx\n", (ulong)*kbd);
-#if defined(DEBUG)
- if (IS_ENABLED(CONFIG_CMD_BDI))
+ if (_DEBUG && IS_ENABLED(CONFIG_CMD_BDI))
do_bdinfo(NULL, 0, 0, NULL);
-#endif
return 0;
}
-#endif
int image_setup_linux(bootm_headers_t *images)
{
ulong of_size = images->ft_len;
char **of_flat_tree = &images->ft_addr;
- struct lmb *lmb = &images->lmb;
+ struct lmb *lmb = images_lmb(images);
int ret;
+ /* This function cannot be called without lmb support */
+ if (!CONFIG_IS_ENABLED(LMB))
+ return -EFAULT;
if (CONFIG_IS_ENABLED(OF_LIBFDT))
boot_fdt_add_mem_rsv_regions(lmb, *of_flat_tree);
@@ -936,7 +955,6 @@
return 0;
}
-#endif
void genimg_print_size(uint32_t size)
{
diff --git a/cmd/Kconfig b/cmd/Kconfig
index ea506a0..37bc64d 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -2518,6 +2518,22 @@
memory by coreboot before jumping to U-Boot. It can be useful for
debugging the beaaviour of coreboot or U-Boot.
+config CMD_CYCLIC
+ bool "cyclic - Show information about cyclic functions"
+ depends on CYCLIC
+ default y
+ help
+ This enables the 'cyclic' command which provides information about
+ cyclic execution functions. This infrastructure allows registering
+ functions to be executed cyclically, e.g. every 100ms. These commands
+ are supported:
+
+ cyclic list - list cyclic functions
+ cyclic cyclic demo <cycletime_ms> <delay_us> - register cyclic
+ demo function
+
+ See doc/develop/cyclic.rst for more details.
+
config CMD_DIAG
bool "diag - Board diagnostics"
help
diff --git a/cmd/Makefile b/cmd/Makefile
index 3b027d3..0ef4e2e 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -56,6 +56,7 @@
endif
obj-$(CONFIG_CMD_ADTIMG) += adtimg.o
obj-$(CONFIG_CMD_ABOOTIMG) += abootimg.o
+obj-$(CONFIG_CMD_CYCLIC) += cyclic.o
obj-$(CONFIG_CMD_EVENT) += event.o
obj-$(CONFIG_CMD_EXTENSION) += extension_board.o
obj-$(CONFIG_CMD_ECHO) += echo.o
diff --git a/cmd/cyclic.c b/cmd/cyclic.c
new file mode 100644
index 0000000..c1bc556
--- /dev/null
+++ b/cmd/cyclic.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * A general-purpose cyclic execution infrastructure, to allow "small"
+ * (run-time wise) functions to be executed at a specified frequency.
+ * Things like LED blinking or watchdog triggering are examples for such
+ * tasks.
+ *
+ * Copyright (C) 2022 Stefan Roese <sr@denx.de>
+ */
+
+#include <common.h>
+#include <command.h>
+#include <cyclic.h>
+#include <div64.h>
+#include <malloc.h>
+#include <linux/delay.h>
+
+struct cyclic_demo_info {
+ uint delay_us;
+};
+
+static void cyclic_demo(void *ctx)
+{
+ struct cyclic_demo_info *info = ctx;
+
+ /* Just a small dummy delay here */
+ udelay(info->delay_us);
+}
+
+static int do_cyclic_demo(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct cyclic_demo_info *info;
+ struct cyclic_info *cyclic;
+ uint time_ms;
+
+ if (argc < 3)
+ return CMD_RET_USAGE;
+
+ info = malloc(sizeof(struct cyclic_demo_info));
+ if (!info) {
+ printf("out of memory\n");
+ return CMD_RET_FAILURE;
+ }
+
+ time_ms = simple_strtoul(argv[1], NULL, 0);
+ info->delay_us = simple_strtoul(argv[2], NULL, 0);
+
+ /* Register demo cyclic function */
+ cyclic = cyclic_register(cyclic_demo, time_ms * 1000, "cyclic_demo",
+ info);
+ if (!cyclic)
+ printf("Registering of cyclic_demo failed\n");
+
+ printf("Registered function \"%s\" to be executed all %dms\n",
+ "cyclic_demo", time_ms);
+
+ return 0;
+}
+
+static int do_cyclic_list(struct cmd_tbl *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ struct cyclic_info *cyclic, *tmp;
+ u64 cnt, freq;
+
+ list_for_each_entry_safe(cyclic, tmp, cyclic_get_list(), list) {
+ cnt = cyclic->run_cnt * 1000000ULL * 100ULL;
+ freq = lldiv(cnt, timer_get_us() - cyclic->start_time_us);
+ printf("function: %s, cpu-time: %lld us, frequency: %lld.%02d times/s\n",
+ cyclic->name, cyclic->cpu_time_us,
+ lldiv(freq, 100), do_div(freq, 100));
+ }
+
+ return 0;
+}
+
+static char cyclic_help_text[] =
+ "cyclic demo <cycletime_ms> <delay_us> - register cyclic demo function\n"
+ "cyclic list - list cyclic functions\n";
+
+U_BOOT_CMD_WITH_SUBCMDS(cyclic, "Cyclic", cyclic_help_text,
+ U_BOOT_SUBCMD_MKENT(demo, 3, 1, do_cyclic_demo),
+ U_BOOT_SUBCMD_MKENT(list, 1, 1, do_cyclic_list));
diff --git a/common/Kconfig b/common/Kconfig
index 2c3f7f4..3e44acd 100644
--- a/common/Kconfig
+++ b/common/Kconfig
@@ -545,6 +545,26 @@
menu "Start-up hooks"
+config CYCLIC
+ bool "General-purpose cyclic execution mechanism"
+ help
+ This enables a general-purpose cyclic execution infrastructure,
+ to allow "small" (run-time wise) functions to be executed at
+ a specified frequency. Things like LED blinking or watchdog
+ triggering are examples for such tasks.
+
+if CYCLIC
+
+config CYCLIC_MAX_CPU_TIME_US
+ int "Sets the max allowed time for a cyclic function in us"
+ default 1000
+ help
+ The max allowed time for a cyclic function in us. If a functions
+ takes longer than this duration this function will get unregistered
+ automatically.
+
+endif # CYCLIC
+
config EVENT
bool "General-purpose event-handling mechanism"
default y if SANDBOX
diff --git a/common/Makefile b/common/Makefile
index 2ed8672..1d56c9f 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -84,6 +84,7 @@
endif
endif
+obj-$(CONFIG_CYCLIC) += cyclic.o
obj-$(CONFIG_$(SPL_TPL_)EVENT) += event.o
obj-$(CONFIG_$(SPL_TPL_)HASH) += hash.o
diff --git a/common/board_f.c b/common/board_f.c
index 18e2246..deb46be 100644
--- a/common/board_f.c
+++ b/common/board_f.c
@@ -16,6 +16,7 @@
#include <console.h>
#include <cpu.h>
#include <cpu_func.h>
+#include <cyclic.h>
#include <display_options.h>
#include <dm.h>
#include <env.h>
@@ -828,6 +829,7 @@
initf_malloc,
log_init,
initf_bootstage, /* uses its own timer, so does not need DM */
+ cyclic_init,
event_init,
#ifdef CONFIG_BLOBLIST
bloblist_init,
diff --git a/common/board_r.c b/common/board_r.c
index 56eb60f..062bc3e 100644
--- a/common/board_r.c
+++ b/common/board_r.c
@@ -13,6 +13,7 @@
#include <api.h>
#include <bootstage.h>
#include <cpu_func.h>
+#include <cyclic.h>
#include <display_options.h>
#include <exports.h>
#ifdef CONFIG_MTD_NOR_FLASH
@@ -611,6 +612,7 @@
#endif
initr_barrier,
initr_malloc,
+ cyclic_init,
log_init,
initr_bootstage, /* Needs malloc() but has its own timer */
#if defined(CONFIG_CONSOLE_RECORD)
diff --git a/common/cyclic.c b/common/cyclic.c
new file mode 100644
index 0000000..cd5dcb1
--- /dev/null
+++ b/common/cyclic.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * A general-purpose cyclic execution infrastructure, to allow "small"
+ * (run-time wise) functions to be executed at a specified frequency.
+ * Things like LED blinking or watchdog triggering are examples for such
+ * tasks.
+ *
+ * Copyright (C) 2022 Stefan Roese <sr@denx.de>
+ */
+
+#include <cyclic.h>
+#include <log.h>
+#include <malloc.h>
+#include <time.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <asm/global_data.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct list_head *cyclic_get_list(void)
+{
+ return &gd->cyclic->cyclic_list;
+}
+
+struct cyclic_info *cyclic_register(cyclic_func_t func, uint64_t delay_us,
+ const char *name, void *ctx)
+{
+ struct cyclic_info *cyclic;
+
+ if (!gd->cyclic->cyclic_ready) {
+ pr_debug("Cyclic IF not ready yet\n");
+ return NULL;
+ }
+
+ cyclic = calloc(1, sizeof(struct cyclic_info));
+ if (!cyclic) {
+ pr_debug("Memory allocation error\n");
+ return NULL;
+ }
+
+ /* Store values in struct */
+ cyclic->func = func;
+ cyclic->ctx = ctx;
+ cyclic->name = strdup(name);
+ cyclic->delay_us = delay_us;
+ cyclic->start_time_us = timer_get_us();
+ list_add_tail(&cyclic->list, &gd->cyclic->cyclic_list);
+
+ return cyclic;
+}
+
+int cyclic_unregister(struct cyclic_info *cyclic)
+{
+ list_del(&cyclic->list);
+ free(cyclic);
+
+ return 0;
+}
+
+void cyclic_run(void)
+{
+ struct cyclic_info *cyclic, *tmp;
+ uint64_t now, cpu_time;
+
+ /* Prevent recursion */
+ if (gd->cyclic->cyclic_running)
+ return;
+
+ gd->cyclic->cyclic_running = true;
+ list_for_each_entry_safe(cyclic, tmp, &gd->cyclic->cyclic_list, list) {
+ /*
+ * Check if this cyclic function needs to get called, e.g.
+ * do not call the cyclic func too often
+ */
+ now = timer_get_us();
+ if (time_after_eq64(now, cyclic->next_call)) {
+ /* Call cyclic function and account it's cpu-time */
+ cyclic->next_call = now + cyclic->delay_us;
+ cyclic->func(cyclic->ctx);
+ cyclic->run_cnt++;
+ cpu_time = timer_get_us() - now;
+ cyclic->cpu_time_us += cpu_time;
+
+ /* Check if cpu-time exceeds max allowed time */
+ if (cpu_time > CONFIG_CYCLIC_MAX_CPU_TIME_US) {
+ pr_err("cyclic function %s took too long: %lldus vs %dus max, disabling\n",
+ cyclic->name, cpu_time,
+ CONFIG_CYCLIC_MAX_CPU_TIME_US);
+
+ /* Unregister this cyclic function */
+ cyclic_unregister(cyclic);
+ }
+ }
+ }
+ gd->cyclic->cyclic_running = false;
+}
+
+int cyclic_uninit(void)
+{
+ struct cyclic_info *cyclic, *tmp;
+
+ list_for_each_entry_safe(cyclic, tmp, &gd->cyclic->cyclic_list, list)
+ cyclic_unregister(cyclic);
+ gd->cyclic->cyclic_ready = false;
+
+ return 0;
+}
+
+int cyclic_init(void)
+{
+ int size = sizeof(struct cyclic_drv);
+
+ gd->cyclic = (struct cyclic_drv *)malloc(size);
+ if (!gd->cyclic)
+ return -ENOMEM;
+
+ memset(gd->cyclic, '\0', size);
+ INIT_LIST_HEAD(&gd->cyclic->cyclic_list);
+ gd->cyclic->cyclic_ready = true;
+
+ return 0;
+}
diff --git a/configs/octeon_nic23_defconfig b/configs/octeon_nic23_defconfig
index 95e98c1..098831e 100644
--- a/configs/octeon_nic23_defconfig
+++ b/configs/octeon_nic23_defconfig
@@ -20,6 +20,8 @@
CONFIG_OF_BOARD_FIXUP=y
CONFIG_SYS_CONSOLE_ENV_OVERWRITE=y
# CONFIG_SYS_DEVICE_NULLDEV is not set
+CONFIG_CYCLIC=y
+CONFIG_CYCLIC_MAX_CPU_TIME_US=5000
CONFIG_ARCH_MISC_INIT=y
CONFIG_BOARD_EARLY_INIT_F=y
CONFIG_BOARD_LATE_INIT=y
@@ -41,6 +43,7 @@
CONFIG_CMD_EXT4=y
CONFIG_CMD_FAT=y
CONFIG_CMD_FS_GENERIC=y
+CONFIG_CMD_CYCLIC=y
CONFIG_EFI_PARTITION=y
CONFIG_ENV_IS_IN_SPI_FLASH=y
CONFIG_TFTP_TSIZE=y
diff --git a/doc/develop/cyclic.rst b/doc/develop/cyclic.rst
new file mode 100644
index 0000000..43bedac
--- /dev/null
+++ b/doc/develop/cyclic.rst
@@ -0,0 +1,50 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+Cyclic functions
+================
+
+The cyclic function execution infrastruture provides a way to periodically
+execute code, e.g. every 100ms. Examples for such functions might be LED
+blinking etc. The functions that are hooked into this cyclic list should
+be small timewise as otherwise the execution of the other code that relies
+on a high frequent polling (e.g. UART rx char ready check) might be
+delayed too much. To detect cyclic functions with a too long execution
+time, the Kconfig option `CONFIG_CYCLIC_MAX_CPU_TIME_US` is introduced,
+which configures the max allowed time for such a cyclic function. If it's
+execution time exceeds this time, this cyclic function will get removed
+from the cyclic list.
+
+Registering a cyclic function
+-----------------------------
+
+To register a cyclic function, use something like this::
+
+ static void cyclic_demo(void *ctx)
+ {
+ /* Just a small dummy delay here */
+ udelay(10);
+ }
+
+ int board_init(void)
+ {
+ struct cyclic_info *cyclic;
+
+ /* Register demo cyclic function */
+ cyclic = cyclic_register(cyclic_demo, 10 * 1000, "cyclic_demo", NULL);
+ if (!cyclic)
+ printf("Registering of cyclic_demo failed\n");
+
+ return 0;
+ }
+
+This will register the function `cyclic_demo()` to be periodically
+executed all 10ms.
+
+How is this cyclic functionality integrated / executed?
+--------------------------------------------------------
+
+The cyclic infrastructure integrates the main function responsible for
+calling all registered cyclic functions cyclic_run() into the common
+WATCHDOG_RESET macro. This guarantees that cyclic_run() is executed
+very often, which is necessary for the cyclic functions to get scheduled
+and executed at their configured periods.
diff --git a/doc/develop/index.rst b/doc/develop/index.rst
index f7ee09d..ce6b38e 100644
--- a/doc/develop/index.rst
+++ b/doc/develop/index.rst
@@ -27,6 +27,7 @@
ci_testing
commands
config_binding
+ cyclic
devicetree/index
distro
driver-model/index
diff --git a/doc/usage/cmd/cyclic.rst b/doc/usage/cmd/cyclic.rst
new file mode 100644
index 0000000..3085cc7
--- /dev/null
+++ b/doc/usage/cmd/cyclic.rst
@@ -0,0 +1,45 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+cyclic command
+==============
+
+Synopsis
+--------
+
+::
+
+ cyclic list
+
+Description
+-----------
+
+The cyclic list command provides a list of the currently registered
+cyclic functions.
+
+This shows the following information:
+
+Function
+ Function name
+
+cpu-time
+ Total time spent in this cyclic function.
+
+Frequency
+ Frequency of execution of this function, e.g. 100 times/s for a
+ pediod of 10ms.
+
+
+See :doc:`../../develop/cyclic` for more information on cyclic functions.
+
+Example
+-------
+
+::
+
+ => cyclic list
+ function: cyclic_demo, cpu-time: 52906 us, frequency: 99.20 times/s
+
+Configuration
+-------------
+
+The cyclic command is only available if CONFIG_CMD_CYCLIC=y.
diff --git a/doc/usage/index.rst b/doc/usage/index.rst
index b36305c..73966c6 100644
--- a/doc/usage/index.rst
+++ b/doc/usage/index.rst
@@ -33,6 +33,7 @@
cmd/bootz
cmd/cbsysinfo
cmd/conitrace
+ cmd/cyclic
cmd/dm
cmd/echo
cmd/eficonfig
diff --git a/fs/cramfs/uncompress.c b/fs/cramfs/uncompress.c
index f431cc4..38e10e2 100644
--- a/fs/cramfs/uncompress.c
+++ b/fs/cramfs/uncompress.c
@@ -62,7 +62,7 @@
stream.avail_in = 0;
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
- stream.outcb = (cb_func) WATCHDOG_RESET;
+ stream.outcb = (cb_func)watchdog_reset_func;
#else
stream.outcb = Z_NULL;
#endif /* CONFIG_HW_WATCHDOG */
diff --git a/include/asm-generic/global_data.h b/include/asm-generic/global_data.h
index 805a6fd..9006c76 100644
--- a/include/asm-generic/global_data.h
+++ b/include/asm-generic/global_data.h
@@ -20,6 +20,7 @@
*/
#ifndef __ASSEMBLY__
+#include <cyclic.h>
#include <event_internal.h>
#include <fdtdec.h>
#include <membuff.h>
@@ -474,6 +475,12 @@
*/
struct event_state event_state;
#endif
+#ifdef CONFIG_CYCLIC
+ /**
+ * @cyclic: cyclic driver data
+ */
+ struct cyclic_drv *cyclic;
+#endif
/**
* @dmtag_list: List of DM tags
*/
diff --git a/include/cyclic.h b/include/cyclic.h
new file mode 100644
index 0000000..2390223
--- /dev/null
+++ b/include/cyclic.h
@@ -0,0 +1,138 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * A general-purpose cyclic execution infrastructure, to allow "small"
+ * (run-time wise) functions to be executed at a specified frequency.
+ * Things like LED blinking or watchdog triggering are examples for such
+ * tasks.
+ *
+ * Copyright (C) 2022 Stefan Roese <sr@denx.de>
+ */
+
+#ifndef __cyclic_h
+#define __cyclic_h
+
+#include <linux/list.h>
+#include <asm/types.h>
+
+/**
+ * struct cyclic_drv - Cyclic driver internal data
+ *
+ * @cyclic_list: Cylic list node
+ * @cyclic_ready: Flag if cyclic infrastructure is ready
+ * @cyclic_running: Flag if cyclic infrastructure is running
+ */
+struct cyclic_drv {
+ struct list_head cyclic_list;
+ bool cyclic_ready;
+ bool cyclic_running;
+};
+
+/**
+ * struct cyclic_info - Information about cyclic execution function
+ *
+ * @func: Function to call periodically
+ * @ctx: Context pointer to get passed to this function
+ * @name: Name of the cyclic function, e.g. shown in the commands
+ * @delay_ns: Delay is ns after which this function shall get executed
+ * @start_time_us: Start time in us, when this function started its execution
+ * @cpu_time_us: Total CPU time of this function
+ * @run_cnt: Counter of executions occurances
+ * @next_call: Next time in us, when the function shall be executed again
+ * @list: List node
+ */
+struct cyclic_info {
+ void (*func)(void *ctx);
+ void *ctx;
+ char *name;
+ uint64_t delay_us;
+ uint64_t start_time_us;
+ uint64_t cpu_time_us;
+ uint64_t run_cnt;
+ uint64_t next_call;
+ struct list_head list;
+};
+
+/** Function type for cyclic functions */
+typedef void (*cyclic_func_t)(void *ctx);
+
+#if defined(CONFIG_CYCLIC)
+/**
+ * cyclic_register - Register a new cyclic function
+ *
+ * @func: Function to call periodically
+ * @delay_us: Delay is us after which this function shall get executed
+ * @name: Cyclic function name/id
+ * @ctx: Context to pass to the function
+ * @return: pointer to cyclic_struct if OK, NULL on error
+ */
+struct cyclic_info *cyclic_register(cyclic_func_t func, uint64_t delay_us,
+ const char *name, void *ctx);
+
+/**
+ * cyclic_unregister - Unregister a cyclic function
+ *
+ * @cyclic: Pointer to cyclic_struct of the function that shall be removed
+ * @return: 0 if OK, -ve on error
+ */
+int cyclic_unregister(struct cyclic_info *cyclic);
+
+/**
+ * cyclic_init() - Set up cyclic functions
+ *
+ * Init a list of cyclic functions, so that these can be added as needed
+ */
+int cyclic_init(void);
+
+/**
+ * cyclic_uninit() - Clean up cyclic functions
+ *
+ * This removes all cyclic functions
+ */
+int cyclic_uninit(void);
+
+/**
+ * cyclic_get_list() - Get cyclic list pointer
+ *
+ * Return the cyclic list pointer
+ *
+ * @return: pointer to cyclic_list
+ */
+struct list_head *cyclic_get_list(void);
+
+/**
+ * cyclic_run() - Interate over all registered cyclic functions
+ *
+ * Interate over all registered cyclic functions and if the it's function
+ * needs to be executed, then call into these registered functions.
+ */
+void cyclic_run(void);
+#else
+static inline struct cyclic_info *cyclic_register(cyclic_func_t func,
+ uint64_t delay_us,
+ const char *name,
+ void *ctx)
+{
+ return NULL;
+}
+
+static inline int cyclic_unregister(struct cyclic_info *cyclic)
+{
+ return 0;
+}
+
+static inline void cyclic_run(void)
+{
+}
+
+static inline int cyclic_init(void)
+{
+ return 0;
+}
+
+static inline int cyclic_uninit(void)
+{
+ return 0;
+}
+#endif
+
+#endif
diff --git a/include/image.h b/include/image.h
index e4c6a50..a148073 100644
--- a/include/image.h
+++ b/include/image.h
@@ -360,6 +360,12 @@
#endif
} bootm_headers_t;
+#ifdef CONFIG_LMB
+#define images_lmb(_images) (&(_images)->lmb)
+#else
+#define images_lmb(_images) NULL
+#endif
+
extern bootm_headers_t images;
/*
diff --git a/include/time.h b/include/time.h
index 9deb2cf..3b2ba09 100644
--- a/include/time.h
+++ b/include/time.h
@@ -83,6 +83,25 @@
(time_after_eq(a,b) && \
time_before(a,c))
+/* Same as above, but does so with platform independent 64bit types.
+ * These must be used when utilizing jiffies_64 (i.e. return value of
+ * get_jiffies_64() */
+#define time_after64(a,b) \
+ (typecheck(__u64, a) && \
+ typecheck(__u64, b) && \
+ ((__s64)((b) - (a)) < 0))
+#define time_before64(a,b) time_after64(b,a)
+
+#define time_after_eq64(a,b) \
+ (typecheck(__u64, a) && \
+ typecheck(__u64, b) && \
+ ((__s64)((a) - (b)) >= 0))
+#define time_before_eq64(a,b) time_after_eq64(b,a)
+
+#define time_in_range64(a, b, c) \
+ (time_after_eq64(a, b) && \
+ time_before_eq64(a, c))
+
/**
* usec2ticks() - Convert microseconds to internal ticks
*
diff --git a/include/watchdog.h b/include/watchdog.h
index 813cc8f..0a9777e 100644
--- a/include/watchdog.h
+++ b/include/watchdog.h
@@ -11,6 +11,8 @@
#define _WATCHDOG_H_
#if !defined(__ASSEMBLY__)
+#include <cyclic.h>
+
/*
* Reset the watchdog timer, always returns 0
*
@@ -60,11 +62,16 @@
/* Don't require the watchdog to be enabled in SPL */
#if defined(CONFIG_SPL_BUILD) && \
!defined(CONFIG_SPL_WATCHDOG)
- #define WATCHDOG_RESET() {}
+ #define WATCHDOG_RESET() { \
+ cyclic_run(); \
+ }
#else
extern void watchdog_reset(void);
- #define WATCHDOG_RESET watchdog_reset
+ #define WATCHDOG_RESET() { \
+ watchdog_reset(); \
+ cyclic_run(); \
+ }
#endif
#endif
#else
@@ -74,11 +81,21 @@
#if defined(__ASSEMBLY__)
#define WATCHDOG_RESET /*XXX DO_NOT_DEL_THIS_COMMENT*/
#else
- #define WATCHDOG_RESET() {}
+ #define WATCHDOG_RESET() { \
+ cyclic_run(); \
+ }
#endif /* __ASSEMBLY__ */
#endif /* CONFIG_WATCHDOG && !__ASSEMBLY__ */
#endif /* CONFIG_HW_WATCHDOG */
+#if !defined(__ASSEMBLY__)
+/* Currently only needed for fs/cramfs/uncompress.c */
+static inline void watchdog_reset_func(void)
+{
+ WATCHDOG_RESET();
+}
+#endif
+
/*
* Prototypes from $(CPU)/cpu.c.
*/
diff --git a/test/common/Makefile b/test/common/Makefile
index 9087788..cc918f6 100644
--- a/test/common/Makefile
+++ b/test/common/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0+
obj-y += cmd_ut_common.o
obj-$(CONFIG_AUTOBOOT) += test_autoboot.o
+obj-$(CONFIG_CYCLIC) += cyclic.o
obj-$(CONFIG_EVENT) += event.o
diff --git a/test/common/cyclic.c b/test/common/cyclic.c
new file mode 100644
index 0000000..a5c4e78
--- /dev/null
+++ b/test/common/cyclic.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Stefan Roese <sr@denx.de>
+ */
+
+#include <common.h>
+#include <cyclic.h>
+#include <dm.h>
+#include <test/common.h>
+#include <test/test.h>
+#include <test/ut.h>
+#include <watchdog.h>
+#include <linux/delay.h>
+
+/* Test that cyclic function is called */
+static bool cyclic_active = false;
+
+static void cyclic_test(void *ctx)
+{
+ cyclic_active = true;
+}
+
+static int dm_test_cyclic_running(struct unit_test_state *uts)
+{
+ cyclic_active = false;
+ ut_assertnonnull(cyclic_register(cyclic_test, 10 * 1000, "cyclic_demo",
+ NULL));
+
+ /* Execute all registered cyclic functions */
+ WATCHDOG_RESET();
+ ut_asserteq(true, cyclic_active);
+
+ return 0;
+}
+COMMON_TEST(dm_test_cyclic_running, 0);
diff --git a/test/test-main.c b/test/test-main.c
index 4f1c54b..ae34002 100644
--- a/test/test-main.c
+++ b/test/test-main.c
@@ -6,6 +6,7 @@
#include <common.h>
#include <console.h>
+#include <cyclic.h>
#include <dm.h>
#include <event.h>
#include <dm/root.h>
@@ -220,6 +221,7 @@
static int test_pre_run(struct unit_test_state *uts, struct unit_test *test)
{
ut_assertok(event_init());
+ ut_assertok(cyclic_init());
if (test->flags & UT_TESTF_DM)
ut_assertok(dm_test_pre_run(uts));
@@ -265,6 +267,7 @@
ut_unsilence_console(uts);
if (test->flags & UT_TESTF_DM)
ut_assertok(dm_test_post_run(uts));
+ ut_assertok(cyclic_uninit());
ut_assertok(event_uninit());
return 0;