Skip to content

Commit 82c7d8d

Browse files
authored
esp: RTOS optimizations (#898)
Some numbers with 200 concurrent tasks: - Plain cooperative rescheduling of a task until the switch to a different task: 140-150 cycles - If the task suspends itself (eg: waiting on a mutex without timeout): 126-137 cycles (this only measures the yielding time, not the overhead induced by the mutex) - If the task sleeps: 268-1700 cycles (1700 is for the 200th task that must be inserted at the end of the sleep_queue since it wakes up last). This is O(n). Also there is an overhead in the tick interrupt when all these tasks are woken up: the max tick interrupt time is close to 4000cycles (O(n) with the number of tasks waking up on the same tick) (otherwise being ~250cycles if no tasks wake). Time slicing support added (switch between tasks of the same priority on the tick interval). This is by default off. Tasks can now be awaited and the memory freed. I used this to implement cleanup for radio/timer.zig, but for the tasks spawned by the wifi driver it is more complicated so I will leave this for a later PR. Unit tests for the esp port now run on CI.
1 parent 0eebe6c commit 82c7d8d

9 files changed

Lines changed: 691 additions & 274 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ jobs:
170170
strategy:
171171
matrix:
172172
os: [ubuntu-latest, windows-latest, macos-latest]
173-
port_dir: [gigadevice/gd32, raspberrypi/rp2xxx, stmicro/stm32, wch/ch32v]
173+
port_dir: [gigadevice/gd32, espressif/esp, raspberrypi/rp2xxx, stmicro/stm32, wch/ch32v]
174174
steps:
175175
- name: Checkout
176176
uses: actions/checkout@v4

examples/espressif/esp/src/rtos.zig

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ const rtos = esp.rtos;
1212
pub const microzig_options: microzig.Options = .{
1313
.logFn = usb_serial_jtag.logger.log,
1414
.interrupts = .{
15-
.interrupt30 = rtos.general_purpose_interrupt_handler,
16-
.interrupt31 = rtos.yield_interrupt_handler,
15+
.interrupt31 = rtos.tick_interrupt_handler,
1716
},
1817
.log_level = .debug,
1918
.cpu = .{
@@ -29,8 +28,6 @@ pub const microzig_options: microzig.Options = .{
2928
},
3029
};
3130

32-
var heap_buf: [10 * 1024]u8 = undefined;
33-
3431
fn task1(queue: *rtos.Queue(u32)) void {
3532
for (0..5) |i| {
3633
queue.put_one(i, null) catch unreachable;
@@ -39,15 +36,16 @@ fn task1(queue: *rtos.Queue(u32)) void {
3936
}
4037

4138
pub fn main() !void {
42-
var heap = try microzig.Allocator.init_with_buffer(&heap_buf);
39+
var heap = try microzig.Allocator.init_with_heap(4096);
4340
const gpa = heap.allocator();
4441

4542
var buffer: [1]u32 = undefined;
4643
var queue: rtos.Queue(u32) = .init(&buffer);
4744

4845
esp.time.sleep_ms(1000);
4946

50-
_ = try rtos.spawn(gpa, task1, .{&queue}, .{});
47+
const task = try rtos.spawn(gpa, task1, .{&queue}, .{});
48+
defer rtos.wait_and_free(gpa, task);
5149

5250
while (true) {
5351
const item = try queue.get_one(.from_ms(1000));

examples/espressif/esp/src/tcp_server.zig

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,8 @@ pub const microzig_options: microzig.Options = .{
2525
},
2626
.logFn = usb_serial_jtag.logger.log,
2727
.interrupts = .{
28-
.interrupt29 = radio.interrupt_handler,
29-
.interrupt30 = rtos.general_purpose_interrupt_handler,
30-
.interrupt31 = rtos.yield_interrupt_handler,
28+
.interrupt30 = radio.interrupt_handler,
29+
.interrupt31 = rtos.tick_interrupt_handler,
3130
},
3231
.cpu = .{
3332
.interrupt_stack = .{

port/espressif/esp/src/cpus/esp_riscv.zig

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -615,11 +615,11 @@ pub const csr = struct {
615615
pub const tdata1 = riscv32_common.csr.tdata1;
616616
pub const tdata2 = riscv32_common.csr.tdata2;
617617
pub const tcontrol = Csr(0x7A5, packed struct {
618-
reserved0: u3,
619-
mte: u1,
620-
reserved1: u3,
621-
mpte: u1,
622-
reserved2: u24,
618+
reserved0: u3 = 0,
619+
mte: u1 = 0,
620+
reserved1: u3 = 0,
621+
mpte: u1 = 0,
622+
reserved2: u24 = 0,
623623
});
624624

625625
pub const dcsr = riscv32_common.csr.dcsr;
@@ -628,23 +628,23 @@ pub const csr = struct {
628628
pub const dscratch1 = riscv32_common.csr.dscratch1;
629629

630630
pub const mpcer = Csr(0x7E0, packed struct {
631-
cycle: u1,
632-
inst: u1,
633-
ld_hazard: u1,
634-
jmp_hazard: u1,
635-
idle: u1,
636-
load: u1,
637-
store: u1,
638-
jmp_uncond: u1,
639-
branch: u1,
640-
branch_taken: u1,
641-
inst_comp: u1,
642-
reserved0: u21,
631+
cycle: u1 = 0,
632+
inst: u1 = 0,
633+
ld_hazard: u1 = 0,
634+
jmp_hazard: u1 = 0,
635+
idle: u1 = 0,
636+
load: u1 = 0,
637+
store: u1 = 0,
638+
jmp_uncond: u1 = 0,
639+
branch: u1 = 0,
640+
branch_taken: u1 = 0,
641+
inst_comp: u1 = 0,
642+
reserved0: u21 = 0,
643643
});
644644
pub const mpcmr = Csr(0x7E1, packed struct {
645-
count_en: u1,
646-
count_sat: u1,
647-
reserved0: u30,
645+
count_en: u1 = 0,
646+
count_sat: u1 = 0,
647+
reserved0: u30 = 0,
648648
});
649649
pub const mpccr = Csr(0x7E2, u32);
650650

port/espressif/esp/src/hal.zig

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const builtin = @import("builtin");
12
const std = @import("std");
23

34
pub const esp_image = @import("esp_image");
@@ -23,8 +24,15 @@ pub const uart = @import("hal/uart.zig");
2324
pub const usb_serial_jtag = @import("hal/usb_serial_jtag.zig");
2425

2526
comptime {
26-
// export atomic intrinsics
27-
_ = @import("hal/atomic.zig");
27+
if (!builtin.is_test) {
28+
// export atomic intrinsics
29+
_ = @import("hal/atomic.zig");
30+
31+
@export(&app_desc, .{
32+
.name = "esp_app_desc",
33+
.section = ".app_desc",
34+
});
35+
}
2836
}
2937

3038
pub const HAL_Options = struct {
@@ -89,7 +97,7 @@ fn disable_watchdogs() void {
8997

9098
// Don't change the name of this export, it is checked by espflash tool. Only
9199
// these fields are populated here. The others will be set by elf2image.
92-
export const esp_app_desc: esp_image.AppDesc linksection(".app_desc") = .{
100+
const app_desc: esp_image.AppDesc = .{
93101
.secure_version = microzig.options.hal.info.secure_version,
94102
.version = str(32, microzig.options.hal.info.version),
95103
.project_name = str(32, microzig.options.hal.info.project_name),
@@ -105,3 +113,7 @@ fn str(comptime l: usize, comptime s: []const u8) [l]u8 {
105113
std.mem.copyForwards(u8, buf[0..s.len], s);
106114
return buf;
107115
}
116+
117+
test {
118+
_ = rtos;
119+
}

port/espressif/esp/src/hal/radio.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub const wifi = @import("radio/wifi.zig");
2222
const log = std.log.scoped(.esp_radio);
2323

2424
pub const Options = struct {
25-
interrupt: microzig.cpu.Interrupt = .interrupt29,
25+
interrupt: microzig.cpu.Interrupt = .interrupt30,
2626
wifi: wifi.Options = .{},
2727
};
2828

@@ -81,7 +81,7 @@ pub fn deinit() void {
8181
return;
8282
}
8383

84-
timer.deinit();
84+
timer.deinit(osi.gpa);
8585
}
8686

8787
pub fn read_mac(iface: enum {

port/espressif/esp/src/hal/radio/osi.zig

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ pub fn usleep(time_us: u32) callconv(.c) c_int {
148148
}
149149

150150
pub fn vTaskDelay(ticks: u32) callconv(.c) void {
151-
rtos.sleep(.from_us(ticks));
151+
rtos.sleep(.from_ticks(ticks));
152152
}
153153

154154
pub var WIFI_EVENT: c.esp_event_base_t = "WIFI_EVENT";
@@ -321,8 +321,8 @@ pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 {
321321
log.debug("semphr_take {?} {}", .{ ptr, tick });
322322

323323
const sem: *rtos.Semaphore = @ptrCast(@alignCast(ptr));
324-
const maybe_timeout: ?time.Duration = if (tick == c.OSI_FUNCS_TIME_BLOCKING)
325-
.from_us(tick)
324+
const maybe_timeout: ?rtos.Duration = if (tick == c.OSI_FUNCS_TIME_BLOCKING)
325+
.from_ticks(tick)
326326
else
327327
null;
328328
sem.take_with_timeout(maybe_timeout) catch {
@@ -512,8 +512,8 @@ pub fn queue_send(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32)
512512
else => queue.inner.put(
513513
item[0..queue.item_len],
514514
1,
515-
if (block_time_tick == c.OSI_FUNCS_TIME_BLOCKING)
516-
.from_us(block_time_tick)
515+
if (block_time_tick != c.OSI_FUNCS_TIME_BLOCKING)
516+
.from_ticks(block_time_tick)
517517
else
518518
null,
519519
),
@@ -553,8 +553,8 @@ pub fn queue_recv(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32)
553553
else => queue.inner.get(
554554
item[0..queue.item_len],
555555
queue.item_len,
556-
if (block_time_tick == c.OSI_FUNCS_TIME_BLOCKING)
557-
.from_us(block_time_tick)
556+
if (block_time_tick != c.OSI_FUNCS_TIME_BLOCKING)
557+
.from_ticks(block_time_tick)
558558
else
559559
null,
560560
),
@@ -673,17 +673,17 @@ pub fn task_delete(handle: ?*anyopaque) callconv(.c) void {
673673
if (handle != null) {
674674
@panic("task_delete(non-null): not implemented");
675675
}
676-
rtos.yield(.delete);
676+
rtos.yield(.exit);
677677
}
678678

679679
pub fn task_delay(tick: u32) callconv(.c) void {
680680
log.debug("task_delay {}", .{tick});
681681

682-
rtos.sleep(.from_us(tick));
682+
rtos.sleep(.from_ticks(tick));
683683
}
684684

685685
pub fn task_ms_to_tick(ms: u32) callconv(.c) i32 {
686-
return @intCast(ms * 1_000);
686+
return @intCast(rtos.Duration.from_ms(ms).to_ticks());
687687
}
688688

689689
pub fn task_get_current_task() callconv(.c) ?*anyopaque {

port/espressif/esp/src/hal/radio/timer.zig

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const std = @import("std");
2+
const assert = std.debug.assert;
23
const Allocator = std.mem.Allocator;
34

45
const c = @import("esp-wifi-driver");
@@ -21,20 +22,36 @@ pub const Timer = struct {
2122
node: std.SinglyLinkedList.Node = .{},
2223
};
2324

25+
var timer_task: ?*rtos.Task = null;
26+
var exit_flag: std.atomic.Value(bool) = .init(false);
2427
var reload_semaphore: rtos.Semaphore = .init(0, 1);
2528
var mutex: rtos.Mutex = .{};
2629
var timer_list: std.SinglyLinkedList = .{};
2730

2831
pub fn init(gpa: Allocator) Allocator.Error!void {
29-
_ = try rtos.spawn(gpa, task_fn, .{}, .{
32+
exit_flag.store(false, .monotonic);
33+
34+
assert(timer_task == null);
35+
timer_task = try rtos.spawn(gpa, task_fn, .{}, .{
3036
.name = "radio_timer",
3137
.priority = .lowest, // TODO: what should the priority be?
3238
.stack_size = 4096,
3339
});
3440
}
3541

36-
pub fn deinit() void {
37-
// TODO: exit mechanism
42+
pub fn deinit(gpa: Allocator) void {
43+
exit_flag.store(true, .monotonic);
44+
reload_semaphore.give();
45+
46+
if (timer_task) |task| {
47+
rtos.wait_and_free(gpa, task);
48+
timer_task = null;
49+
}
50+
51+
while (timer_list.popFirst()) |node| {
52+
const timer: *Timer = @alignCast(@fieldParentPtr("node", node));
53+
gpa.destroy(timer);
54+
}
3855
}
3956

4057
pub fn setfn(
@@ -112,6 +129,9 @@ pub fn done(gpa: std.mem.Allocator, ets_timer: *c.ets_timer) void {
112129

113130
fn task_fn() void {
114131
while (true) {
132+
if (exit_flag.load(.monotonic))
133+
return;
134+
115135
const now = get_time_since_boot();
116136
while (true) {
117137
const callback, const arg = blk: {
@@ -130,11 +150,11 @@ fn task_fn() void {
130150
callback(arg);
131151
}
132152

133-
const sleep_duration = blk: {
153+
const sleep_duration: ?rtos.Duration = blk: {
134154
mutex.lock();
135155
defer mutex.unlock();
136156
break :blk if (find_next_wake_absolute()) |next_wake_absolute|
137-
next_wake_absolute.diff(now)
157+
.from_us(@truncate(next_wake_absolute.diff(now).to_us()))
138158
else
139159
null;
140160
};

0 commit comments

Comments
 (0)