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 <peter.bigot@nordicsemi.no>
Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass 2020-08-20 08:07:29 -06:00 committed by Anas Nashif
parent dce9580490
commit 302d671ea2
8 changed files with 227 additions and 0 deletions

View file

@ -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

108
include/emul.h Normal file
View file

@ -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_ */

View file

@ -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 = .;

View file

@ -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)

View file

@ -17,6 +17,8 @@ source "subsys/debug/Kconfig"
source "subsys/disk/Kconfig"
source "subsys/emul/Kconfig"
source "subsys/fb/Kconfig"
source "subsys/fs/Kconfig"

View file

@ -0,0 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_EMUL emul.c)

37
subsys/emul/Kconfig Normal file
View file

@ -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

63
subsys/emul/emul.c Normal file
View file

@ -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 <logging/log.h>
LOG_MODULE_REGISTER(emul);
#include <device.h>
#include <emul.h>
#include <string.h>
/**
* 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;
}