diff --git a/CODEOWNERS b/CODEOWNERS index dcb8816b61d..6c6600653a4 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 00000000000..27fc9b77762 --- /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 f938b3dffe4..6501ef76138 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 d335d2d0809..badb969729b 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 6dc00e58577..520a7830e36 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 00000000000..2bb6a47b6e6 --- /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 00000000000..0a8b455cc91 --- /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 00000000000..3fb8c6476bc --- /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; +}