zephyr/doc/hardware/emulator/bus_emulators.rst

204 lines
7.6 KiB
ReStructuredText

.. _bus_emul:
External Bus and Bus Connected Peripherals Emulators
####################################################
Overview
========
Zephyr supports a simple emulator framework to support testing of external peripheral drivers
without requiring real hardware.
Emulators are used to emulate external 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.
Concept
=======
The diagram below shows application code / high-level tests at the top.
This is the ultimate application we want to run.
.. figure:: img/arch.svg
:align: center
:alt: Emulator architecture showing tests, emulators and drivers
Below that are peripheral drivers, such as the AT24 EEPROM driver. We can test
peripheral drivers using an emulation driver connected via a emulated I2C
controller/emulator which passes I2C traffic from the AT24 driver to the AT24
simulator.
Separately we can test the STM32 and NXP I2C drivers on real hardware using API
tests. These require some sort of device attached to the bus, but with this, we
can validate much of the driver functionality.
Putting the two together, we can test the application and peripheral code
entirely on native_sim. Since we know that the I2C driver on the real hardware
works, we should expect the application and peripheral drivers to work on the
real hardware also.
Using the above framework we can test an entire application (e.g. Embedded
Controller) on native_sim using emulators for all non-chip drivers.
With this approach we can:
* Write individual tests for each driver (green), covering all failure modes,
error conditions, etc.
* Ensure 100% test coverage for drivers (green)
* Write tests for combinations of drivers, such as GPIOs provided by an I2C GPIO
expander driver talking over an I2C bus, with the GPIOs controlling a charger.
All of this can work in the emulated environment or on real hardware.
* Write a complex application that ties together all of these pieces and runs on
native_sim. We can develop on a host, use source-level debugging, etc.
* Transfer the application to any board which provides the required features
(e.g. I2C, enough GPIOs), by adding Kconfig and devicetree fragments.
Creating a Device Driver Emulator
=================================
The emulator subsystem is modeled on the :ref:`device_model_api`. You create
an emulator instance using one of the :c:func:`EMUL_DT_DEFINE()` or
:c:func:`EMUL_DT_INST_DEFINE()` APIs.
Emulators for peripheral devices reuse the same devicetree node as the real
device driver. This means that your emulator defines ``DT_DRV_COMPAT`` using the
same ``compat`` value from the real driver.
.. code-block:: C
/* From drivers/sensor/bm160/bm160.c */
#define DT_DRV_COMPAT bosch_bmi160
/* From drivers/sensor/bmi160/emul_bmi160.c */
#define DT_DRV_COMPAT bosch_bmi160
The ``EMUL_DT_DEFINE()`` function accepts two API types:
#. ``bus_api`` - This points to the API for the upstream bus that the emulator
connects to. The ``bus_api`` parameter is required. The supported
emulated bus types include I2C, SPI, eSPI, and MSPI.
#. ``_backend_api`` - This points to the device-class specific backend API for
the emulator. The ``_backend_api`` parameter is optional.
The diagram below demonstrates the logical organization of the ``bus_api`` and
``_backend_api`` using the BC1.2 charging detector driver as the model
device-class.
.. figure:: img/device_class_emulator.svg
:align: center
:alt: Device class example, demonstrating BC1.2 charging detectors.
The real code is shown in green, while the emulator code is shown in yellow.
The ``bus_api`` connects the BC1.2 emulators to the ``native_sim`` I2C
controller. The real BC1.2 drivers are unchanged and operate exactly as if there
was a physical I2C controller present in the system. The ``native_sim`` I2C
controller uses the ``bus_api`` to initiate register reads and writes to the
emulator.
The ``_backend_api`` provides a mechanism for tests to manipulate the emulator
out of band. Each device class defines it's own API functions. The backend API
functions focus on high-level behavior and do not provide hooks for specific
emulators.
In the case of the BC1.2 charging detector the backend API provides functions
to simulate connecting and disconnecting a charger to the emulated BC1.2 device.
Each emulator is responsible for updating the correct vendor specific registers
and potentially signalling an interrupt.
Example test flow:
#. Test registers BC1.2 detection callback using the Zephyr BC1.2 driver API.
#. Test connects a charger using the BC1.2 emulator backend.
#. Test verifies B1.2 detection callback invoked with correct charger type.
#. Test disconnects a charger using the BC1.2 emulator backend.
With this architecture, the same test can be used will all supported drivers in
the same driver class.
Available Emulators
===================
Zephyr includes the following emulators:
* I2C emulator driver, allowing drivers to be connected to an emulator so that
tests can be performed without access to the real hardware
* SPI emulator driver, which does the same for SPI
* eSPI emulator driver, which does the same for eSPI. The emulator is being
developed to support more functionalities.
* MSPI emulator driver, allowing drivers to be connected to an emulator so that
tests can be performed without access to the real hardware.
I2C Emulation features
----------------------
In the binding of the I2C emulated bus, there's a custom property for address
based forwarding. Given the following devicetree node:
.. code-block:: devicetree
i2c0: i2c@100 {
status = "okay";
compatible = "zephyr,i2c-emul-controller";
clock-frequency = <I2C_BITRATE_STANDARD>;
#address-cells = <1>;
#size-cells = <0>;
#forward-cells = <1>;
reg = <0x100 4>;
forwards = <&i2c1 0x20>;
};
The final property, ``forwards`` indicates that any read/write requests sent to
address ``0x20`` should be sent to ``i2c1`` with the same address. This allows
us to test both the controller and the target end of the communication on the
same image.
.. note::
The ``#forward-cells`` attribute should always be 1. Each entry in the
``forwards`` attribute consists of the phandle followed by the address. In
the example above, ``<&i2c1 0x20>`` will forward all read/write operations
made to ``i2c0`` at port ``0x20`` to ``i2c1`` on the same port. Since no
additional cells are used by the emulated controller, the number of cells
should remain 1.
Samples
=======
Here are some examples present in Zephyr:
#. Bosch BMI160 sensor driver connected via both I2C and SPI to an emulator:
.. zephyr-app-commands::
:zephyr-app: tests/drivers/sensor/bmi160
:board: native_sim
:goals: build
#. The same test can be built with a second EEPROM which is an Atmel AT24 EEPROM driver
connected via I2C an emulator:
.. zephyr-app-commands::
:zephyr-app: tests/drivers/eeprom/api
:board: native_sim
:goals: build
:gen-args: -DDTC_OVERLAY_FILE=at2x_emul.overlay -DEXTRA_CONF_FILE=at2x_emul.conf
API Reference
=============
.. doxygengroup:: io_emulators