Skip to content

Commit

Permalink
i2c_emul: Add support for CONFIG_I2C_TARGET_BUFFER_MODE
Browse files Browse the repository at this point in the history
Add emulation and test to support the buffered target mode.

(cherry picked from commit 6901343)

Cq-Depend: chromium:5798623
Original-Signed-off-by: Yuval Peress <peress@google.com>
GitOrigin-RevId: 6901343
Cr-Build-Id: 8739635188800557281
Cr-Build-Url: https://cr-buildbucket.appspot.com/build/8739635188800557281
Copybot-Job-Name: zephyr-main-copybot-downstream
Change-Id: I9a1b92f5dd8fbe299768d163292a7f084bcc09ed
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/zephyr/+/5779569
Tested-by: Eric Yilun Lin <yllin@google.com>
Reviewed-by: Dawid Niedźwiecki <dawidn@google.com>
Commit-Queue: Eric Yilun Lin <yllin@google.com>
  • Loading branch information
yperess authored and Chromeos LUCI committed Aug 20, 2024
1 parent ab75ed4 commit d5f5318
Show file tree
Hide file tree
Showing 11 changed files with 334 additions and 38 deletions.
4 changes: 2 additions & 2 deletions doc/hardware/emulator/bus_emulators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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
Expand Down
37 changes: 37 additions & 0 deletions drivers/i2c/i2c_emul.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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])) {
Expand Down Expand Up @@ -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), \
}; \
Expand Down
6 changes: 6 additions & 0 deletions dts/bindings/i2c/zephyr,i2c-emul-controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
7 changes: 6 additions & 1 deletion tests/drivers/i2c/i2c_emul/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
8 changes: 8 additions & 0 deletions tests/drivers/i2c/i2c_emul/boards/native_sim.buf.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

&i2c1 {
target-buffered-mode;
};
8 changes: 7 additions & 1 deletion tests/drivers/i2c/i2c_emul/include/emulated_target.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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) \
Expand Down
12 changes: 11 additions & 1 deletion tests/drivers/i2c/i2c_emul/src/emulated_target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)};
Expand Down
155 changes: 155 additions & 0 deletions tests/drivers/i2c/i2c_emul/src/test_forwarding_buf.cpp
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Loading

0 comments on commit d5f5318

Please sign in to comment.