test: Add i2c emulation for targets

Update i2c_emul.c to support i2c_target_register and i2c_target_unregister
function calls as well as support address forwarding in emulation.
Address forwarding helps us test IPCs in native sim. Instead of having to
emulate 2 separate cores, we can forward read/write requests from one bus
to another bus (effectively creating a loop). This way the same image can
simulate both the controller and the target.

Signed-off-by: Yuval Peress <peress@google.com>
This commit is contained in:
Yuval Peress 2024-06-17 12:00:11 -06:00 committed by Anas Nashif
commit c394b2e6f8
11 changed files with 617 additions and 4 deletions

View file

@ -114,6 +114,7 @@
clock-frequency = <I2C_BITRATE_STANDARD>; clock-frequency = <I2C_BITRATE_STANDARD>;
#address-cells = <1>; #address-cells = <1>;
#size-cells = <0>; #size-cells = <0>;
#forward-cells = <1>;
reg = <0x100 4>; reg = <0x100 4>;
}; };

View file

@ -144,6 +144,38 @@ Zephyr includes the following emulators:
* MSPI emulator driver, allowing drivers to be connected to an emulator so that * MSPI emulator driver, allowing drivers to be connected to an emulator so that
tests can be performed without access to the real hardware. 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::
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
``fowards`` 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 Samples
======= =======
@ -166,6 +198,6 @@ Here are some examples present in Zephyr:
:gen-args: -DDTC_OVERLAY_FILE=at2x_emul.overlay -DOVERLAY_CONFIG=at2x_emul.conf :gen-args: -DDTC_OVERLAY_FILE=at2x_emul.overlay -DOVERLAY_CONFIG=at2x_emul.conf
API Reference API Reference
************* =============
.. doxygengroup:: io_emulators .. doxygengroup:: io_emulators

View file

@ -29,6 +29,15 @@ struct i2c_emul_data {
/* I2C host configuration */ /* I2C host configuration */
uint32_t config; uint32_t config;
uint32_t bitrate; uint32_t bitrate;
#ifdef CONFIG_I2C_TARGET
struct i2c_target_config *target_cfg;
#endif
};
struct i2c_emul_config {
struct emul_list_for_bus emul_list;
const struct i2c_dt_spec *forward_list;
uint16_t forward_list_size;
}; };
/** /**
@ -74,13 +83,104 @@ static int i2c_emul_get_config(const struct device *dev, uint32_t *dev_config)
return 0; return 0;
} }
#ifdef CONFIG_I2C_TARGET
static int i2c_emul_send_to_target(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs)
{
struct i2c_emul_data *data = dev->data;
const struct i2c_target_callbacks *callbacks = data->target_cfg->callbacks;
for (uint8_t i = 0; i < num_msgs; ++i) {
LOG_DBG(" msgs[%u].flags? 0x%02x", i, msgs[i].flags);
if (i2c_is_read_op(&msgs[i])) {
for (uint32_t j = 0; j < msgs[i].len; ++j) {
int rc = 0;
if (j == 0) {
LOG_DBG(" Calling read_requested with data %p",
(void *)&msgs[i].buf[j]);
rc = callbacks->read_requested(data->target_cfg,
&msgs[i].buf[j]);
} else {
LOG_DBG(" Calling read_processed with data %p",
(void *)&msgs[i].buf[j]);
rc = callbacks->read_processed(data->target_cfg,
&msgs[i].buf[j]);
}
if (rc != 0) {
return rc;
}
}
} else {
for (uint32_t j = 0; j < msgs[i].len; ++j) {
int rc = 0;
if (j == 0) {
LOG_DBG(" Calling write_requested");
rc = callbacks->write_requested(data->target_cfg);
}
if (rc != 0) {
return rc;
}
LOG_DBG(" Calling write_received with data 0x%02x",
msgs[i].buf[j]);
rc = callbacks->write_received(data->target_cfg, msgs[i].buf[j]);
if (rc != 0) {
return rc;
}
}
}
if (i2c_is_stop_op(&msgs[i])) {
int rc = callbacks->stop(data->target_cfg);
if (rc != 0) {
return rc;
}
}
}
return 0;
}
#endif /* CONFIG_I2C_TARGET*/
static int i2c_emul_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs, static int i2c_emul_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs,
uint16_t addr) uint16_t addr)
{ {
const struct i2c_emul_config *conf = dev->config;
struct i2c_emul *emul; struct i2c_emul *emul;
const struct i2c_emul_api *api; const struct i2c_emul_api *api;
int ret; int ret;
LOG_DBG("%s(dev=%p, addr=0x%02x)", __func__, (void *)dev, addr);
#ifdef CONFIG_I2C_TARGET
struct i2c_emul_data *data = dev->data;
/*
* First check if the bus is configured as a target, targets either listen to the address or
* ignore the messages. So if the address doesn't match, we're just going to bail.
*/
LOG_DBG(" has_target_cfg? %d", data->target_cfg != NULL);
if (data->target_cfg != NULL) {
LOG_DBG(" target_cfg->address? 0x%02x", data->target_cfg->address);
if (data->target_cfg->address != addr) {
return -EINVAL;
}
LOG_DBG(" forwarding to target");
return i2c_emul_send_to_target(dev, msgs, num_msgs);
}
#endif /* CONFIG_I2C_TARGET */
/*
* We're not a target, but lets check if we need to forward this request before we start
* looking for a peripheral.
*/
for (uint16_t i = 0; i < conf->forward_list_size; ++i) {
LOG_DBG(" Checking forward list [%u].addr? 0x%02x", i,
conf->forward_list[i].addr);
if (conf->forward_list[i].addr == addr) {
/* We need to forward this request */
return i2c_transfer(conf->forward_list[i].bus, msgs, num_msgs, addr);
}
}
emul = i2c_emul_find(dev, addr); emul = i2c_emul_find(dev, addr);
if (!emul) { if (!emul) {
return -EIO; return -EIO;
@ -132,12 +232,38 @@ int i2c_emul_register(const struct device *dev, struct i2c_emul *emul)
return 0; return 0;
} }
#ifdef CONFIG_I2C_TARGET
static int i2c_emul_target_register(const struct device *dev, struct i2c_target_config *cfg)
{
struct i2c_emul_data *data = dev->data;
data->target_cfg = cfg;
return 0;
}
static int i2c_emul_target_unregister(const struct device *dev, struct i2c_target_config *cfg)
{
struct i2c_emul_data *data = dev->data;
if (data->target_cfg != cfg) {
return -EINVAL;
}
data->target_cfg = NULL;
return 0;
}
#endif /* CONFIG_I2C_TARGET */
/* Device instantiation */ /* Device instantiation */
static const struct i2c_driver_api i2c_emul_api = { static const struct i2c_driver_api i2c_emul_api = {
.configure = i2c_emul_configure, .configure = i2c_emul_configure,
.get_config = i2c_emul_get_config, .get_config = i2c_emul_get_config,
.transfer = i2c_emul_transfer, .transfer = i2c_emul_transfer,
#ifdef CONFIG_I2C_TARGET
.target_register = i2c_emul_target_register,
.target_unregister = i2c_emul_target_unregister,
#endif
}; };
#define EMUL_LINK_AND_COMMA(node_id) \ #define EMUL_LINK_AND_COMMA(node_id) \
@ -145,12 +271,26 @@ static const struct i2c_driver_api i2c_emul_api = {
.dev = DEVICE_DT_GET(node_id), \ .dev = DEVICE_DT_GET(node_id), \
}, },
#define EMUL_FORWARD_ITEM(node_id, prop, idx) \
{ \
.bus = DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), \
.addr = DT_PHA_BY_IDX(node_id, prop, idx, addr), \
},
#define I2C_EMUL_INIT(n) \ #define I2C_EMUL_INIT(n) \
static const struct emul_link_for_bus emuls_##n[] = { \ static const struct emul_link_for_bus emuls_##n[] = { \
DT_FOREACH_CHILD_STATUS_OKAY(DT_DRV_INST(n), EMUL_LINK_AND_COMMA)}; \ DT_FOREACH_CHILD_STATUS_OKAY(DT_DRV_INST(n), EMUL_LINK_AND_COMMA)}; \
static struct emul_list_for_bus i2c_emul_cfg_##n = { \ static const struct i2c_dt_spec emul_forward_list_##n[] = { \
.children = emuls_##n, \ COND_CODE_1(DT_INST_NODE_HAS_PROP(n, forwards), \
.num_children = ARRAY_SIZE(emuls_##n), \ (DT_INST_FOREACH_PROP_ELEM(n, forwards, EMUL_FORWARD_ITEM)), ())}; \
static struct i2c_emul_config i2c_emul_cfg_##n = { \
.emul_list = \
{ \
.children = emuls_##n, \
.num_children = ARRAY_SIZE(emuls_##n), \
}, \
.forward_list = emul_forward_list_##n, \
.forward_list_size = ARRAY_SIZE(emul_forward_list_##n), \
}; \ }; \
static struct i2c_emul_data i2c_emul_data_##n = { \ static struct i2c_emul_data i2c_emul_data_##n = { \
.bitrate = DT_INST_PROP(n, clock_frequency), \ .bitrate = DT_INST_PROP(n, clock_frequency), \

View file

@ -10,3 +10,17 @@ include: i2c-controller.yaml
properties: properties:
reg: reg:
required: true required: true
forwards:
type: phandle-array
description: |
When added, read/write requests sent to this bus for a given address will
be forwarded to the specified phandle (must be another i2c bus). As an
example, if we wanted to forward any requests from i2c0@0x20 to i2c1, we
would use:
&i2c0 {
forward = <&i2c1 0x20>;
};
forward-cells:
- addr

View file

@ -0,0 +1,12 @@
# Copyright (c) 2024 Google LLC
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(i2c_emul)
target_sources(app PRIVATE
src/emulated_target.cpp
src/test_forwarding.cpp
)
target_include_directories(app PRIVATE include)

View file

@ -0,0 +1,30 @@
/*
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/ {
i2c1: i2c@400 {
status = "okay";
compatible = "zephyr,i2c-emul-controller";
clock-frequency = <I2C_BITRATE_STANDARD>;
#address-cells = <1>;
#size-cells = <0>;
#forward-cells = <1>;
reg = <0x400 4>;
};
i2c2: i2c@500 {
status = "okay";
compatible = "zephyr,i2c-emul-controller";
clock-frequency = <I2C_BITRATE_STANDARD>;
#address-cells = <1>;
#size-cells = <0>;
#forward-cells = <1>;
reg = <0x500 4>;
};
};
&i2c0 {
forwards = <&i2c1 0x20>, <&i2c2 0x24>;
};

View file

@ -0,0 +1,50 @@
/*
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _TESTS_DRIVERS_I2C_I2C_EMUL_INCLUDE_EMULATED_TARGET_H
#define _TESTS_DRIVERS_I2C_I2C_EMUL_INCLUDE_EMULATED_TARGET_H
#include <functional>
#define CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN, FUNCNAME, ...) \
std::function<RETURN(__VA_ARGS__)> FUNCNAME
#include <zephyr/devicetree.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/fff.h>
#define CONTROLLER_LABEL DT_NODELABEL(i2c0)
#define TARGET_LABEL(n) DT_NODELABEL(DT_CAT(i2c, n))
#define FORWARD_COUNT DT_PROP_LEN(CONTROLLER_LABEL, forwards)
extern struct i2c_target_callbacks emulated_callbacks[FORWARD_COUNT];
extern struct i2c_target_config emulated_target_config[FORWARD_COUNT];
/* Declare all the fake functions needed */
#define DECLARE_FAKE_TARGET_FUNCTIONS(node_id, prop, n) \
DECLARE_FAKE_VALUE_FUNC(int, target_read_requested_##n, struct i2c_target_config *, \
uint8_t *); \
DECLARE_FAKE_VALUE_FUNC(int, target_read_processed_##n, struct i2c_target_config *, \
uint8_t *); \
DECLARE_FAKE_VALUE_FUNC(int, target_write_requested_##n, struct i2c_target_config *); \
DECLARE_FAKE_VALUE_FUNC(int, target_write_received_##n, struct i2c_target_config *, \
uint8_t); \
DECLARE_FAKE_VALUE_FUNC(int, target_stop_##n, struct i2c_target_config *);
DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, DECLARE_FAKE_TARGET_FUNCTIONS)
#define FFF_FAKE_ACTION(node_id, prop, n, fn) \
do { \
fn(target_read_requested_##n); \
fn(target_read_processed_##n); \
fn(target_write_requested_##n); \
fn(target_write_received_##n); \
fn(target_stop_##n); \
} while (0);
#define FFF_FAKES_LIST_FOREACH(fn) \
DT_FOREACH_PROP_ELEM_VARGS(CONTROLLER_LABEL, forwards, FFF_FAKE_ACTION, fn)
#endif /* _TESTS_DRIVERS_I2C_I2C_EMUL_INCLUDE_EMULATED_TARGET_H */

View file

@ -0,0 +1,11 @@
# Copyright 2024 Google LLC
# SPDX-License-Identifier: Apache-2.0
CONFIG_ZTEST=y
CONFIG_I2C=y
CONFIG_I2C_TARGET=y
CONFIG_EMUL=y
CONFIG_CPP=y
CONFIG_STD_CPP17=y
CONFIG_REQUIRES_FULL_LIBCPP=y

View file

@ -0,0 +1,42 @@
/*
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
#include "emulated_target.hpp"
DEFINE_FFF_GLOBALS;
#define DEFINE_FAKE_TARGET_FUNCTION(node_id, prop, n) \
DEFINE_FAKE_VALUE_FUNC(int, target_read_requested_##n, struct i2c_target_config *, \
uint8_t *); \
DEFINE_FAKE_VALUE_FUNC(int, target_read_processed_##n, struct i2c_target_config *, \
uint8_t *); \
DEFINE_FAKE_VALUE_FUNC(int, target_write_requested_##n, struct i2c_target_config *); \
DEFINE_FAKE_VALUE_FUNC(int, target_write_received_##n, struct i2c_target_config *, \
uint8_t); \
DEFINE_FAKE_VALUE_FUNC(int, target_stop_##n, struct i2c_target_config *);
DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, DEFINE_FAKE_TARGET_FUNCTION);
#define DEFINE_EMULATED_CALLBACK(node_id, prop, n) \
[n] = { \
.write_requested = target_write_requested_##n, \
.read_requested = target_read_requested_##n, \
.write_received = target_write_received_##n, \
.read_processed = target_read_processed_##n, \
.stop = target_stop_##n, \
},
struct i2c_target_callbacks emulated_callbacks[FORWARD_COUNT] = {
DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, DEFINE_EMULATED_CALLBACK)};
#define DEFINE_EMULATED_TARGET_CONFIG(node_id, prop, n) \
[n] = { \
.flags = 0, \
.address = DT_PHA_BY_IDX(node_id, prop, n, addr), \
.callbacks = &emulated_callbacks[n], \
},
struct i2c_target_config emulated_target_config[FORWARD_COUNT] = {
DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, DEFINE_EMULATED_TARGET_CONFIG)};

View file

@ -0,0 +1,274 @@
/*
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
#include "emulated_target.hpp"
#include <zephyr/devicetree.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/fff.h>
#include <zephyr/ztest.h>
namespace
{
#define GET_TARGET_DEVICE(node_id, prop, n) DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, n)),
/* Get the devicetree constants */
constexpr const struct device *controller = DEVICE_DT_GET(CONTROLLER_LABEL);
constexpr const struct device *targets[FORWARD_COUNT] = {
DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, GET_TARGET_DEVICE)};
static void *i2c_emul_forwarding_setup(void)
{
// Register the target
for (int i = 0; i < FORWARD_COUNT; ++i) {
zassert_ok(i2c_target_register(targets[i], &emulated_target_config[i]));
}
return NULL;
}
static void i2c_emul_forwarding_before(void *fixture)
{
ARG_UNUSED(fixture);
// Reset all fakes
FFF_FAKES_LIST_FOREACH(RESET_FAKE);
FFF_RESET_HISTORY();
}
static void i2c_emul_forwarding_teardown(void *fixture)
{
ARG_UNUSED(fixture);
// Unregister the I2C target callbacks
for (int i = 0; i < FORWARD_COUNT; ++i) {
zassert_ok(i2c_target_unregister(targets[i], &emulated_target_config[i]));
}
}
ZTEST_SUITE(i2c_emul_forwarding, NULL, i2c_emul_forwarding_setup, i2c_emul_forwarding_before, NULL,
i2c_emul_forwarding_teardown);
ZTEST(i2c_emul_forwarding, test_write_is_forwarded)
{
// Try writing some values
for (uint8_t data = 0; data < 10; ++data) {
const int expected_call_count = 1 + data;
zassert_ok(i2c_write(controller, &data, sizeof(data),
emulated_target_config[0].address));
// Expected no reads to be made
zexpect_equal(0, target_read_requested_0_fake.call_count);
zexpect_equal(0, target_read_processed_0_fake.call_count);
// Expected N write requests to be made
zexpect_equal(expected_call_count, target_write_requested_0_fake.call_count,
"Expected to be called %d times, got %d", expected_call_count,
target_write_requested_0_fake.call_count);
zexpect_equal(expected_call_count, target_write_received_0_fake.call_count,
"Expected to be called %d times, got %d", expected_call_count,
target_write_received_0_fake.call_count);
// Check that the data written was correct
zexpect_equal(data, target_write_received_0_fake.arg1_val,
"Expected data value %u. got %u", data,
target_write_received_0_fake.arg1_val);
// Expect 1 stop call per write request
zexpect_equal(expected_call_count, target_stop_0_fake.call_count,
"Expected to be called %d times, got %d", expected_call_count,
target_stop_0_fake.call_count);
}
}
ZTEST(i2c_emul_forwarding, test_read_is_forwarded)
{
// Try reading some values
for (uint8_t i = 0; i < 10; ++i) {
const uint8_t expected_data[2] = {
static_cast<uint8_t>(0x1 * i),
static_cast<uint8_t>(0x2 * i),
};
const unsigned int expected_call_count = 1 + i;
uint8_t data[2];
// Setup some lambdas to do the actual reads using 'expected_data'
target_read_requested_0_fake.custom_fake =
[expected_data](struct i2c_target_config *, uint8_t *out) -> int {
*out = expected_data[0];
return 0;
};
target_read_processed_0_fake.custom_fake =
[expected_data](struct i2c_target_config *, uint8_t *out) -> int {
*out = expected_data[1];
return 0;
};
zassert_ok(i2c_read(controller, data, sizeof(data),
emulated_target_config[0].address));
// Expect the read functions to be called N times
zexpect_equal(expected_call_count, target_read_requested_0_fake.call_count,
"Expected to be called %d times, got %d", expected_call_count,
target_read_requested_0_fake.call_count);
zexpect_equal(expected_call_count, target_read_processed_0_fake.call_count,
"Expected to be called %d times, got %d", expected_call_count,
target_read_processed_0_fake.call_count);
// Expect the data read to match
zexpect_equal(expected_data[0], data[0], "Expected 0x%02x, got 0x%02x",
expected_data[0], data[0]);
zexpect_equal(expected_data[1], data[1], "Expected 0x%02x, got 0x%02x",
expected_data[1], data[1]);
// Expect 0 write calls
zexpect_equal(0, target_write_requested_0_fake.call_count);
zexpect_equal(0, target_write_received_0_fake.call_count);
// Expect 1 stop call per read
zexpect_equal(expected_call_count, target_stop_0_fake.call_count,
"Expected to be called %d times, got %d", expected_call_count,
target_stop_0_fake.call_count);
}
}
ZTEST(i2c_emul_forwarding, test_recover_failed_write)
{
uint8_t write_data[2];
// Fail on the write request (should never call the write_received function)
target_write_requested_0_fake.return_val = -EINVAL;
zassert_equal(-EINVAL, i2c_write(controller, write_data, sizeof(write_data),
emulated_target_config[0].address));
zexpect_equal(1, target_write_requested_0_fake.call_count, "Was called %d times",
target_write_requested_0_fake.call_count);
zexpect_equal(0, target_write_received_0_fake.call_count, "Was called %d times",
target_write_requested_0_fake.call_count);
// Next instruction should succeed
target_write_requested_0_fake.return_val = 0;
zassert_ok(i2c_write(controller, write_data, sizeof(write_data),
emulated_target_config[0].address));
zexpect_equal(2, target_write_requested_0_fake.call_count, "Was called %d times",
target_write_requested_0_fake.call_count);
zexpect_equal(2, target_write_received_0_fake.call_count, "Was called %d times",
target_write_requested_0_fake.call_count);
}
ZTEST(i2c_emul_forwarding, test_recover_failed_read)
{
uint8_t read_data[2];
// Fail the read_requested (should never call the read_processed function)
target_read_requested_0_fake.return_val = -EINVAL;
zassert_equal(-EINVAL, i2c_read(controller, read_data, sizeof(read_data),
emulated_target_config[0].address));
zexpect_equal(1, target_read_requested_0_fake.call_count, "Was called %d times",
target_read_requested_0_fake.call_count);
zexpect_equal(0, target_read_processed_0_fake.call_count, "Was called %d times",
target_read_processed_0_fake.call_count);
// Next instruction should pass
target_read_requested_0_fake.return_val = 0;
zassert_ok(i2c_read(controller, read_data, sizeof(read_data),
emulated_target_config[0].address));
zexpect_equal(2, target_read_requested_0_fake.call_count, "Was called %d times",
target_read_requested_0_fake.call_count);
zexpect_equal(1, target_read_processed_0_fake.call_count, "Was called %d times",
target_read_processed_0_fake.call_count);
}
ZTEST(i2c_emul_forwarding, test_transfer_is_forwarded)
{
uint8_t write_data[1] = {};
uint8_t read_data[2] = {};
struct i2c_msg msgs[] = {
{
.buf = write_data,
.len = sizeof(write_data),
.flags = I2C_MSG_WRITE,
},
{
.buf = read_data,
.len = sizeof(read_data),
.flags = I2C_MSG_READ | I2C_MSG_STOP,
},
};
int phase = 0;
target_write_requested_0_fake.custom_fake = [&phase](struct i2c_target_config *) -> int {
zassert_equal(0, phase, "Expected a call to write_requested before anything else");
phase++;
return 0;
};
target_write_received_0_fake.custom_fake = [&phase](struct i2c_target_config *,
uint8_t) -> int {
zassert_equal(1, phase, "Expected a call to write_received as the second step");
phase++;
return 0;
};
target_read_requested_0_fake.custom_fake = [&phase](struct i2c_target_config *,
uint8_t *) -> int {
zassert_equal(2, phase, "Expected a call to read_requested as the 3rd step");
phase++;
return 0;
};
target_read_processed_0_fake.custom_fake = [&phase](struct i2c_target_config *,
uint8_t *) -> int {
zassert_equal(3, phase, "Expected a call to read_processed as the 4th step");
phase++;
return 0;
};
target_stop_0_fake.custom_fake = [&phase](struct i2c_target_config *) -> int {
zassert_equal(4, phase, "Expected a call to stop as the 5th step");
phase++;
return 0;
};
zassert_ok(i2c_transfer(controller, msgs, ARRAY_SIZE(msgs),
emulated_target_config[0].address));
zexpect_equal(1, target_write_requested_0_fake.call_count,
"Expected target_write_requested to be called once, but got %d",
target_write_requested_0_fake.call_count);
zexpect_equal(1, target_write_received_0_fake.call_count,
"Expected target_write_received to be called once, but got %d",
target_write_received_0_fake.call_count);
zexpect_equal(1, target_read_requested_0_fake.call_count,
"Expected target_read_requested to be called once, but got %d",
target_read_requested_0_fake.call_count);
zexpect_equal(1, target_read_processed_0_fake.call_count,
"Expected target_read_processed to be called once, but got %d",
target_read_processed_0_fake.call_count);
zexpect_equal(1, target_stop_0_fake.call_count,
"Expected target_stop to be called once, but got %d",
target_stop_0_fake.call_count);
zexpect_equal(5, phase, "Expected a total of 5 phases, but got %d", phase);
}
ZTEST(i2c_emul_forwarding, test_forward_two_targets)
{
uint8_t read_data[2];
// Read the second forward and ensure that we only forwarded to the correct one
zassert_ok(i2c_read(controller, read_data, sizeof(read_data),
emulated_target_config[1].address));
// Check that we got the forward
zexpect_equal(1, target_read_requested_1_fake.call_count,
"Expected to be called 1 time, got %d",
target_read_requested_1_fake.call_count);
zexpect_equal(1, target_read_processed_1_fake.call_count,
"Expected to be called 1 time, got %d",
target_read_processed_1_fake.call_count);
// Check that we didn't forward to the first target
zexpect_equal(0, target_read_requested_0_fake.call_count,
"Expected to be called 0 times, got %d",
target_read_requested_0_fake.call_count);
zexpect_equal(0, target_read_processed_0_fake.call_count,
"Expected to be called 0 times, got %d",
target_read_processed_0_fake.call_count);
}
} // namespace

View file

@ -0,0 +1,7 @@
# Copyright 2024 Google LLC
# SPDX-License-Identifier: Apache-2.0
tests:
drivers.i2c.emul:
platform_allow:
- native_sim