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:
parent
c68d67aefb
commit
c394b2e6f8
11 changed files with 617 additions and 4 deletions
12
tests/drivers/i2c/i2c_emul/CMakeLists.txt
Normal file
12
tests/drivers/i2c/i2c_emul/CMakeLists.txt
Normal 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)
|
30
tests/drivers/i2c/i2c_emul/boards/native_sim.overlay
Normal file
30
tests/drivers/i2c/i2c_emul/boards/native_sim.overlay
Normal 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>;
|
||||
};
|
50
tests/drivers/i2c/i2c_emul/include/emulated_target.hpp
Normal file
50
tests/drivers/i2c/i2c_emul/include/emulated_target.hpp
Normal 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 */
|
11
tests/drivers/i2c/i2c_emul/prj.conf
Normal file
11
tests/drivers/i2c/i2c_emul/prj.conf
Normal 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
|
42
tests/drivers/i2c/i2c_emul/src/emulated_target.cpp
Normal file
42
tests/drivers/i2c/i2c_emul/src/emulated_target.cpp
Normal 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)};
|
274
tests/drivers/i2c/i2c_emul/src/test_forwarding.cpp
Normal file
274
tests/drivers/i2c/i2c_emul/src/test_forwarding.cpp
Normal 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
|
7
tests/drivers/i2c/i2c_emul/testcase.yaml
Normal file
7
tests/drivers/i2c/i2c_emul/testcase.yaml
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Copyright 2024 Google LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
tests:
|
||||
drivers.i2c.emul:
|
||||
platform_allow:
|
||||
- native_sim
|
Loading…
Add table
Add a link
Reference in a new issue