Skip to content

Commit c4e2be5

Browse files
committed
Added version of libi2c utilising libmicrokitco for LionsOS usage
Signed-off-by: Lesley Rossouw <[email protected]>
1 parent 7b1f3b7 commit c4e2be5

File tree

10 files changed

+283
-18
lines changed

10 files changed

+283
-18
lines changed

build.zig

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ const util_putchar_serial_src = [_][]const u8{
6666
};
6767

6868
const libi2c_src = [_][]const u8{"i2c/libi2c.c"};
69+
const libi2c_raw_src = [_][]const u8{"i2c/libi2c_raw.c"};
6970

7071
var libmicrokit: std.Build.LazyPath = undefined;
7172
var libmicrokit_linker_script: std.Build.LazyPath = undefined;
@@ -483,6 +484,23 @@ pub fn build(b: *std.Build) !void {
483484
libi2c.installHeadersDirectory(b.path("include"), "", .{});
484485
b.installArtifact(libi2c);
485486

487+
const libi2c_raw = b.addStaticLibrary(.{
488+
.name = "libi2c_raw",
489+
.target = target,
490+
.optimize = optimize,
491+
});
492+
libi2c_raw.addCSourceFiles(.{
493+
.files = &libi2c_raw_src,
494+
});
495+
libi2c_raw.addIncludePath(b.path("include/sddf/i2c"));
496+
libi2c_raw.addIncludePath(b.path("include/sddf"));
497+
libi2c_raw.addIncludePath(b.path("include/microkit"));
498+
libi2c_raw.addIncludePath(b.path("include/"));
499+
libi2c_raw.addIncludePath(b.path("libco/"));
500+
libi2c_raw.addIncludePath(libmicrokit_include);
501+
libi2c_raw.installHeadersDirectory(b.path("include"), "", .{});
502+
b.installArtifact(libi2c_raw);
503+
486504
// I2C drivers
487505
inline for (std.meta.fields(DriverClass.I2cHost)) |class| {
488506
const driver = addI2cDriverHost(b, util, @enumFromInt(class.value), target, optimize);

examples/i2c/build.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ pub fn build(b: *std.Build) !void {
132132
client_pn532.linkLibrary(sddf_dep.artifact("util"));
133133
client_pn532.linkLibrary(sddf_dep.artifact("util_putchar_serial"));
134134
client_pn532.linkLibrary(pn532_driver);
135-
client_pn532.linkLibrary(sddf_dep.artifact("libi2c"));
135+
client_pn532.linkLibrary(sddf_dep.artifact("libi2c_raw"));
136136

137137
const client_ds3231 = b.addExecutable(.{
138138
.name = "client_ds3231.elf",
@@ -149,7 +149,7 @@ pub fn build(b: *std.Build) !void {
149149
client_ds3231.linkLibrary(sddf_dep.artifact("util"));
150150
client_ds3231.linkLibrary(sddf_dep.artifact("util_putchar_serial"));
151151
client_ds3231.linkLibrary(ds3231_driver);
152-
client_ds3231.linkLibrary(sddf_dep.artifact("libi2c"));
152+
client_ds3231.linkLibrary(sddf_dep.artifact("libi2c_raw"));
153153

154154
// Here we compile libco. Right now this is the only example that uses libco and so
155155
// we just compile it here instead of in a separate build.zig

examples/i2c/client_ds3231.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#include <sddf/i2c/client.h>
1616
#include <sddf/i2c/config.h>
1717
#include <sddf/i2c/devices/ds3231/ds3231.h>
18-
#include <sddf/i2c/libi2c.h>
18+
#include <sddf/i2c/libi2c_raw.h>
1919

2020
// #define DEBUG_CLIENT
2121

examples/i2c/client_pn532.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
#include <sddf/i2c/client.h>
1515
#include <sddf/i2c/config.h>
1616
#include <sddf/i2c/devices/pn532/pn532.h>
17-
#include <sddf/i2c/libi2c.h>
17+
#include <sddf/i2c/libi2c_raw.h>
1818

1919
bool delay_ms(size_t milliseconds);
2020

i2c/devices/ds3231/ds3231.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#include <sddf/i2c/client.h>
1919
#include <sddf/i2c/config.h>
2020
#include <sddf/i2c/devices/ds3231/ds3231.h>
21-
#include <sddf/i2c/libi2c.h>
21+
#include <sddf/i2c/libi2c_raw.h>
2222

2323
// #define DEBUG_DS3231
2424

i2c/devices/pn532/pn532.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#include <sddf/i2c/queue.h>
1111
#include <sddf/i2c/config.h>
1212
#include <sddf/i2c/devices/pn532/pn532.h>
13-
#include <sddf/i2c/libi2c.h>
13+
#include <sddf/i2c/libi2c_raw.h>
1414

1515
// #define DEBUG_PN532
1616

i2c/libi2c.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ static int __i2c_dispatch(libi2c_conf_t *conf, i2c_addr_t address, void *buf, ui
124124
microkit_notify(i2c_config.virt.id);
125125

126126
// Await response.
127-
co_switch(t_event);
127+
microkit_cothread_wait_on_channel(i2c_config.virt.id);
128128

129129
i2c_addr_t returned_addr = 0;
130130
size_t err_cmd = 0; // Irrelevant for single-command runs.
@@ -179,3 +179,11 @@ int i2c_writeread(libi2c_conf_t *conf, i2c_addr_t address, i2c_addr_t reg_addres
179179

180180
return __i2c_dispatch(conf, address, read_buf, len, I2C_FLAG_STOP | I2C_FLAG_READ | I2C_FLAG_WRRD);
181181
}
182+
183+
/**
184+
* Perform a raw I2C dispatch with custom flags.
185+
*/
186+
int i2c_dispatch(libi2c_conf_t *conf, i2c_addr_t address, void *buf, uint16_t len, uint8_t flag_mask)
187+
{
188+
return __i2c_dispatch(conf, address, buf, len, flag_mask);
189+
}

i2c/libi2c_raw.c

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
/*
2+
* Copyright 2025, UNSW
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <sddf/i2c/libi2c_raw.h>
8+
9+
#ifdef DEBUG_LIBI2C
10+
#define LOG_LIBI2C(...) do{ sddf_dprintf("CLIENT|INFO: "); sddf_printf(__VA_ARGS__); }while(0)
11+
#else
12+
#define LOG_LIBI2C(...) do{}while(0)
13+
#endif
14+
15+
/**
16+
* Initialise libI2C and return the conf struct needed for all operations.
17+
*/
18+
int libi2c_init(libi2c_conf_t *conf_struct, i2c_queue_handle_t *queue_handle)
19+
{
20+
conf_struct->handle = queue_handle;
21+
22+
// Allocate bitmask (i.e. zero portion of data region)
23+
for (int i = 0; i < LIBI2C_BITMASK_SZ; i++) {
24+
I2C_DATA_REGION[i] = 0;
25+
}
26+
27+
// Index commands after bytes used for bitmask
28+
conf_struct->data_start = (void *)(I2C_DATA_REGION + LIBI2C_BITMASK_SZ);
29+
return 0;
30+
}
31+
32+
// ########### Command allocator functions ############
33+
// SDFgen will do a lot of the work for us, but unfortunately all of the variables it generates
34+
// are considered runtime-context by the C compiler. As a result, we need to do some magic to
35+
// have a sane allocator which doesn't need a bunch of #defines set based on region sizes.
36+
//
37+
// This allocator sets aside a tracking bitmask from the available room in the data region.
38+
// Commands are 2 bytes, hence a region of size S can store a max of S/2 commands.
39+
// S/2 commands can be indexed using (S/2)/64 = S/128. We set aside this many bitmask words at
40+
// the base of the region. The remaining C=S - (S/128)=127/128 * S bytes are used to store
41+
// ((127/128)S) / 2 commands.
42+
43+
/**
44+
* Given configuration struct, return first available command from allocation bitmask.
45+
* Returns NULL if allocator is exhausted.
46+
*/
47+
static i2c_cmd_t *__libi2c_alloc_cmd(libi2c_conf_t *conf)
48+
{
49+
// Find first non-zero byte in bitmask range
50+
int victim_idx = -1;
51+
for (int i = 0; i < LIBI2C_BITMASK_SZ; i++) {
52+
uint8_t mask = I2C_DATA_REGION[i];
53+
if (I2C_DATA_REGION[i] != 0xFF) {
54+
victim_idx = i;
55+
for (int bit = 0; bit < 8; bit++) {
56+
if (!(mask & (1 << bit))) {
57+
// Mark this bit as allocated
58+
I2C_DATA_REGION[i] |= (1 << bit);
59+
// Calculate command index
60+
int cmd_idx = (i * 8) + bit;
61+
return &conf->data_start[cmd_idx];
62+
}
63+
}
64+
}
65+
}
66+
return NULL; // No space.
67+
}
68+
69+
/**
70+
* Given a pointer to a command in the data region, release this command to the allocator.
71+
*/
72+
static void __libi2c_free_cmd(libi2c_conf_t *conf, i2c_cmd_t *cmd)
73+
{
74+
// Make sure command is actually in the data region.
75+
assert((uintptr_t)cmd > (uintptr_t)I2C_DATA_REGION
76+
&& ((uintptr_t)cmd - (uintptr_t)I2C_DATA_REGION) <= i2c_config.data.size);
77+
78+
size_t cmd_idx = cmd - conf->data_start;
79+
size_t bitmask_byte = cmd_idx / 8;
80+
uint8_t bitmask_bit = cmd_idx % 8;
81+
I2C_DATA_REGION[bitmask_byte] &= ~(1 << bitmask_bit);
82+
}
83+
84+
// ########### I2C interface ###########
85+
86+
static inline int check_meta_buf(void *meta_buf)
87+
{
88+
if ((uintptr_t)meta_buf < (uintptr_t)I2C_META_REGION
89+
|| (uintptr_t)meta_buf > ((uintptr_t)I2C_META_REGION + i2c_config.meta.size)) {
90+
LOG_LIBI2C_ERR("i2c_write called with meta_buf not in meta region!");
91+
return -1;
92+
}
93+
return 0;
94+
}
95+
96+
/**
97+
* Given a buffer pointer from the META region, create an I2C op, dispatch and return when
98+
* complete. This is a blocking function call. Implements all single-cmd ops.
99+
* @return -1 if queue ops fail, positive value corresponding to i2c_err_t, or 0 if successful.
100+
*/
101+
static int __i2c_dispatch(libi2c_conf_t *conf, i2c_addr_t address, void *buf, uint16_t len, uint8_t flag_mask)
102+
{
103+
// Check that supplied buffer is within bounds of meta region
104+
if (check_meta_buf(buf)) {
105+
return -1;
106+
}
107+
// Create a write command
108+
i2c_cmd_t *cmd = __libi2c_alloc_cmd(conf);
109+
if (cmd == NULL) {
110+
LOG_LIBI2C_ERR("__i2c_dispatch failed to allocate command!\n");
111+
return -1;
112+
}
113+
size_t meta_offset = (uint8_t *)buf - I2C_META_REGION;
114+
i2c_err_t error = 0;
115+
cmd->offset = meta_offset;
116+
cmd->len = len;
117+
cmd->flag_mask = flag_mask;
118+
119+
if (i2c_enqueue_request(*conf->handle, address, (uintptr_t)cmd, (uintptr_t)I2C_META_REGION, 1)) {
120+
LOG_LIBI2C_ERR("__i2c_dispatch failed to enqueue request!\n");
121+
error = -1;
122+
goto i2c_dispatch_fail;
123+
}
124+
microkit_notify(i2c_config.virt.id);
125+
126+
// Await response.
127+
co_switch(t_event);
128+
129+
i2c_addr_t returned_addr = 0;
130+
size_t err_cmd = 0; // Irrelevant for single-command runs.
131+
if (i2c_dequeue_response(*conf->handle, &returned_addr, &error, &err_cmd)) {
132+
LOG_LIBI2C_ERR("__i2c_dispatch failed to dequeue response!\n");
133+
error = -1;
134+
goto i2c_dispatch_fail;
135+
}
136+
assert(returned_addr == address); // If this ever fails, the protocol is broken or misused!
137+
i2c_dispatch_fail:
138+
__libi2c_free_cmd(conf, cmd);
139+
return error;
140+
}
141+
142+
/**
143+
* Perform a simple I2C write given a META region buffer containing data.
144+
* To perform a write to a device register, ensure the FIRST byte of write_buf contains
145+
* the register address.
146+
* This is a blocking function call.
147+
* @return -1 if queue ops fail, positive value corresponding to i2c_err_t, or 0 if successful.
148+
*/
149+
int i2c_write(libi2c_conf_t *conf, i2c_addr_t address, void *write_buf, uint16_t len)
150+
{
151+
return __i2c_dispatch(conf, address, write_buf, len, I2C_FLAG_STOP);
152+
}
153+
154+
/**
155+
* Perform a simple I2C read given a META region buffer to store result.
156+
* To perform a read to a device register, use i2c_writeread!
157+
* This is a blocking function call.
158+
* @return -1 if queue ops fail, positive value corresponding to i2c_err_t, or 0 if successful.
159+
*/
160+
int i2c_read(libi2c_conf_t *conf, i2c_addr_t address, void *read_buf, uint16_t len)
161+
{
162+
return __i2c_dispatch(conf, address, read_buf, len, I2C_FLAG_STOP | I2C_FLAG_READ);
163+
}
164+
165+
/**
166+
* Perform an I2C read given a META region buffer to store result, writing the address of a
167+
* peripheral register first.
168+
* This is a blocking function call.
169+
* @return -1 if queue ops fail, positive value corresponding to i2c_err_t, or 0 if successful.
170+
*/
171+
int i2c_writeread(libi2c_conf_t *conf, i2c_addr_t address, i2c_addr_t reg_address, void *read_buf, uint16_t len)
172+
{
173+
// Check that supplied buffer is within bounds of meta region
174+
if (check_meta_buf(read_buf)) {
175+
return -1;
176+
}
177+
// Inject register address to read buf
178+
((i2c_addr_t *)read_buf)[0] = reg_address;
179+
180+
return __i2c_dispatch(conf, address, read_buf, len, I2C_FLAG_STOP | I2C_FLAG_READ | I2C_FLAG_WRRD);
181+
}

include/sddf/i2c/libi2c.h

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* SPDX-License-Identifier: BSD-2-Clause
55
*/
66

7-
// I2C interface library for clients.
7+
// I2C interface library for clients using libmicrokitco.
88
// Provides helper functions for creating requests and handing them to the virtualiser.
99
// Enables automatic allocation of command structs, but user is expected to perform management
1010
// of META region to supply buffers as this is not practical to generalise.
@@ -15,20 +15,12 @@
1515
//
1616
// See i2c/queue.h for details about the I2C transport layer.
1717

18-
// WARNING:: the event cothread is assumed to be available whenever a blocking function is called!
19-
// Be aware of possible danger if your client performs complex multitasking. This library
20-
// also assumes that nothing else is using the DATA or META regions if in use.
21-
2218
#pragma once
23-
#include <libco.h>
2419
#include <stdint.h>
2520
#include <sddf/i2c/queue.h>
2621
#include <sddf/util/printf.h>
2722
#include <sddf/i2c/config.h>
28-
29-
// Client must define and set up these cothreads for this interface to function.
30-
extern cothread_t t_event;
31-
extern cothread_t t_main;
23+
#include <libmicrokitco.h>
3224

3325
// Client must define this. E.g.
3426
// __attribute__((__section__(".i2c_client_config"))) i2c_client_config_t i2c_config;
@@ -56,11 +48,11 @@ extern i2c_client_config_t i2c_config;
5648
typedef struct libi2c_conf {
5749
i2c_queue_handle_t *handle;
5850
i2c_cmd_t *data_start; // Pointer to first command in data region
59-
6051
} libi2c_conf_t;
6152

6253
int libi2c_init(libi2c_conf_t *conf_struct, i2c_queue_handle_t *queue_handle);
6354
static i2c_cmd_t *__libi2c_alloc_cmd(libi2c_conf_t *conf);
6455
int i2c_write(libi2c_conf_t *conf, i2c_addr_t address, void *write_buf, uint16_t len);
6556
int i2c_read(libi2c_conf_t *conf, i2c_addr_t address, void *read_buf, uint16_t len);
6657
int i2c_writeread(libi2c_conf_t *conf, i2c_addr_t address, i2c_addr_t reg_address, void *read_buf, uint16_t len);
58+
int i2c_dispatch(libi2c_conf_t *conf, i2c_addr_t address, void *buf, uint16_t len, uint8_t flag_mask);

include/sddf/i2c/libi2c_raw.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2025, UNSW
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
// I2C interface library for clients using libco on its own (see non-raw variant for libmicrokitco)
8+
// Provides helper functions for creating requests and handing them to the virtualiser.
9+
// Enables automatic allocation of command structs, but user is expected to perform management
10+
// of META region to supply buffers as this is not practical to generalise.
11+
//
12+
// Currently this interface only supports single command requests, but the protocol is capable
13+
// of doing many command requests. If your usage requires more commands per request, do not use
14+
// this library and instead implement direct calls to the protocol in <sddf/i2c/queue.h>
15+
//
16+
// See i2c/queue.h for details about the I2C transport layer.
17+
18+
// WARNING:: the event cothread is assumed to be available whenever a blocking function is called!
19+
// Be aware of possible danger if your client performs complex multitasking. This library
20+
// also assumes that nothing else is using the DATA or META regions if in use.
21+
22+
#pragma once
23+
#include <libco.h>
24+
#include <stdint.h>
25+
#include <sddf/i2c/queue.h>
26+
#include <sddf/util/printf.h>
27+
#include <sddf/i2c/config.h>
28+
29+
// Client must define and set up these cothreads for this interface to function.
30+
extern cothread_t t_event;
31+
extern cothread_t t_main;
32+
33+
// Client must define this. E.g.
34+
// __attribute__((__section__(".i2c_client_config"))) i2c_client_config_t i2c_config;
35+
extern i2c_client_config_t i2c_config;
36+
37+
#define I2C_DATA_REGION ((uint8_t *)i2c_config.data.vaddr)
38+
#define I2C_META_REGION ((uint8_t *)i2c_config.meta.vaddr)
39+
40+
#define LOG_LIBI2C_ERR(...) do{ sddf_printf("LIBI2C|ERROR: "); sddf_printf(__VA_ARGS__); }while(0)
41+
42+
/*
43+
* The sDDF I2C protocol reduces all I2C transactions into a series of commands.
44+
* Commands may designate any of the following operations:
45+
* 1. A read of N bytes, stored in buffer B.
46+
* 2. A read of N bytes from device register R, with register address R stored in the first byte of the read buffer B. Overwritten with read data on return.
47+
* 3. A write of N bytes, supplied by buffer B. Writes to device registers are expressed by
48+
* putting the register address in the first byte of the write buffer.
49+
*
50+
* Any of these command types may optionally use the following flags:
51+
* * RSTART: repeated start
52+
* Other flags are used to describe a read, write or write-read (sub-address read)
53+
*/
54+
55+
#define LIBI2C_BITMASK_SZ (i2c_config.data.size % 2 ? i2c_config.data.size/128 : (i2c_config.data.size+1)/128)
56+
typedef struct libi2c_conf {
57+
i2c_queue_handle_t *handle;
58+
i2c_cmd_t *data_start; // Pointer to first command in data region
59+
60+
} libi2c_conf_t;
61+
62+
int libi2c_init(libi2c_conf_t *conf_struct, i2c_queue_handle_t *queue_handle);
63+
static i2c_cmd_t *__libi2c_alloc_cmd(libi2c_conf_t *conf);
64+
int i2c_write(libi2c_conf_t *conf, i2c_addr_t address, void *write_buf, uint16_t len);
65+
int i2c_read(libi2c_conf_t *conf, i2c_addr_t address, void *read_buf, uint16_t len);
66+
int i2c_writeread(libi2c_conf_t *conf, i2c_addr_t address, i2c_addr_t reg_address, void *read_buf, uint16_t len);

0 commit comments

Comments
 (0)