Skip to content

Commit a738562

Browse files
ziuziakowskajwnrt
authored andcommitted
[sw,test] create QEMU I2C target mode test and harness using transport
This creates a test for Opentitan's I2C target mode for QEMU, using a test harness that utilises the I2C QEMU transport implemented in the previous commit. The tested device emulates a simple "memory" device on each I2C bus. The device consists of a 256-byte "memory". On a write transfer to the device, the first byte sets the "pointer" to within the memory where subsequent bytes are written to or read from. This value is incremented after each written or read byte. During a read transfer, bytes from memory to be read are buffered in the TX FIFO, which is refilled if its fill level drops below a threshold. The test harness uses the I2C host proxy device in QEMU to issue transfers to write to and read from the emulated memory device. Signed-off-by: Alice Ziuziakowska <[email protected]>
1 parent 67ec0fd commit a738562

File tree

4 files changed

+443
-0
lines changed

4 files changed

+443
-0
lines changed

sw/device/tests/qemu/BUILD

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,29 @@ opentitan_test(
4646
"//sw/device/lib/testing/test_framework:ottf_main",
4747
],
4848
)
49+
50+
opentitan_test(
51+
name = "i2c_qemu_target_device_test",
52+
srcs = ["i2c_qemu_target_device_test.c"],
53+
exec_env = {
54+
"//hw/top_earlgrey:sim_qemu_rom_with_fake_keys": None,
55+
"//hw/top_earlgrey:sim_qemu_sival_rom_ext": None,
56+
},
57+
qemu = qemu_params(
58+
test_harness = "//sw/host/tests/qemu:i2c_target",
59+
),
60+
deps = [
61+
"//hw/top_earlgrey/sw/autogen:top_earlgrey",
62+
"//sw/device/lib/arch:device",
63+
"//sw/device/lib/base:memory",
64+
"//sw/device/lib/base:mmio",
65+
"//sw/device/lib/dif:i2c",
66+
"//sw/device/lib/dif:rv_plic",
67+
"//sw/device/lib/runtime:hart",
68+
"//sw/device/lib/runtime:log",
69+
"//sw/device/lib/runtime:print",
70+
"//sw/device/lib/testing:i2c_testutils",
71+
"//sw/device/lib/testing:rv_core_ibex_testutils",
72+
"//sw/device/lib/testing/test_framework:ottf_main",
73+
],
74+
)
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
// Copyright lowRISC contributors (OpenTitan project).
2+
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3+
// SPDX-License-Identifier: Apache-2.0
4+
#include <assert.h>
5+
#include <stdint.h>
6+
7+
#include "sw/device/lib/base/mmio.h"
8+
#include "sw/device/lib/dif/dif_i2c.h"
9+
#include "sw/device/lib/dif/dif_rv_plic.h"
10+
#include "sw/device/lib/runtime/irq.h"
11+
#include "sw/device/lib/testing/i2c_testutils.h"
12+
#include "sw/device/lib/testing/rv_plic_testutils.h"
13+
#include "sw/device/lib/testing/test_framework/check.h"
14+
#include "sw/device/lib/testing/test_framework/ottf_main.h"
15+
16+
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
17+
#include "i2c_regs.h" // Generated.
18+
19+
static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__,
20+
"This test assumes the target platform is little endian.");
21+
22+
OTTF_DEFINE_TEST_CONFIG();
23+
24+
/*
25+
* This test emulates a simple memory device over Opentitan's I2C target mode.
26+
* The device consists of a 256-byte memory that can be written and read.
27+
*
28+
* On a write transfer to the device, the first byte written sets the pointer
29+
* to which subsequent bytes are written to and read from.
30+
* Subsequent written bytes set the value in memory at the pointer's location
31+
* and increment it. Reads from the device read from the memory at the
32+
* pointer's location and increment it.
33+
*
34+
* We also listen for another address on the I2C bus. Writes to that address
35+
* are used to signal successful termination of the test with a magic value.
36+
*
37+
* This test runs in QEMU. Read and write transfers should be performed with
38+
* Opentitantool.
39+
*/
40+
41+
enum {
42+
kHart = kTopEarlgreyPlicTargetIbex0,
43+
// Our address on the bus. On this address, emulates a memory device
44+
kI2cTargetMemoryDeviceAddr = 0x40u,
45+
// Second address for our device. Sending a magic value to this address causes
46+
// the test to terminate successfully.
47+
kI2cTargetMagicDeviceAddr = 0x41u,
48+
// Our address mask for I2C target mode: All 1s except least significant bit,
49+
// to match addresses 0x40 and 0x41
50+
kI2cTargetDeviceMask = 0x7eu,
51+
// TX level threshold and how much to fill to for device reads
52+
kTxThreshold = 4u,
53+
kTxFill = 2u * kTxThreshold,
54+
// Memory device memory size
55+
kMemorySize = 256u,
56+
// Magic number to terminate the test successfully
57+
kMagic = 0xaau,
58+
};
59+
60+
static_assert(
61+
kMemorySize <= 256u,
62+
"Memory size must be at most the number of representable byte values");
63+
64+
static dif_i2c_t i2cs[3];
65+
static dif_rv_plic_t plic;
66+
67+
// Memory device that Opentitan will be emulating in I2C Target Mode
68+
struct device {
69+
/* Memory data */
70+
uint8_t data[kMemorySize];
71+
// Index into memory data, set by first write on byte and then
72+
// auto-incremented on read/write byte
73+
uint8_t ptr;
74+
// Whether the next byte is the first byte to set the memory pointer
75+
bool first_byte;
76+
// Target used for this transfer, used by the device to emulate different
77+
// functionality based on the address used
78+
uint8_t address;
79+
};
80+
81+
static struct device devices[3];
82+
83+
static volatile bool quit;
84+
static volatile status_t isr_status;
85+
86+
static status_t external_isr(void);
87+
88+
void ottf_external_isr(uint32_t *exc_info) {
89+
OT_DISCARD(exc_info);
90+
isr_status = external_isr();
91+
}
92+
93+
// Fill TX FIFO with memory data
94+
status_t i2c_mem_device_tx_fill(size_t i2c_idx) {
95+
dif_i2c_t *i2c = &i2cs[i2c_idx];
96+
struct device *device = &devices[i2c_idx];
97+
dif_i2c_level_t tx_lvl;
98+
do {
99+
TRY(dif_i2c_transmit_byte(i2c, device->data[device->ptr]));
100+
device->ptr = (device->ptr + 1) % kMemorySize;
101+
TRY(dif_i2c_get_fifo_levels(i2c, NULL, NULL, &tx_lvl, NULL));
102+
} while (tx_lvl < kTxFill);
103+
return OK_STATUS();
104+
}
105+
106+
status_t i2c_mem_device_start(size_t i2c_idx, uint8_t data) {
107+
dif_i2c_t *i2c = &i2cs[i2c_idx];
108+
struct device *device = &devices[i2c_idx];
109+
device->first_byte = true;
110+
if ((data & 0x1u) == 1u) {
111+
// is a read command, enable tx threshold interrupts and queue
112+
// response bytes, reading from the memory
113+
TRY(dif_i2c_reset_tx_fifo(i2c));
114+
TRY(i2c_mem_device_tx_fill(i2c_idx));
115+
TRY(dif_i2c_set_target_watermarks(i2c, /*tx level=*/kTxThreshold,
116+
/* acq_level */ 0u));
117+
TRY(dif_i2c_irq_set_enabled(i2c, kDifI2cIrqTxThreshold, kDifToggleEnabled));
118+
}
119+
return OK_STATUS();
120+
}
121+
122+
status_t i2c_mem_device_process_byte(size_t i2c_idx, uint8_t data) {
123+
struct device *device = &devices[i2c_idx];
124+
// Only received during an ongoing write transaction, so this is a write
125+
// command
126+
if (device->first_byte) {
127+
device->first_byte = false;
128+
device->ptr = data;
129+
} else {
130+
device->data[device->ptr] = data;
131+
device->ptr = (device->ptr + 1) % kMemorySize;
132+
}
133+
return OK_STATUS();
134+
}
135+
136+
status_t i2c_mem_device_stop(size_t i2c_idx) {
137+
dif_i2c_t *i2c = &i2cs[i2c_idx];
138+
TRY(dif_i2c_irq_set_enabled(i2c, kDifI2cIrqTxThreshold, kDifToggleDisabled));
139+
TRY(dif_i2c_set_target_watermarks(i2c, /*tx level=*/0u,
140+
/* acq_level */ 0u));
141+
TRY(dif_i2c_reset_tx_fifo(i2c));
142+
return OK_STATUS();
143+
}
144+
145+
// Handle I2C transaction state change and data write
146+
status_t i2c_device_process_byte(size_t i2c_idx, uint8_t data,
147+
dif_i2c_signal_t signal) {
148+
struct device *device = &devices[i2c_idx];
149+
switch (signal) {
150+
case kDifI2cSignalStart:
151+
device->address = (data >> 1u);
152+
if (device->address == kI2cTargetMemoryDeviceAddr) {
153+
// emulate the memory device
154+
TRY(i2c_mem_device_start(i2c_idx, data));
155+
} else if (device->address != kI2cTargetMagicDeviceAddr) {
156+
// unreachable
157+
return ABORTED();
158+
}
159+
break;
160+
case kDifI2cSignalStop:
161+
if (device->address == kI2cTargetMemoryDeviceAddr) {
162+
// emulate the memory device
163+
TRY(i2c_mem_device_stop(i2c_idx));
164+
} else if (device->address != kI2cTargetMagicDeviceAddr) {
165+
// unreachable
166+
return ABORTED();
167+
}
168+
break;
169+
case kDifI2cSignalNone:
170+
if (device->address == kI2cTargetMemoryDeviceAddr) {
171+
TRY(i2c_mem_device_process_byte(i2c_idx, data));
172+
} else if (device->address == kI2cTargetMagicDeviceAddr) {
173+
// quit if magic value was written
174+
quit = (data == kMagic);
175+
} else {
176+
// unreachable
177+
return ABORTED();
178+
}
179+
break;
180+
default:
181+
return ABORTED();
182+
}
183+
return OK_STATUS();
184+
}
185+
186+
status_t i2c_device_acq_bytes(size_t i2c_idx) {
187+
dif_i2c_t *i2c = &i2cs[i2c_idx];
188+
dif_i2c_status_t status;
189+
do {
190+
uint8_t acq_byte = 0u;
191+
dif_i2c_signal_t signal = kDifI2cSignalNone;
192+
TRY(dif_i2c_acquire_byte(i2c, &acq_byte, &signal));
193+
TRY(i2c_device_process_byte(i2c_idx, acq_byte, signal));
194+
TRY(dif_i2c_get_status(i2c, &status));
195+
} while (!status.acq_fifo_empty);
196+
return OK_STATUS();
197+
}
198+
199+
status_t i2c_device_emulate(size_t i2c_idx, dif_i2c_irq_t irq) {
200+
if (irq == kDifI2cIrqAcqThreshold) {
201+
TRY(i2c_device_acq_bytes(i2c_idx));
202+
} else if (irq == kDifI2cIrqTxThreshold) {
203+
TRY(i2c_mem_device_tx_fill(i2c_idx));
204+
} else {
205+
// Unexpected IRQ
206+
return ABORTED();
207+
}
208+
return OK_STATUS();
209+
}
210+
211+
static status_t i2c_setup(dif_i2c_t *i2c) {
212+
dif_i2c_id_t i2c_address = {.address = kI2cTargetMemoryDeviceAddr,
213+
.mask = kI2cTargetDeviceMask};
214+
TRY(dif_i2c_set_device_id(i2c, &i2c_address, NULL));
215+
TRY(dif_i2c_set_target_watermarks(i2c, /*tx level=*/0u,
216+
/* acq_level */ 0u));
217+
TRY(dif_i2c_device_set_enabled(i2c, kDifToggleEnabled));
218+
return OK_STATUS();
219+
}
220+
221+
static status_t external_isr(void) {
222+
dif_rv_plic_irq_id_t plic_irq_id;
223+
// Claim IRQ
224+
CHECK_DIF_OK(dif_rv_plic_irq_claim(&plic, kHart, &plic_irq_id));
225+
226+
top_earlgrey_plic_peripheral_t peripheral = (top_earlgrey_plic_peripheral_t)
227+
top_earlgrey_plic_interrupt_for_peripheral[plic_irq_id];
228+
229+
size_t i2c_idx = 0;
230+
dif_i2c_irq_t i2c_irq_id = 0;
231+
switch (peripheral) {
232+
case kTopEarlgreyPlicPeripheralI2c0:
233+
i2c_idx = 0;
234+
i2c_irq_id = (dif_i2c_irq_t)(plic_irq_id -
235+
(dif_rv_plic_irq_id_t)
236+
kTopEarlgreyPlicIrqIdI2c0FmtThreshold);
237+
break;
238+
case kTopEarlgreyPlicPeripheralI2c1:
239+
i2c_idx = 1;
240+
i2c_irq_id = (dif_i2c_irq_t)(plic_irq_id -
241+
(dif_rv_plic_irq_id_t)
242+
kTopEarlgreyPlicIrqIdI2c1FmtThreshold);
243+
break;
244+
case kTopEarlgreyPlicPeripheralI2c2:
245+
i2c_idx = 2;
246+
i2c_irq_id = (dif_i2c_irq_t)(plic_irq_id -
247+
(dif_rv_plic_irq_id_t)
248+
kTopEarlgreyPlicIrqIdI2c2FmtThreshold);
249+
break;
250+
default:
251+
CHECK(false, "Interrupt from unexpected peripheral triggered");
252+
}
253+
254+
status_t ret = i2c_device_emulate(i2c_idx, i2c_irq_id);
255+
256+
// Acknowledge IRQ and return
257+
CHECK_DIF_OK(dif_i2c_irq_acknowledge(&i2cs[i2c_idx], i2c_irq_id));
258+
CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic, kHart, plic_irq_id));
259+
return ret;
260+
}
261+
262+
bool test_main(void) {
263+
memset(&devices, 0u, sizeof(devices));
264+
265+
const uintptr_t kI2cBaseAddrs[3] = {TOP_EARLGREY_I2C0_BASE_ADDR,
266+
TOP_EARLGREY_I2C1_BASE_ADDR,
267+
TOP_EARLGREY_I2C2_BASE_ADDR};
268+
269+
for (size_t i = 0; i < ARRAYSIZE(i2cs); i++) {
270+
mmio_region_t i2c_region;
271+
i2c_region = mmio_region_from_addr(kI2cBaseAddrs[i]);
272+
CHECK_DIF_OK(dif_i2c_init(i2c_region, &i2cs[i]));
273+
CHECK_STATUS_OK(i2c_setup(&i2cs[i]));
274+
}
275+
276+
mmio_region_t plic_region =
277+
mmio_region_from_addr(TOP_EARLGREY_RV_PLIC_BASE_ADDR);
278+
CHECK_DIF_OK(dif_rv_plic_init(plic_region, &plic));
279+
280+
rv_plic_testutils_irq_range_enable(&plic, kHart,
281+
kTopEarlgreyPlicIrqIdI2c0FmtThreshold,
282+
kTopEarlgreyPlicIrqIdI2c2HostTimeout);
283+
284+
for (size_t i = 0; i < ARRAYSIZE(i2cs); i++) {
285+
CHECK_DIF_OK(dif_i2c_irq_set_enabled(&i2cs[i], kDifI2cIrqAcqThreshold,
286+
kDifToggleEnabled));
287+
}
288+
289+
quit = false;
290+
isr_status = OK_STATUS();
291+
292+
irq_external_ctrl(true);
293+
LOG_INFO("SYNC: Device Ready");
294+
295+
ATOMIC_WAIT_FOR_INTERRUPT(quit == true || !status_ok(isr_status));
296+
297+
return status_ok(isr_status);
298+
}

sw/host/tests/qemu/BUILD

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,16 @@ rust_binary(
1717
"@crate_index//:clap",
1818
],
1919
)
20+
21+
rust_binary(
22+
name = "i2c_target",
23+
srcs = [
24+
"src/i2c_target.rs",
25+
],
26+
deps = [
27+
"//sw/host/opentitanlib",
28+
"@crate_index//:anyhow",
29+
"@crate_index//:clap",
30+
"@crate_index//:humantime",
31+
],
32+
)

0 commit comments

Comments
 (0)