Skip to content

Commit c394b2e

Browse files
yperessnashif
authored andcommitted
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>
1 parent c68d67a commit c394b2e

File tree

11 files changed

+617
-4
lines changed

11 files changed

+617
-4
lines changed

boards/native/native_sim/native_sim.dts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
clock-frequency = <I2C_BITRATE_STANDARD>;
115115
#address-cells = <1>;
116116
#size-cells = <0>;
117+
#forward-cells = <1>;
117118
reg = <0x100 4>;
118119
};
119120

doc/hardware/emulator/bus_emulators.rst

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,38 @@ Zephyr includes the following emulators:
144144
* MSPI emulator driver, allowing drivers to be connected to an emulator so that
145145
tests can be performed without access to the real hardware.
146146

147+
I2C Emulation features
148+
----------------------
149+
150+
In the binding of the I2C emulated bus, there's a custom property for address
151+
based forwarding. Given the following devicetree node:
152+
153+
.. code-block::
154+
155+
i2c0: i2c@100 {
156+
status = "okay";
157+
compatible = "zephyr,i2c-emul-controller";
158+
clock-frequency = <I2C_BITRATE_STANDARD>;
159+
#address-cells = <1>;
160+
#size-cells = <0>;
161+
#forward-cells = <1>;
162+
reg = <0x100 4>;
163+
forwards = <&i2c1 0x20>;
164+
};
165+
166+
The final property, ``forwards`` indicates that any read/write requests sent to
167+
address ``0x20`` should be sent to ``i2c1`` with the same address. This allows
168+
us to test both the controller and the target end of the communication on the
169+
same image.
170+
171+
.. note::
172+
The ``#forward-cells`` attribute should always be 1. Each entry in the
173+
``fowards`` attribute consists of the phandle followed by the address. In
174+
the example above, ``<&i2c1 0x20>`` will forward all read/write operations
175+
made to ``i2c0`` at port ``0x20`` to ``i2c1`` on the same port. Since no
176+
additional cells are used by the emulated controller, the number of cells
177+
should remain 1.
178+
147179
Samples
148180
=======
149181

@@ -166,6 +198,6 @@ Here are some examples present in Zephyr:
166198
:gen-args: -DDTC_OVERLAY_FILE=at2x_emul.overlay -DOVERLAY_CONFIG=at2x_emul.conf
167199

168200
API Reference
169-
*************
201+
=============
170202

171203
.. doxygengroup:: io_emulators

drivers/i2c/i2c_emul.c

Lines changed: 143 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ struct i2c_emul_data {
2929
/* I2C host configuration */
3030
uint32_t config;
3131
uint32_t bitrate;
32+
#ifdef CONFIG_I2C_TARGET
33+
struct i2c_target_config *target_cfg;
34+
#endif
35+
};
36+
37+
struct i2c_emul_config {
38+
struct emul_list_for_bus emul_list;
39+
const struct i2c_dt_spec *forward_list;
40+
uint16_t forward_list_size;
3241
};
3342

3443
/**
@@ -74,13 +83,104 @@ static int i2c_emul_get_config(const struct device *dev, uint32_t *dev_config)
7483
return 0;
7584
}
7685

86+
#ifdef CONFIG_I2C_TARGET
87+
static int i2c_emul_send_to_target(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs)
88+
{
89+
struct i2c_emul_data *data = dev->data;
90+
const struct i2c_target_callbacks *callbacks = data->target_cfg->callbacks;
91+
92+
for (uint8_t i = 0; i < num_msgs; ++i) {
93+
LOG_DBG(" msgs[%u].flags? 0x%02x", i, msgs[i].flags);
94+
if (i2c_is_read_op(&msgs[i])) {
95+
for (uint32_t j = 0; j < msgs[i].len; ++j) {
96+
int rc = 0;
97+
98+
if (j == 0) {
99+
LOG_DBG(" Calling read_requested with data %p",
100+
(void *)&msgs[i].buf[j]);
101+
rc = callbacks->read_requested(data->target_cfg,
102+
&msgs[i].buf[j]);
103+
} else {
104+
LOG_DBG(" Calling read_processed with data %p",
105+
(void *)&msgs[i].buf[j]);
106+
rc = callbacks->read_processed(data->target_cfg,
107+
&msgs[i].buf[j]);
108+
}
109+
if (rc != 0) {
110+
return rc;
111+
}
112+
}
113+
} else {
114+
for (uint32_t j = 0; j < msgs[i].len; ++j) {
115+
int rc = 0;
116+
117+
if (j == 0) {
118+
LOG_DBG(" Calling write_requested");
119+
rc = callbacks->write_requested(data->target_cfg);
120+
}
121+
if (rc != 0) {
122+
return rc;
123+
}
124+
LOG_DBG(" Calling write_received with data 0x%02x",
125+
msgs[i].buf[j]);
126+
rc = callbacks->write_received(data->target_cfg, msgs[i].buf[j]);
127+
if (rc != 0) {
128+
return rc;
129+
}
130+
}
131+
}
132+
if (i2c_is_stop_op(&msgs[i])) {
133+
int rc = callbacks->stop(data->target_cfg);
134+
135+
if (rc != 0) {
136+
return rc;
137+
}
138+
}
139+
}
140+
return 0;
141+
}
142+
#endif /* CONFIG_I2C_TARGET*/
143+
77144
static int i2c_emul_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs,
78145
uint16_t addr)
79146
{
147+
const struct i2c_emul_config *conf = dev->config;
80148
struct i2c_emul *emul;
81149
const struct i2c_emul_api *api;
82150
int ret;
83151

152+
LOG_DBG("%s(dev=%p, addr=0x%02x)", __func__, (void *)dev, addr);
153+
#ifdef CONFIG_I2C_TARGET
154+
struct i2c_emul_data *data = dev->data;
155+
156+
/*
157+
* First check if the bus is configured as a target, targets either listen to the address or
158+
* ignore the messages. So if the address doesn't match, we're just going to bail.
159+
*/
160+
LOG_DBG(" has_target_cfg? %d", data->target_cfg != NULL);
161+
if (data->target_cfg != NULL) {
162+
LOG_DBG(" target_cfg->address? 0x%02x", data->target_cfg->address);
163+
if (data->target_cfg->address != addr) {
164+
return -EINVAL;
165+
}
166+
LOG_DBG(" forwarding to target");
167+
return i2c_emul_send_to_target(dev, msgs, num_msgs);
168+
}
169+
#endif /* CONFIG_I2C_TARGET */
170+
171+
/*
172+
* We're not a target, but lets check if we need to forward this request before we start
173+
* looking for a peripheral.
174+
*/
175+
for (uint16_t i = 0; i < conf->forward_list_size; ++i) {
176+
LOG_DBG(" Checking forward list [%u].addr? 0x%02x", i,
177+
conf->forward_list[i].addr);
178+
if (conf->forward_list[i].addr == addr) {
179+
/* We need to forward this request */
180+
return i2c_transfer(conf->forward_list[i].bus, msgs, num_msgs, addr);
181+
}
182+
}
183+
84184
emul = i2c_emul_find(dev, addr);
85185
if (!emul) {
86186
return -EIO;
@@ -132,25 +232,65 @@ int i2c_emul_register(const struct device *dev, struct i2c_emul *emul)
132232
return 0;
133233
}
134234

235+
#ifdef CONFIG_I2C_TARGET
236+
static int i2c_emul_target_register(const struct device *dev, struct i2c_target_config *cfg)
237+
{
238+
struct i2c_emul_data *data = dev->data;
239+
240+
data->target_cfg = cfg;
241+
return 0;
242+
}
243+
244+
static int i2c_emul_target_unregister(const struct device *dev, struct i2c_target_config *cfg)
245+
{
246+
struct i2c_emul_data *data = dev->data;
247+
248+
if (data->target_cfg != cfg) {
249+
return -EINVAL;
250+
}
251+
252+
data->target_cfg = NULL;
253+
return 0;
254+
}
255+
#endif /* CONFIG_I2C_TARGET */
256+
135257
/* Device instantiation */
136258

137259
static const struct i2c_driver_api i2c_emul_api = {
138260
.configure = i2c_emul_configure,
139261
.get_config = i2c_emul_get_config,
140262
.transfer = i2c_emul_transfer,
263+
#ifdef CONFIG_I2C_TARGET
264+
.target_register = i2c_emul_target_register,
265+
.target_unregister = i2c_emul_target_unregister,
266+
#endif
141267
};
142268

143269
#define EMUL_LINK_AND_COMMA(node_id) \
144270
{ \
145271
.dev = DEVICE_DT_GET(node_id), \
146272
},
147273

274+
#define EMUL_FORWARD_ITEM(node_id, prop, idx) \
275+
{ \
276+
.bus = DEVICE_DT_GET(DT_PHANDLE_BY_IDX(node_id, prop, idx)), \
277+
.addr = DT_PHA_BY_IDX(node_id, prop, idx, addr), \
278+
},
279+
148280
#define I2C_EMUL_INIT(n) \
149281
static const struct emul_link_for_bus emuls_##n[] = { \
150282
DT_FOREACH_CHILD_STATUS_OKAY(DT_DRV_INST(n), EMUL_LINK_AND_COMMA)}; \
151-
static struct emul_list_for_bus i2c_emul_cfg_##n = { \
152-
.children = emuls_##n, \
153-
.num_children = ARRAY_SIZE(emuls_##n), \
283+
static const struct i2c_dt_spec emul_forward_list_##n[] = { \
284+
COND_CODE_1(DT_INST_NODE_HAS_PROP(n, forwards), \
285+
(DT_INST_FOREACH_PROP_ELEM(n, forwards, EMUL_FORWARD_ITEM)), ())}; \
286+
static struct i2c_emul_config i2c_emul_cfg_##n = { \
287+
.emul_list = \
288+
{ \
289+
.children = emuls_##n, \
290+
.num_children = ARRAY_SIZE(emuls_##n), \
291+
}, \
292+
.forward_list = emul_forward_list_##n, \
293+
.forward_list_size = ARRAY_SIZE(emul_forward_list_##n), \
154294
}; \
155295
static struct i2c_emul_data i2c_emul_data_##n = { \
156296
.bitrate = DT_INST_PROP(n, clock_frequency), \

dts/bindings/i2c/zephyr,i2c-emul-controller.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,17 @@ include: i2c-controller.yaml
1010
properties:
1111
reg:
1212
required: true
13+
forwards:
14+
type: phandle-array
15+
description: |
16+
When added, read/write requests sent to this bus for a given address will
17+
be forwarded to the specified phandle (must be another i2c bus). As an
18+
example, if we wanted to forward any requests from i2c0@0x20 to i2c1, we
19+
would use:
20+
21+
&i2c0 {
22+
forward = <&i2c1 0x20>;
23+
};
24+
25+
forward-cells:
26+
- addr
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright (c) 2024 Google LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
cmake_minimum_required(VERSION 3.20.0)
5+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
6+
project(i2c_emul)
7+
8+
target_sources(app PRIVATE
9+
src/emulated_target.cpp
10+
src/test_forwarding.cpp
11+
)
12+
target_include_directories(app PRIVATE include)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/ {
7+
i2c1: i2c@400 {
8+
status = "okay";
9+
compatible = "zephyr,i2c-emul-controller";
10+
clock-frequency = <I2C_BITRATE_STANDARD>;
11+
#address-cells = <1>;
12+
#size-cells = <0>;
13+
#forward-cells = <1>;
14+
reg = <0x400 4>;
15+
};
16+
17+
i2c2: i2c@500 {
18+
status = "okay";
19+
compatible = "zephyr,i2c-emul-controller";
20+
clock-frequency = <I2C_BITRATE_STANDARD>;
21+
#address-cells = <1>;
22+
#size-cells = <0>;
23+
#forward-cells = <1>;
24+
reg = <0x500 4>;
25+
};
26+
};
27+
28+
&i2c0 {
29+
forwards = <&i2c1 0x20>, <&i2c2 0x24>;
30+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#ifndef _TESTS_DRIVERS_I2C_I2C_EMUL_INCLUDE_EMULATED_TARGET_H
7+
#define _TESTS_DRIVERS_I2C_I2C_EMUL_INCLUDE_EMULATED_TARGET_H
8+
9+
#include <functional>
10+
11+
#define CUSTOM_FFF_FUNCTION_TEMPLATE(RETURN, FUNCNAME, ...) \
12+
std::function<RETURN(__VA_ARGS__)> FUNCNAME
13+
14+
#include <zephyr/devicetree.h>
15+
#include <zephyr/drivers/i2c.h>
16+
#include <zephyr/fff.h>
17+
18+
#define CONTROLLER_LABEL DT_NODELABEL(i2c0)
19+
#define TARGET_LABEL(n) DT_NODELABEL(DT_CAT(i2c, n))
20+
#define FORWARD_COUNT DT_PROP_LEN(CONTROLLER_LABEL, forwards)
21+
22+
extern struct i2c_target_callbacks emulated_callbacks[FORWARD_COUNT];
23+
extern struct i2c_target_config emulated_target_config[FORWARD_COUNT];
24+
25+
/* Declare all the fake functions needed */
26+
#define DECLARE_FAKE_TARGET_FUNCTIONS(node_id, prop, n) \
27+
DECLARE_FAKE_VALUE_FUNC(int, target_read_requested_##n, struct i2c_target_config *, \
28+
uint8_t *); \
29+
DECLARE_FAKE_VALUE_FUNC(int, target_read_processed_##n, struct i2c_target_config *, \
30+
uint8_t *); \
31+
DECLARE_FAKE_VALUE_FUNC(int, target_write_requested_##n, struct i2c_target_config *); \
32+
DECLARE_FAKE_VALUE_FUNC(int, target_write_received_##n, struct i2c_target_config *, \
33+
uint8_t); \
34+
DECLARE_FAKE_VALUE_FUNC(int, target_stop_##n, struct i2c_target_config *);
35+
36+
DT_FOREACH_PROP_ELEM(CONTROLLER_LABEL, forwards, DECLARE_FAKE_TARGET_FUNCTIONS)
37+
38+
#define FFF_FAKE_ACTION(node_id, prop, n, fn) \
39+
do { \
40+
fn(target_read_requested_##n); \
41+
fn(target_read_processed_##n); \
42+
fn(target_write_requested_##n); \
43+
fn(target_write_received_##n); \
44+
fn(target_stop_##n); \
45+
} while (0);
46+
47+
#define FFF_FAKES_LIST_FOREACH(fn) \
48+
DT_FOREACH_PROP_ELEM_VARGS(CONTROLLER_LABEL, forwards, FFF_FAKE_ACTION, fn)
49+
50+
#endif /* _TESTS_DRIVERS_I2C_I2C_EMUL_INCLUDE_EMULATED_TARGET_H */

tests/drivers/i2c/i2c_emul/prj.conf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copyright 2024 Google LLC
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
CONFIG_ZTEST=y
5+
CONFIG_I2C=y
6+
CONFIG_I2C_TARGET=y
7+
CONFIG_EMUL=y
8+
9+
CONFIG_CPP=y
10+
CONFIG_STD_CPP17=y
11+
CONFIG_REQUIRES_FULL_LIBCPP=y

0 commit comments

Comments
 (0)