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>;
#address-cells = <1>;
#size-cells = <0>;
#forward-cells = <1>;
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
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
=======
@ -166,6 +198,6 @@ Here are some examples present in Zephyr:
:gen-args: -DDTC_OVERLAY_FILE=at2x_emul.overlay -DOVERLAY_CONFIG=at2x_emul.conf
API Reference
*************
=============
.. doxygengroup:: io_emulators

View file

@ -29,6 +29,15 @@ struct i2c_emul_data {
/* I2C host configuration */
uint32_t config;
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;
}
#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,
uint16_t addr)
{
const struct i2c_emul_config *conf = dev->config;
struct i2c_emul *emul;
const struct i2c_emul_api *api;
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);
if (!emul) {
return -EIO;
@ -132,12 +232,38 @@ int i2c_emul_register(const struct device *dev, struct i2c_emul *emul)
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 */
static const struct i2c_driver_api i2c_emul_api = {
.configure = i2c_emul_configure,
.get_config = i2c_emul_get_config,
.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) \
@ -145,12 +271,26 @@ static const struct i2c_driver_api i2c_emul_api = {
.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) \
static const struct emul_link_for_bus emuls_##n[] = { \
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[] = { \
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, forwards), \
(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 = { \
.bitrate = DT_INST_PROP(n, clock_frequency), \

View file

@ -10,3 +10,17 @@ include: i2c-controller.yaml
properties:
reg:
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