diff --git a/boards/native/native_sim/native_sim.dts b/boards/native/native_sim/native_sim.dts index 073d5fe21f1..1f1b11ad9e7 100644 --- a/boards/native/native_sim/native_sim.dts +++ b/boards/native/native_sim/native_sim.dts @@ -114,6 +114,7 @@ clock-frequency = ; #address-cells = <1>; #size-cells = <0>; + #forward-cells = <1>; reg = <0x100 4>; }; diff --git a/doc/hardware/emulator/bus_emulators.rst b/doc/hardware/emulator/bus_emulators.rst index 76250b452a8..0886ad12b4b 100644 --- a/doc/hardware/emulator/bus_emulators.rst +++ b/doc/hardware/emulator/bus_emulators.rst @@ -144,6 +144,38 @@ Zephyr includes the following emulators: * MSPI emulator driver, allowing drivers to be connected to an emulator so that tests can be performed without access to the real hardware. +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:: + + i2c0: i2c@100 { + status = "okay"; + compatible = "zephyr,i2c-emul-controller"; + clock-frequency = ; + #address-cells = <1>; + #size-cells = <0>; + #forward-cells = <1>; + reg = <0x100 4>; + forwards = <&i2c1 0x20>; + }; + +The final property, ``forwards`` indicates that any read/write requests sent to +address ``0x20`` should be sent to ``i2c1`` with the same address. This allows +us to test both the controller and the target end of the communication on the +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 + 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 + should remain 1. + Samples ======= @@ -166,6 +198,6 @@ Here are some examples present in Zephyr: :gen-args: -DDTC_OVERLAY_FILE=at2x_emul.overlay -DOVERLAY_CONFIG=at2x_emul.conf API Reference -************* +============= .. doxygengroup:: io_emulators diff --git a/drivers/i2c/i2c_emul.c b/drivers/i2c/i2c_emul.c index 62e0b0d7621..51800c3a442 100644 --- a/drivers/i2c/i2c_emul.c +++ b/drivers/i2c/i2c_emul.c @@ -29,6 +29,15 @@ struct i2c_emul_data { /* I2C host configuration */ uint32_t config; uint32_t bitrate; +#ifdef CONFIG_I2C_TARGET + struct i2c_target_config *target_cfg; +#endif +}; + +struct i2c_emul_config { + struct emul_list_for_bus emul_list; + const struct i2c_dt_spec *forward_list; + uint16_t forward_list_size; }; /** @@ -74,13 +83,104 @@ static int i2c_emul_get_config(const struct device *dev, uint32_t *dev_config) return 0; } +#ifdef CONFIG_I2C_TARGET +static int i2c_emul_send_to_target(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs) +{ + struct i2c_emul_data *data = dev->data; + const struct i2c_target_callbacks *callbacks = data->target_cfg->callbacks; + + 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])) { + for (uint32_t j = 0; j < msgs[i].len; ++j) { + int rc = 0; + + if (j == 0) { + LOG_DBG(" Calling read_requested with data %p", + (void *)&msgs[i].buf[j]); + rc = callbacks->read_requested(data->target_cfg, + &msgs[i].buf[j]); + } else { + LOG_DBG(" Calling read_processed with data %p", + (void *)&msgs[i].buf[j]); + rc = callbacks->read_processed(data->target_cfg, + &msgs[i].buf[j]); + } + if (rc != 0) { + return rc; + } + } + } else { + for (uint32_t j = 0; j < msgs[i].len; ++j) { + int rc = 0; + + if (j == 0) { + LOG_DBG(" Calling write_requested"); + rc = callbacks->write_requested(data->target_cfg); + } + if (rc != 0) { + return rc; + } + LOG_DBG(" Calling write_received with data 0x%02x", + msgs[i].buf[j]); + rc = callbacks->write_received(data->target_cfg, msgs[i].buf[j]); + if (rc != 0) { + return rc; + } + } + } + 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*/ + static int i2c_emul_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs, uint16_t addr) { + const struct i2c_emul_config *conf = dev->config; struct i2c_emul *emul; const struct i2c_emul_api *api; int ret; + LOG_DBG("%s(dev=%p, addr=0x%02x)", __func__, (void *)dev, addr); +#ifdef CONFIG_I2C_TARGET + struct i2c_emul_data *data = dev->data; + + /* + * First check if the bus is configured as a target, targets either listen to the address or + * ignore the messages. So if the address doesn't match, we're just going to bail. + */ + LOG_DBG(" has_target_cfg? %d", data->target_cfg != NULL); + if (data->target_cfg != NULL) { + LOG_DBG(" target_cfg->address? 0x%02x", data->target_cfg->address); + if (data->target_cfg->address != addr) { + return -EINVAL; + } + LOG_DBG(" forwarding to target"); + return i2c_emul_send_to_target(dev, msgs, num_msgs); + } +#endif /* CONFIG_I2C_TARGET */ + + /* + * We're not a target, but lets check if we need to forward this request before we start + * looking for a peripheral. + */ + for (uint16_t i = 0; i < conf->forward_list_size; ++i) { + LOG_DBG(" Checking forward list [%u].addr? 0x%02x", i, + conf->forward_list[i].addr); + if (conf->forward_list[i].addr == addr) { + /* We need to forward this request */ + return i2c_transfer(conf->forward_list[i].bus, msgs, num_msgs, addr); + } + } + emul = i2c_emul_find(dev, addr); if (!emul) { return -EIO; @@ -132,12 +232,38 @@ int i2c_emul_register(const struct device *dev, struct i2c_emul *emul) return 0; } +#ifdef CONFIG_I2C_TARGET +static int i2c_emul_target_register(const struct device *dev, struct i2c_target_config *cfg) +{ + struct i2c_emul_data *data = dev->data; + + data->target_cfg = cfg; + return 0; +} + +static int i2c_emul_target_unregister(const struct device *dev, struct i2c_target_config *cfg) +{ + struct i2c_emul_data *data = dev->data; + + if (data->target_cfg != cfg) { + return -EINVAL; + } + + data->target_cfg = NULL; + return 0; +} +#endif /* CONFIG_I2C_TARGET */ + /* Device instantiation */ static const struct i2c_driver_api i2c_emul_api = { .configure = i2c_emul_configure, .get_config = i2c_emul_get_config, .transfer = i2c_emul_transfer, +#ifdef CONFIG_I2C_TARGET + .target_register = i2c_emul_target_register, + .target_unregister = i2c_emul_target_unregister, +#endif }; #define EMUL_LINK_AND_COMMA(node_id) \ @@ -145,12 +271,26 @@ static const struct i2c_driver_api i2c_emul_api = { .dev = DEVICE_DT_GET(node_id), \ }, +#define EMUL_FORWARD_ITEM(node_id, prop, idx) \ + { \ + .bus = DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), \ + .addr = DT_PHA_BY_IDX(node_id, prop, idx, addr), \ + }, + #define I2C_EMUL_INIT(n) \ static const struct emul_link_for_bus emuls_##n[] = { \ DT_FOREACH_CHILD_STATUS_OKAY(DT_DRV_INST(n), EMUL_LINK_AND_COMMA)}; \ - static struct emul_list_for_bus i2c_emul_cfg_##n = { \ - .children = emuls_##n, \ - .num_children = ARRAY_SIZE(emuls_##n), \ + static const struct i2c_dt_spec emul_forward_list_##n[] = { \ + COND_CODE_1(DT_INST_NODE_HAS_PROP(n, forwards), \ + (DT_INST_FOREACH_PROP_ELEM(n, forwards, EMUL_FORWARD_ITEM)), ())}; \ + static struct i2c_emul_config i2c_emul_cfg_##n = { \ + .emul_list = \ + { \ + .children = emuls_##n, \ + .num_children = ARRAY_SIZE(emuls_##n), \ + }, \ + .forward_list = emul_forward_list_##n, \ + .forward_list_size = ARRAY_SIZE(emul_forward_list_##n), \ }; \ static struct i2c_emul_data i2c_emul_data_##n = { \ .bitrate = DT_INST_PROP(n, clock_frequency), \ diff --git a/dts/bindings/i2c/zephyr,i2c-emul-controller.yaml b/dts/bindings/i2c/zephyr,i2c-emul-controller.yaml index 3352e29750b..45cc67406d8 100644 --- a/dts/bindings/i2c/zephyr,i2c-emul-controller.yaml +++ b/dts/bindings/i2c/zephyr,i2c-emul-controller.yaml @@ -10,3 +10,17 @@ include: i2c-controller.yaml properties: reg: required: true + forwards: + type: phandle-array + description: | + When added, read/write requests sent to this bus for a given address will + be forwarded to the specified phandle (must be another i2c bus). As an + example, if we wanted to forward any requests from i2c0@0x20 to i2c1, we + would use: + + &i2c0 { + forward = <&i2c1 0x20>; + }; + +forward-cells: + - addr diff --git a/tests/drivers/i2c/i2c_emul/CMakeLists.txt b/tests/drivers/i2c/i2c_emul/CMakeLists.txt new file mode 100644 index 00000000000..52553744406 --- /dev/null +++ b/tests/drivers/i2c/i2c_emul/CMakeLists.txt @@ -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) diff --git a/tests/drivers/i2c/i2c_emul/boards/native_sim.overlay b/tests/drivers/i2c/i2c_emul/boards/native_sim.overlay new file mode 100644 index 00000000000..e17f418b676 --- /dev/null +++ b/tests/drivers/i2c/i2c_emul/boards/native_sim.overlay @@ -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 = ; + #address-cells = <1>; + #size-cells = <0>; + #forward-cells = <1>; + reg = <0x400 4>; + }; + + i2c2: i2c@500 { + status = "okay"; + compatible = "zephyr,i2c-emul-controller"; + clock-frequency = ; + #address-cells = <1>; + #size-cells = <0>; + #forward-cells = <1>; + reg = <0x500 4>; + }; +}; + +&i2c0 { + forwards = <&i2c1 0x20>, <&i2c2 0x24>; +}; diff --git a/tests/drivers/i2c/i2c_emul/include/emulated_target.hpp b/tests/drivers/i2c/i2c_emul/include/emulated_target.hpp new file mode 100644 index 00000000000..f76b9b325e7 --- /dev/null +++ b/tests/drivers/i2c/i2c_emul/include/emulated_target.hpp @@ -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 + +#define CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN, FUNCNAME, ...) \ + std::function FUNCNAME + +#include +#include +#include + +#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 */ diff --git a/tests/drivers/i2c/i2c_emul/prj.conf b/tests/drivers/i2c/i2c_emul/prj.conf new file mode 100644 index 00000000000..9c8d11709b5 --- /dev/null +++ b/tests/drivers/i2c/i2c_emul/prj.conf @@ -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 diff --git a/tests/drivers/i2c/i2c_emul/src/emulated_target.cpp b/tests/drivers/i2c/i2c_emul/src/emulated_target.cpp new file mode 100644 index 00000000000..a99cab96bb0 --- /dev/null +++ b/tests/drivers/i2c/i2c_emul/src/emulated_target.cpp @@ -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)}; diff --git a/tests/drivers/i2c/i2c_emul/src/test_forwarding.cpp b/tests/drivers/i2c/i2c_emul/src/test_forwarding.cpp new file mode 100644 index 00000000000..4c0286d2f3d --- /dev/null +++ b/tests/drivers/i2c/i2c_emul/src/test_forwarding.cpp @@ -0,0 +1,274 @@ +/* + * Copyright 2024 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ +#include "emulated_target.hpp" + +#include +#include +#include +#include + +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(0x1 * i), + static_cast(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 diff --git a/tests/drivers/i2c/i2c_emul/testcase.yaml b/tests/drivers/i2c/i2c_emul/testcase.yaml new file mode 100644 index 00000000000..b044a67e73f --- /dev/null +++ b/tests/drivers/i2c/i2c_emul/testcase.yaml @@ -0,0 +1,7 @@ +# Copyright 2024 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +tests: + drivers.i2c.emul: + platform_allow: + - native_sim