i2c_emul: Add support for CONFIG_I2C_TARGET_BUFFER_MODE

Add emulation and test to support the buffered target mode.

Signed-off-by: Yuval Peress <peress@google.com>
This commit is contained in:
Yuval Peress 2024-07-16 10:43:18 -06:00 committed by Anas Nashif
commit 690134356c
11 changed files with 334 additions and 38 deletions

View file

@ -150,7 +150,7 @@ 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::
.. code-block:: devicetree
i2c0: i2c@100 {
status = "okay";
@ -170,7 +170,7 @@ 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
``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

View file

@ -36,6 +36,7 @@ struct i2c_emul_data {
struct i2c_emul_config {
struct emul_list_for_bus emul_list;
bool target_buffered_mode;
const struct i2c_dt_spec *forward_list;
uint16_t forward_list_size;
};
@ -89,6 +90,41 @@ static int i2c_emul_send_to_target(const struct device *dev, struct i2c_msg *msg
struct i2c_emul_data *data = dev->data;
const struct i2c_target_callbacks *callbacks = data->target_cfg->callbacks;
#ifdef CONFIG_I2C_TARGET_BUFFER_MODE
const struct i2c_emul_config *config = dev->config;
if (config->target_buffered_mode) {
for (uint8_t i = 0; i < num_msgs; ++i) {
if (i2c_is_read_op(&msgs[i])) {
uint8_t *ptr = NULL;
uint32_t len;
int rc =
callbacks->buf_read_requested(data->target_cfg, &ptr, &len);
if (rc != 0) {
return rc;
}
if (len > msgs[i].len) {
LOG_ERR("buf_read_requested returned too many bytes");
return -ENOMEM;
}
memcpy(msgs[i].buf, ptr, len);
} else {
callbacks->buf_write_received(data->target_cfg, msgs[i].buf,
msgs[i].len);
}
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_BUFFER_MODE */
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])) {
@ -289,6 +325,7 @@ static const struct i2c_driver_api i2c_emul_api = {
.children = emuls_##n, \
.num_children = ARRAY_SIZE(emuls_##n), \
}, \
.target_buffered_mode = DT_INST_PROP(n, target_buffered_mode), \
.forward_list = emul_forward_list_##n, \
.forward_list_size = ARRAY_SIZE(emul_forward_list_##n), \
}; \

View file

@ -10,6 +10,12 @@ include: i2c-controller.yaml
properties:
reg:
required: true
target-buffered-mode:
type: boolean
description: |
This option is used when the I2C target is enabled and it can support
buffered mode for I2C target transfer. When 'false', the target will use
PIO (Programmed I/O) mode.
forwards:
type: phandle-array
description: |

View file

@ -7,6 +7,11 @@ project(i2c_emul)
target_sources(app PRIVATE
src/emulated_target.cpp
src/test_forwarding.cpp
src/test_fowarding_common.cpp
)
if(CONFIG_I2C_TARGET_BUFFER_MODE)
target_sources(app PRIVATE src/test_forwarding_buf.cpp)
else()
target_sources(app PRIVATE src/test_forwarding_pio.cpp)
endif()
target_include_directories(app PRIVATE include)

View file

@ -0,0 +1,8 @@
/*
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
&i2c1 {
target-buffered-mode;
};

View file

@ -31,7 +31,11 @@ extern struct i2c_target_config emulated_target_config[FORWARD_COUNT];
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 *);
DECLARE_FAKE_VALUE_FUNC(int, target_stop_##n, struct i2c_target_config *); \
DECLARE_FAKE_VALUE_FUNC(int, target_buf_read_requested_##n, struct i2c_target_config *, \
uint8_t **, uint32_t *) \
DECLARE_FAKE_VOID_FUNC(target_buf_write_received_##n, struct i2c_target_config *, \
uint8_t *, uint32_t)
DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, DECLARE_FAKE_TARGET_FUNCTIONS)
@ -42,6 +46,8 @@ DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, DECLARE_FAKE_TARGET_FUNCTIONS)
fn(target_write_requested_##n); \
fn(target_write_received_##n); \
fn(target_stop_##n); \
fn(target_buf_read_requested_##n); \
fn(target_buf_write_received_##n); \
} while (0);
#define FFF_FAKES_LIST_FOREACH(fn) \

View file

@ -15,18 +15,28 @@ DEFINE_FFF_GLOBALS;
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 *);
DEFINE_FAKE_VALUE_FUNC(int, target_stop_##n, struct i2c_target_config *); \
DEFINE_FAKE_VALUE_FUNC(int, target_buf_read_requested_##n, struct i2c_target_config *, \
uint8_t **, uint32_t *) \
DEFINE_FAKE_VOID_FUNC(target_buf_write_received_##n, struct i2c_target_config *, \
uint8_t *, uint32_t)
DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, DEFINE_FAKE_TARGET_FUNCTION);
/* clang-format off */
#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, \
COND_CODE_1(CONFIG_I2C_TARGET_BUFFER_MODE, \
(.buf_write_received = target_buf_write_received_##n, ), ()) \
COND_CODE_1(CONFIG_I2C_TARGET_BUFFER_MODE, \
(.buf_read_requested = target_buf_read_requested_##n, ), ()) \
.stop = target_stop_##n, \
},
/* clang-format on */
struct i2c_target_callbacks emulated_callbacks[FORWARD_COUNT] = {
DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, DEFINE_EMULATED_CALLBACK)};

View file

@ -0,0 +1,155 @@
/*
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
#include "emulated_target.hpp"
#include <cstdint>
#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)};
ZTEST(i2c_emul_forwarding, test_write_is_forwarded)
{
uint8_t data[] = {0x00, 0x01, 0x02};
target_buf_write_received_0_fake.custom_fake = [&data](struct i2c_target_config *,
uint8_t *buf, uint32_t len) {
zassert_equal(ARRAY_SIZE(data), len);
zexpect_mem_equal(data, buf, len);
};
zassert_ok(
i2c_write(controller, data, ARRAY_SIZE(data), emulated_target_config[0].address));
// Expect 0 reads and 1 write/stop to be made
zexpect_equal(0, target_buf_read_requested_0_fake.call_count);
zexpect_equal(1, target_buf_write_received_0_fake.call_count);
zexpect_equal(1, target_stop_0_fake.call_count);
}
ZTEST(i2c_emul_forwarding, test_read_is_forwarded)
{
uint8_t expected[] = {0x01, 0x02, 0x03};
uint8_t data[ARRAY_SIZE(expected)] = {};
/* Set the custom fake function to a lambda which captures the expected value as a reference.
* This means that when the function is executed, we can access 'expected' as though it were
* within the lambda's scope.
*/
target_buf_read_requested_0_fake.custom_fake = [&expected](struct i2c_target_config *,
uint8_t **ptr, uint32_t *len) {
*ptr = expected;
*len = ARRAY_SIZE(expected);
return 0;
};
zassert_ok(i2c_read(controller, data, ARRAY_SIZE(expected),
emulated_target_config[0].address));
// Expect 1 read/stop and 0 write to be made
zexpect_equal(1, target_buf_read_requested_0_fake.call_count);
zexpect_equal(0, target_buf_write_received_0_fake.call_count);
zexpect_equal(1, target_stop_0_fake.call_count);
zexpect_mem_equal(expected, data, ARRAY_SIZE(expected));
}
ZTEST(i2c_emul_forwarding, test_failed_read_request)
{
uint8_t data;
target_buf_read_requested_0_fake.return_val = -EINTR;
zassert_equal(-EINTR, i2c_read(controller, &data, 1, emulated_target_config[0].address));
zexpect_equal(1, target_buf_read_requested_0_fake.call_count);
zexpect_equal(0, target_buf_write_received_0_fake.call_count);
zexpect_equal(0, target_stop_0_fake.call_count);
}
ZTEST(i2c_emul_forwarding, test_read_request_overflow)
{
uint8_t data;
/* Set the custom_fake to a local lambda with no capture values. */
target_buf_read_requested_0_fake.custom_fake = [](struct i2c_target_config *, uint8_t **_,
uint32_t *len) {
*len = UINT32_MAX;
return 0;
};
zassert_equal(-ENOMEM, i2c_read(controller, &data, 1, emulated_target_config[0].address));
zexpect_equal(1, target_buf_read_requested_0_fake.call_count);
zexpect_equal(0, target_buf_write_received_0_fake.call_count);
zexpect_equal(0, target_stop_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_buf_write_received_0_fake.custom_fake = [&phase](struct i2c_target_config *,
uint8_t *, uint32_t) {
zassert_equal(0, phase,
"Expected a call to buf_write_received before anything else");
phase++;
};
target_buf_read_requested_0_fake.custom_fake = [&phase](struct i2c_target_config *,
uint8_t **ptr, uint32_t *len) {
zassert_equal(1, phase, "Expected a call to buf_Read_requested as the second step");
phase++;
// Write a random byte. It doesn't make a difference.
*ptr = (uint8_t *)&phase;
*len = 1;
return 0;
};
target_stop_0_fake.custom_fake = [&phase](struct i2c_target_config *) -> int {
zassert_equal(2, phase, "Expected a call to stop as the 3rd step");
phase++;
return 0;
};
zassert_ok(i2c_transfer(controller, msgs, ARRAY_SIZE(msgs),
emulated_target_config[0].address));
zexpect_equal(1, target_buf_write_received_0_fake.call_count);
zexpect_equal(1, target_buf_read_requested_0_fake.call_count);
zexpect_equal(1, target_stop_0_fake.call_count);
zexpect_equal(3, phase, "Expected a total of 3 phases, but got %d", phase);
}
ZTEST(i2c_emul_forwarding, test_call_pio_forwarded_bus_when_buffering_enabled)
{
uint8_t data[2];
zassert_ok(i2c_read(controller, data, ARRAY_SIZE(data), emulated_target_config[1].address));
zexpect_equal(1, target_read_requested_1_fake.call_count);
zexpect_equal(1, target_read_processed_1_fake.call_count);
zexpect_equal(1, target_stop_1_fake.call_count);
}
} // namespace

View file

@ -19,38 +19,6 @@ 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
@ -271,4 +239,16 @@ ZTEST(i2c_emul_forwarding, test_forward_two_targets)
"Expected to be called 0 times, got %d",
target_read_processed_0_fake.call_count);
}
ZTEST(i2c_emul_forwarding, test_error_in_write_received)
{
uint8_t data;
target_write_received_0_fake.return_val = -EINTR;
zassert_equal(-EINTR, i2c_write(controller, &data, 1, emulated_target_config[0].address));
zexpect_equal(1, target_write_requested_0_fake.call_count);
zexpect_equal(1, target_write_received_0_fake.call_count);
zexpect_equal(0, target_stop_0_fake.call_count);
}
} // namespace

View file

@ -0,0 +1,79 @@
/*
* 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);
/* Common tests */
ZTEST(i2c_emul_forwarding, test_invalid_address_for_target)
{
uint8_t data;
int rc = i2c_write(targets[0], &data, 1, emulated_target_config[0].address + 1);
zassert_equal(-EINVAL, rc, "Expected %d (-EINVAL), but got %d", -EINVAL, rc);
zexpect_equal(0, target_read_requested_0_fake.call_count);
zexpect_equal(0, target_read_processed_0_fake.call_count);
zexpect_equal(0, target_write_requested_0_fake.call_count);
zexpect_equal(0, target_write_received_0_fake.call_count);
zexpect_equal(0, target_buf_write_received_0_fake.call_count);
zexpect_equal(0, target_buf_read_requested_0_fake.call_count);
zexpect_equal(0, target_stop_0_fake.call_count);
}
ZTEST(i2c_emul_forwarding, test_error_in_stop)
{
uint8_t data;
target_stop_0_fake.return_val = -EINTR;
zassert_equal(-EINTR, i2c_write(controller, &data, 1, emulated_target_config[0].address));
zexpect_equal(1, target_stop_0_fake.call_count);
}
} // namespace

View file

@ -2,6 +2,16 @@
# SPDX-License-Identifier: Apache-2.0
tests:
drivers.i2c.emul:
drivers.i2c.emul.target_pio:
platform_allow:
- native_sim
extra_configs:
- CONFIG_I2C_TARGET_BUFFER_MODE=n
drivers.i2c.emul.target_buf:
platform_allow:
- native_sim
extra_configs:
- CONFIG_I2C_TARGET_BUFFER_MODE=y
extra_dtc_overlay_files:
- "boards/native_sim.overlay"
- "boards/native_sim.buf.overlay"