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 In the binding of the I2C emulated bus, there's a custom property for address
based forwarding. Given the following devicetree node: based forwarding. Given the following devicetree node:
.. code-block:: .. code-block:: devicetree
i2c0: i2c@100 { i2c0: i2c@100 {
status = "okay"; status = "okay";
@ -170,7 +170,7 @@ same image.
.. note:: .. note::
The ``#forward-cells`` attribute should always be 1. Each entry in the 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 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 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 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 i2c_emul_config {
struct emul_list_for_bus emul_list; struct emul_list_for_bus emul_list;
bool target_buffered_mode;
const struct i2c_dt_spec *forward_list; const struct i2c_dt_spec *forward_list;
uint16_t forward_list_size; 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; struct i2c_emul_data *data = dev->data;
const struct i2c_target_callbacks *callbacks = data->target_cfg->callbacks; 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) { for (uint8_t i = 0; i < num_msgs; ++i) {
LOG_DBG(" msgs[%u].flags? 0x%02x", i, msgs[i].flags); LOG_DBG(" msgs[%u].flags? 0x%02x", i, msgs[i].flags);
if (i2c_is_read_op(&msgs[i])) { if (i2c_is_read_op(&msgs[i])) {
@ -289,6 +325,7 @@ static const struct i2c_driver_api i2c_emul_api = {
.children = emuls_##n, \ .children = emuls_##n, \
.num_children = ARRAY_SIZE(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 = emul_forward_list_##n, \
.forward_list_size = ARRAY_SIZE(emul_forward_list_##n), \ .forward_list_size = ARRAY_SIZE(emul_forward_list_##n), \
}; \ }; \

View file

@ -10,6 +10,12 @@ include: i2c-controller.yaml
properties: properties:
reg: reg:
required: true 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: forwards:
type: phandle-array type: phandle-array
description: | description: |

View file

@ -7,6 +7,11 @@ project(i2c_emul)
target_sources(app PRIVATE target_sources(app PRIVATE
src/emulated_target.cpp 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) 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_requested_##n, struct i2c_target_config *); \
DECLARE_FAKE_VALUE_FUNC(int, target_write_received_##n, struct i2c_target_config *, \ DECLARE_FAKE_VALUE_FUNC(int, target_write_received_##n, struct i2c_target_config *, \
uint8_t); \ 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) 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_requested_##n); \
fn(target_write_received_##n); \ fn(target_write_received_##n); \
fn(target_stop_##n); \ fn(target_stop_##n); \
fn(target_buf_read_requested_##n); \
fn(target_buf_write_received_##n); \
} while (0); } while (0);
#define FFF_FAKES_LIST_FOREACH(fn) \ #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_requested_##n, struct i2c_target_config *); \
DEFINE_FAKE_VALUE_FUNC(int, target_write_received_##n, struct i2c_target_config *, \ DEFINE_FAKE_VALUE_FUNC(int, target_write_received_##n, struct i2c_target_config *, \
uint8_t); \ 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); DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, DEFINE_FAKE_TARGET_FUNCTION);
/* clang-format off */
#define DEFINE_EMULATED_CALLBACK(node_id, prop, n) \ #define DEFINE_EMULATED_CALLBACK(node_id, prop, n) \
[n] = { \ [n] = { \
.write_requested = target_write_requested_##n, \ .write_requested = target_write_requested_##n, \
.read_requested = target_read_requested_##n, \ .read_requested = target_read_requested_##n, \
.write_received = target_write_received_##n, \ .write_received = target_write_received_##n, \
.read_processed = target_read_processed_##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, \ .stop = target_stop_##n, \
}, },
/* clang-format on */
struct i2c_target_callbacks emulated_callbacks[FORWARD_COUNT] = { struct i2c_target_callbacks emulated_callbacks[FORWARD_COUNT] = {
DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, DEFINE_EMULATED_CALLBACK)}; 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] = { constexpr const struct device *targets[FORWARD_COUNT] = {
DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, GET_TARGET_DEVICE)}; 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) ZTEST(i2c_emul_forwarding, test_write_is_forwarded)
{ {
// Try writing some values // Try writing some values
@ -271,4 +239,16 @@ ZTEST(i2c_emul_forwarding, test_forward_two_targets)
"Expected to be called 0 times, got %d", "Expected to be called 0 times, got %d",
target_read_processed_0_fake.call_count); 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 } // 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 # SPDX-License-Identifier: Apache-2.0
tests: tests:
drivers.i2c.emul: drivers.i2c.emul.target_pio:
platform_allow: platform_allow:
- native_sim - 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"