From 302d671ea2cff055b78e244d059c676eb9692d26 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Thu, 20 Aug 2020 08:07:29 -0600 Subject: [PATCH] emul: Create an emulation implementation Create a header file and implementation for emulators. Set up a linker list so that emulators can be found and initialised at start-up. Emulators are used to emulate hardware devices, to support testing of various subsystems. For example, it is possible to write an emulator for an I2C compass such that it appears on the I2C bus and can be used just like a real hardware device. Emulators often implement special features for testing. For example a compass may support returning bogus data if the I2C bus speed is too high, or may return invalid measurements if calibration has not yet been completed. This allows for testing that high-level code can handle these situations correctly. Test coverage can therefore approach 100% if all failure conditions are emulated. Signed-off-by: Peter Bigot Signed-off-by: Simon Glass --- CODEOWNERS | 2 + include/emul.h | 108 +++++++++++++++++++++++++++++++++++ include/linker/common-rom.ld | 9 +++ subsys/CMakeLists.txt | 1 + subsys/Kconfig | 2 + subsys/emul/CMakeLists.txt | 5 ++ subsys/emul/Kconfig | 37 ++++++++++++ subsys/emul/emul.c | 63 ++++++++++++++++++++ 8 files changed, 227 insertions(+) create mode 100644 include/emul.h create mode 100644 subsys/emul/CMakeLists.txt create mode 100644 subsys/emul/Kconfig create mode 100644 subsys/emul/emul.c diff --git a/CODEOWNERS b/CODEOWNERS index dcb8816b61..6c6600653a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -365,6 +365,7 @@ /include/dt-bindings/dma/stm32_dma.h @cybertale /include/dt-bindings/pcie/ @andrewboie /include/dt-bindings/usb/usb.h @galak @finikorg +/include/emul.h @sjg20 /include/fs/ @nashif @wentongwu /include/init.h @andrewboie @andyross /include/irq.h @andrewboie @andyross @@ -472,6 +473,7 @@ /subsys/disk/disk_access_sdhc.h @JunYangNXP /subsys/disk/disk_access_usdhc.c @JunYangNXP /subsys/disk/disk_access_stm32_sdmmc.c @anthonybrandon +/subsys/emul/ @sjg20 /subsys/fb/ @jfischer-phytec-iot /subsys/fs/ @nashif /subsys/fs/fcb/ @nvlsianpu diff --git a/include/emul.h b/include/emul.h new file mode 100644 index 0000000000..27fc9b7776 --- /dev/null +++ b/include/emul.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2020 Nordic Semiconductor ASA + * Copyright 2020 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_EMUL_H_ +#define ZEPHYR_INCLUDE_EMUL_H_ + +/** + * @brief Emulators used to test drivers and higher-level code that uses them + * @defgroup io_emulators Emulator interface + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +struct device; +struct emul; + +/** + * Structure uniquely identifying a device to be emulated + * + * Currently this uses the device node label, but that will go away by 2.5. + */ +struct emul_link_for_bus { + const char *label; +}; + +/** List of emulators attached to a bus */ +struct emul_list_for_bus { + /** Identifiers for children of the node */ + const struct emul_link_for_bus *children; + /** Number of children of the node */ + unsigned int num_children; +}; + +/** + * Standard callback for emulator initialisation providing the initialiser + * record and the device that calls the emulator functions. + * + * @param emul Emulator to init + * @param parent Parent device that is using the emulator + */ +typedef int (*emul_init_t)(const struct emul *emul, struct device *parent); + +/** An emulator instance */ +struct emul { + /** function used to initialise the emulator state */ + emul_init_t init; + /** handle to the device for which this provides low-level emulation */ + const char *dev_label; + /** Emulator-specific configuration data */ + const void *cfg; +}; + +/** + * Emulators are aggregated into an array at link time, from which emulating + * devices can find the emulators that they are to use. + */ +extern const struct emul __emul_list_start[]; +extern const struct emul __emul_list_end[]; + +/* Use the devicetree node identifier as a unique name. */ +#define EMUL_REG_NAME(node_id) (_CONCAT(__emulreg_, node_id)) + +/** + * Define a new emulator + * + * This adds a new struct emul to the linker list of emulations. This is + * typically used in your emulator's DT_INST_FOREACH_STATUS_OKAY() clause. + * + * @param init_ptr function to call to initialise the emulator (see emul_init + * typedef) + * @param node_id Node ID of the driver to emulate (e.g. DT_DRV_INST(n)) + * @param cfg_ptr emulator-specific configuration data + */ +#define EMUL_DEFINE(init_ptr, node_id, cfg_ptr) \ + static struct emul EMUL_REG_NAME(node_id) \ + __attribute__((__section__(".emulators"))) __used = { \ + .init = (init_ptr), \ + .dev_label = DT_LABEL(node_id), \ + .cfg = (cfg_ptr), \ + }; + +/** + * Set up a list of emulators + * + * @param dev Device the emulators are attached to (e.g. an I2C controller) + * @param list List of devices to set up + * @return 0 if OK + * @return negative value on error + */ +int emul_init_for_bus_from_list(struct device *dev, + const struct emul_list_for_bus *list); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_EMUL_H_ */ diff --git a/include/linker/common-rom.ld b/include/linker/common-rom.ld index f938b3dffe..6501ef7613 100644 --- a/include/linker/common-rom.ld +++ b/include/linker/common-rom.ld @@ -106,6 +106,15 @@ Z_ITERABLE_SECTION_ROM(settings_handler_static, 4) #endif +#if defined(CONFIG_EMUL) + SECTION_DATA_PROLOGUE(log_const_sections,,) + { + __emul_list_start = .; + KEEP(*(SORT_BY_NAME(".emulators"))); + __emul_list_end = .; + } GROUP_LINK_IN(ROMABLE_REGION) +#endif /* CONFIG_EMUL */ + SECTION_DATA_PROLOGUE(log_const_sections,,) { __log_const_start = .; diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index d335d2d080..badb969729 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -7,6 +7,7 @@ add_subdirectory_ifdef(CONFIG_CONSOLE_SUBSYS console) add_subdirectory_ifdef(CONFIG_SHELL shell) add_subdirectory_ifdef(CONFIG_CPLUSPLUS cpp) add_subdirectory_ifdef(CONFIG_DISK_ACCESS disk) +add_subdirectory_ifdef(CONFIG_EMUL emul) add_subdirectory(fs) add_subdirectory(mgmt) add_subdirectory_ifdef(CONFIG_MCUBOOT_IMG_MANAGER dfu) diff --git a/subsys/Kconfig b/subsys/Kconfig index 6dc00e5857..520a7830e3 100644 --- a/subsys/Kconfig +++ b/subsys/Kconfig @@ -17,6 +17,8 @@ source "subsys/debug/Kconfig" source "subsys/disk/Kconfig" +source "subsys/emul/Kconfig" + source "subsys/fb/Kconfig" source "subsys/fs/Kconfig" diff --git a/subsys/emul/CMakeLists.txt b/subsys/emul/CMakeLists.txt new file mode 100644 index 0000000000..2bb6a47b6e --- /dev/null +++ b/subsys/emul/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_EMUL emul.c) diff --git a/subsys/emul/Kconfig b/subsys/emul/Kconfig new file mode 100644 index 0000000000..0a8b455cc9 --- /dev/null +++ b/subsys/emul/Kconfig @@ -0,0 +1,37 @@ +# Emulator configuration options + +# Copyright 2020 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +# +# Emulator options +# +menuconfig EMUL + bool "Emulation drivers" + help + Enable Emulation Driver Configuration + These drivers are used to emulate hardware devices, to support testing + of various subsystems. For example, it is possible to write an + emulator for an I2C compass such that it appears on the I2C bus and + can be used just like a real hardware device. + + Emulators often implement special features for testing. For example + a compass may support returning bogus data if the I2C bus speed is + too high, or may return invalid measurements if calibration has not + yet been completed. This allows for testing that high-level code can + handle these situations correctly. Test coverage can therefore + approach 100% if all failure conditions are emulated. + +if EMUL + +config EMUL_INIT_PRIORITY + int "Init priority" + default 60 + help + Emulation device driver initialisation priority. + +module = EMUL +module-str = emul +source "subsys/logging/Kconfig.template.log_config" + +endif diff --git a/subsys/emul/emul.c b/subsys/emul/emul.c new file mode 100644 index 0000000000..3fb8c6476b --- /dev/null +++ b/subsys/emul/emul.c @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Google LLC + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define LOG_LEVEL CONFIG_EMUL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(emul); + +#include +#include +#include + +/** + * Find a an emulator using its link information + * + * @param emul Emulator info to find + * @return pointer to emulator, or NULL if not found + */ +static const struct emul * +emul_find_by_link(const struct emul_link_for_bus *emul) +{ + const struct emul *erp; + + for (erp = __emul_list_start; erp < __emul_list_end; erp++) { + if (strcmp(erp->dev_label, emul->label) == 0) { + return erp; + } + } + + return NULL; +} + +int emul_init_for_bus_from_list(struct device *dev, + const struct emul_list_for_bus *list) +{ + const struct emul_list_for_bus *cfg = dev->config; + + /* + * Walk the list of children, find the corresponding emulator and + * initialise it. + */ + const struct emul_link_for_bus *elp; + const struct emul_link_for_bus *const end = + cfg->children + cfg->num_children; + + for (elp = cfg->children; elp < end; elp++) { + const struct emul *emul = emul_find_by_link(elp); + + __ASSERT_NO_MSG(emul); + + int rc = emul->init(emul, dev); + + if (rc != 0) { + LOG_WRN("Init %s emulator failed: %d\n", + elp->label, rc); + } + } + + return 0; +}