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

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