From 156adf01147dfcef3fa47f20dec880faa903154f Mon Sep 17 00:00:00 2001 From: tact1m4n3 Date: Mon, 14 Jul 2025 20:35:38 +0300 Subject: [PATCH 01/43] initial commit (it works!!!) --- examples/espressif/esp/.gdb_history | 15 + examples/espressif/esp/build.zig | 17 + examples/espressif/esp/src/wifi.zig | 106 + modules/riscv32-common/src/riscv32_common.zig | 4 + port/espressif/esp/build.zig | 75 +- port/espressif/esp/build.zig.zon | 4 + port/espressif/esp/esp32_c3.ld | 2591 +++++++++++++++++ .../esp/ld/esp32_c3/direct_boot_sections.ld | 26 +- .../esp/ld/esp32_c3/flashless_sections.ld | 18 + .../esp/ld/esp32_c3/image_boot_sections.ld | 85 +- .../esp/ld/esp32_c3/rom_functions.ld | 287 +- port/espressif/esp/src/cpus/esp_riscv.zig | 11 +- port/espressif/esp/src/hal.zig | 3 +- port/espressif/esp/src/hal/radio.zig | 190 ++ .../espressif/esp/src/hal/radio/bluetooth.zig | 0 .../esp/src/hal/radio/global_state.zig | 0 .../src/hal/radio/libc_dummy_include/assert.h | 0 .../src/hal/radio/libc_dummy_include/stdio.h | 5 + .../src/hal/radio/libc_dummy_include/stdlib.h | 0 .../hal/radio/libc_dummy_include/sys/queue.h | 0 .../esp/src/hal/radio/multitasking.zig | 95 + port/espressif/esp/src/hal/radio/osi.zig | 1431 +++++++++ port/espressif/esp/src/hal/radio/timer.zig | 106 + port/espressif/esp/src/hal/radio/wifi.zig | 750 +++++ port/espressif/esp/src/hal/wifi.zig | 1752 +++++++++++ tools/printer/src/root.zig | 2 +- 26 files changed, 7270 insertions(+), 303 deletions(-) create mode 100644 examples/espressif/esp/.gdb_history create mode 100644 examples/espressif/esp/src/wifi.zig create mode 100644 port/espressif/esp/esp32_c3.ld create mode 100644 port/espressif/esp/src/hal/radio.zig create mode 100644 port/espressif/esp/src/hal/radio/bluetooth.zig create mode 100644 port/espressif/esp/src/hal/radio/global_state.zig create mode 100644 port/espressif/esp/src/hal/radio/libc_dummy_include/assert.h create mode 100644 port/espressif/esp/src/hal/radio/libc_dummy_include/stdio.h create mode 100644 port/espressif/esp/src/hal/radio/libc_dummy_include/stdlib.h create mode 100644 port/espressif/esp/src/hal/radio/libc_dummy_include/sys/queue.h create mode 100644 port/espressif/esp/src/hal/radio/multitasking.zig create mode 100644 port/espressif/esp/src/hal/radio/osi.zig create mode 100644 port/espressif/esp/src/hal/radio/timer.zig create mode 100644 port/espressif/esp/src/hal/radio/wifi.zig create mode 100644 port/espressif/esp/src/hal/wifi.zig diff --git a/examples/espressif/esp/.gdb_history b/examples/espressif/esp/.gdb_history new file mode 100644 index 000000000..1c81a854f --- /dev/null +++ b/examples/espressif/esp/.gdb_history @@ -0,0 +1,15 @@ +target remote :3333 +l +r +continue +target remote :3333 +c +target remote :3333 +b hal.radio.osi.event_post +c +stack 20 +target remote :3333 +c +target remote :3333 +b ccmp_encrypt +c diff --git a/examples/espressif/esp/build.zig b/examples/espressif/esp/build.zig index f58a5e8c4..126e779be 100644 --- a/examples/espressif/esp/build.zig +++ b/examples/espressif/esp/build.zig @@ -60,6 +60,23 @@ pub fn build(b: *std.Build) void { mb.install_firmware(firmware, .{ .format = .elf }); } } + + if (maybe_example) |selected_example| { + if (std.mem.containsAtLeast(u8, "wifi", 1, selected_example)) { + // only works with image boot target for now (it should also + // support direct boot in the future). The flashless target doesn't + // have enough space to run this. + const wifi_example_firmware = mb.add_firmware(.{ + .name = "wifi", + .target = mb.ports.esp.chips.esp32_c3, + .optimize = optimize, + .root_source_file = b.path("src/wifi.zig"), + }); + + mb.install_firmware(wifi_example_firmware, .{}); + mb.install_firmware(wifi_example_firmware, .{ .format = .elf }); + } + } } const TargetDescription = struct { diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig new file mode 100644 index 000000000..352437eb2 --- /dev/null +++ b/examples/espressif/esp/src/wifi.zig @@ -0,0 +1,106 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const interrupt = microzig.cpu.interrupt; +const hal = microzig.hal; +const radio = hal.radio; +const usb_serial_jtag = hal.usb_serial_jtag; + +pub const microzig_options: microzig.Options = .{ + .log_level = .debug, + .log_scope_levels = &.{ + .{ + .scope = .esp_radio, + .level = .info, + }, + .{ + .scope = .esp_radio_osi, + .level = .info, + }, + .{ + .scope = .esp_wifi_driver_internal, + .level = .err, + }, + }, + .logFn = log, + .interrupts = .{ + .interrupt1 = radio.interrupt_handlers.wifi_xxx, + .interrupt2 = radio.interrupt_handlers.timer, + .interrupt3 = radio.interrupt_handlers.software, + }, +}; + +pub fn log( + comptime level: std.log.Level, + comptime scope: @TypeOf(.EnumLiteral), + comptime format: []const u8, + args: anytype, +) void { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + usb_serial_jtag.logger.log(level, scope, format, args); +} + +var buffer: [50 * 1024]u8 = undefined; + +pub fn main() !void { + // microzig.cpu.interrupt.map(.assist_debug, .interrupt1); + // microzig.cpu.interrupt.set_priority(.interrupt1, .lowest); + // microzig.cpu.interrupt.enable(.interrupt1); + // microzig.cpu.interrupt.enable_interrupts(); + // + // microzig.chip.peripherals.ASSIST_DEBUG.CORE_0_SP_MAX.write_raw(0xffffffff); + // microzig.chip.peripherals.ASSIST_DEBUG.CORE_0_SP_MIN.write_raw(0x3FC7C000 + 400 * 1024 - 10); + // microzig.chip.peripherals.ASSIST_DEBUG.CORE_0_INTR_ENA.modify(.{ + // .CORE_0_SP_SPILL_MIN_INTR_ENA = 1, + // .CORE_0_SP_SPILL_MAX_INTR_ENA = 1, + // }); + + var fba: std.heap.FixedBufferAllocator = .init(&buffer); + const allocator = fba.threadSafeAllocator(); + + microzig.cpu.interrupt.enable_interrupts(); + + try radio.init(allocator); + try radio.wifi.init(); + + try radio.wifi.set_mode(.sta); + try radio.wifi.set_client_config(.{ + .ssid = "Internet", + }); + + try radio.wifi.start(); + + try radio.wifi.c_result(radio.wifi.c.esp_wifi_connect()); + + // var ssid: [1:0]u8 = @splat(0); + // var bssid: [1:0]u8 = @splat(0); + // + // var scan_config: c.wifi_scan_config_t = .{ + // .ssid = &ssid, + // .bssid = &bssid, + // .channel = 0, + // .show_hidden = true, + // .scan_type = c.WIFI_SCAN_TYPE_PASSIVE, + // .scan_time = .{ + // .active = .{ .min = 0, .max = 0 }, + // .passive = 2000, + // }, + // .home_chan_dwell_time = 0, + // .channel_bitmap = .{ + // .ghz_2_channels = 0, + // .ghz_5_channels = 0, + // }, + // }; + // try radio.wifi.c_result(c.esp_wifi_scan_start(&scan_config, true)); + // + // var no: u16 = undefined; + // try radio.wifi.c_result(c.esp_wifi_scan_get_ap_num(&no)); + + // std.log.info("found {} aps", .{no}); + + while (true) { + std.log.info("tick!", .{}); + hal.time.sleep_ms(1000); + } +} diff --git a/modules/riscv32-common/src/riscv32_common.zig b/modules/riscv32-common/src/riscv32_common.zig index 4d1384846..1cb22b682 100644 --- a/modules/riscv32-common/src/riscv32_common.zig +++ b/modules/riscv32-common/src/riscv32_common.zig @@ -53,6 +53,10 @@ pub fn wfi() void { asm volatile ("wfi"); } +pub fn fence() void { + asm volatile ("fence"); +} + // NOTE: Contains all csrs from the riscv manual and should follow their spec. Cpu implementations can // reexport what they need from here. pub const csr = struct { diff --git a/port/espressif/esp/build.zig b/port/espressif/esp/build.zig index f86265c81..43cc568a2 100644 --- a/port/espressif/esp/build.zig +++ b/port/espressif/esp/build.zig @@ -12,16 +12,23 @@ chips: struct { boards: struct {}, +const esp32_c3_zig_target: std.Target.Query = .{ + .cpu_arch = .riscv32, + .cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32 }, + .cpu_features_add = std.Target.riscv.featureSet(&.{ + .c, + .m, + }), + .os_tag = .freestanding, + .abi = .eabi, +}; + pub fn init(dep: *std.Build.Dependency) Self { const b = dep.builder; const riscv32_common_dep = b.dependency("microzig/modules/riscv32-common", .{}); const riscv32_common_mod = riscv32_common_dep.module("riscv32-common"); - const hal: microzig.HardwareAbstractionLayer = .{ - .root_source_file = b.path("src/hal.zig"), - }; - const chip_esp32_c3: microzig.Target = .{ .dep = dep, .preferred_binary_format = .{ .esp = .{ @@ -30,16 +37,7 @@ pub fn init(dep: *std.Build.Dependency) Self { .flash_size = .@"4mb", .flash_freq = .@"40m", } }, - .zig_target = .{ - .cpu_arch = .riscv32, - .cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32 }, - .cpu_features_add = std.Target.riscv.featureSet(&.{ - .c, - .m, - }), - .os_tag = .freestanding, - .abi = .eabi, - }, + .zig_target = esp32_c3_zig_target, .cpu = .{ .name = "esp_riscv", .root_source_file = b.path("src/cpus/esp_riscv.zig"), @@ -66,7 +64,15 @@ pub fn init(dep: *std.Build.Dependency) Self { .{ .name = "DRAM", .tag = .ram, .offset = 0x3FC7C000 + 0x4000, .length = 313 * 1024, .access = .rw }, }, }, - .hal = hal, + .hal = .{ + .root_source_file = b.path("src/hal.zig"), + .imports = b.allocator.dupe(Import, &.{ + .{ + .name = "esp-wifi-driver", + .module = dep.module("esp-wifi-driver"), + }, + }) catch @panic("OOM"), + }, .linker_script = .{ .generate = .memory_regions, .file = generate_linker_script( @@ -145,6 +151,45 @@ pub fn build(b: *std.Build) void { }), }); b.installArtifact(cat_exe); + + const esp32_c3_resolved_zig_target = b.resolveTargetQuery(esp32_c3_zig_target); + + const esp_wifi_sys_dep = b.dependency("esp-wifi-sys", .{}); + + const translate_c = b.addTranslateC(.{ + .root_source_file = esp_wifi_sys_dep.path("esp-wifi-sys/include/include.h"), + .target = esp32_c3_resolved_zig_target, + .optimize = .ReleaseFast, + .link_libc = false, + }); + + const mod = translate_c.addModule("esp-wifi-driver"); + + translate_c.addIncludePath(b.path("src/hal/radio/libc_dummy_include")); + translate_c.addIncludePath(esp_wifi_sys_dep.path("esp-wifi-sys/include")); + translate_c.addIncludePath(esp_wifi_sys_dep.path("esp-wifi-sys/headers")); + + // esp32_c3 specific + translate_c.addIncludePath(esp_wifi_sys_dep.path("esp-wifi-sys/headers/esp32c3")); + + mod.addLibraryPath(esp_wifi_sys_dep.path("esp-wifi-sys/libs/esp32c3")); + inline for (&.{ + "btbb", + "btdm_app", + "coexist", + "core", + "espnow", + "mesh", + "net80211", + "phy", + "pp", + "smartconfig", + "wapi", + "wpa_supplicant", + "printf", + }) |library| { + mod.linkSystemLibrary(library, .{}); + } } const BootMode = enum { diff --git a/port/espressif/esp/build.zig.zon b/port/espressif/esp/build.zig.zon index 1e01235ae..765d844e3 100644 --- a/port/espressif/esp/build.zig.zon +++ b/port/espressif/esp/build.zig.zon @@ -5,6 +5,10 @@ .dependencies = .{ .@"microzig/build-internals" = .{ .path = "../../../build-internals" }, .@"microzig/modules/riscv32-common" = .{ .path = "../../../modules/riscv32-common" }, + .@"esp-wifi-sys" = .{ + .url = "git+https://github.com/esp-rs/esp-wifi-sys#99dd43f9992b276efff3c75467839801e7ea55c7", + .hash = "N-V-__8AAHPIYgTLdblCzREIT-te95dusmO6q1o0PST0CziO", + }, }, .paths = .{ "README.md", diff --git a/port/espressif/esp/esp32_c3.ld b/port/espressif/esp/esp32_c3.ld new file mode 100644 index 000000000..ae9ad7af1 --- /dev/null +++ b/port/espressif/esp/esp32_c3.ld @@ -0,0 +1,2591 @@ +/* + * This file was auto-generated by microzig + * + * Target CPU: generic_rv32 + * Target Chip: ESP32-C3 + */ + +ENTRY(_start); + +MEMORY +{ + irom (rx) : ORIGIN = 0x42000020, LENGTH = 0x800000 - 0x20 + drom (r) : ORIGIN = 0x3C000020, LENGTH = 0x800000 - 0x20 + iram (rwx) : ORIGIN = 0x40380000, LENGTH = 313K + dram (rw) : ORIGIN = 0x3FC80000, LENGTH = 313K +} + +SECTIONS +{ + .irom.text : + { + KEEP(*(microzig_flash_start)) + *(.text*) + . += 16; + } > irom + + .drom.dummy (NOLOAD) : + { + . = ALIGN(ALIGNOF(.irom.text)) + SIZEOF(.irom.text); + . = ALIGN(0x10000) + 0x20; + } > drom + + .drom.rodata : ALIGN(0x10) + { + KEEP(*(.app_desc)) + *(.rodata*) + *(.srodata*) + + /* wifi rodata */ + *(.rodata_wlog_*.*) + + . = ALIGN(0x10); + } > drom + + .iram.rwtext : ALIGN(4) + { + . = ALIGN(4); + + KEEP(*(.trap)) + KEEP(*(.ram_text)) + *(.rwtext*) + + /* wifi rwtext */ + *(.wifi0iram .wifi0iram.*) + *(.wifirxiram .wifirxiram.*) + *(.wifislprxiram .wifislprxiram.*) + *(.wifislpiram .wifislpiram.*) + *(.phyiram .phyiram.*) + *(.iram1 .iram1.*) + *(.wifiextrairam.* ) + *(.coexiram.* ) + } > iram + + .dram.dummy (NOLOAD) : + { + . = ALIGN(ALIGNOF(.iram.rwtext)) + SIZEOF(.iram.rwtext); + } > dram + + .dram.data : + { + microzig_data_start = .; + *(.sdata*) + *(.data*) + + /* wifi data */ + *(.dram1 .dram1.*) + } > dram + + .dram.bss (NOLOAD) : + { + microzig_bss_start = .; + *(.bss*) + *(.sbss*) + microzig_bss_end = .; + } > dram + + PROVIDE(__global_pointer$ = microzig_data_start + 0x800); +} + +/* ROM function interface esp32c3.rom.ld for esp32c3 + * + * + * Generated from ./interface-esp32c3.yml md5sum 93b28a9e1fe42d212018eb4336849208 + * + * Compatible with ROM where ECO version equal or greater to 0. + * + * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT. + */ + +/*************************************** + Group common + ***************************************/ + +/* Functions */ +rtc_get_reset_reason = 0x40000018; +analog_super_wdt_reset_happened = 0x4000001c; +jtag_cpu_reset_happened = 0x40000020; +rtc_get_wakeup_cause = 0x40000024; +rtc_boot_control = 0x40000028; +rtc_select_apb_bridge = 0x4000002c; +rtc_unhold_all_pads = 0x40000030; +set_rtc_memory_crc = 0x40000034; +cacl_rtc_memory_crc = 0x40000038; +ets_is_print_boot = 0x4000003c; +ets_printf = 0x40000040; +ets_install_putc1 = 0x40000044; +ets_install_uart_printf = 0x40000048; +ets_install_putc2 = 0x4000004c; +PROVIDE( ets_delay_us = 0x40000050 ); +ets_get_stack_info = 0x40000054; +ets_install_lock = 0x40000058; +ets_backup_dma_copy = 0x4000005c; +ets_apb_backup_init_lock_func = 0x40000060; +UartRxString = 0x40000064; +uart_tx_one_char = 0x40000068; +uart_tx_one_char2 = 0x4000006c; +uart_rx_one_char = 0x40000070; +uart_rx_one_char_block = 0x40000074; +uart_rx_readbuff = 0x40000078; +uartAttach = 0x4000007c; +uart_tx_flush = 0x40000080; +uart_tx_wait_idle = 0x40000084; +uart_div_modify = 0x40000088; +multofup = 0x4000008c; +software_reset = 0x40000090; +software_reset_cpu = 0x40000094; +assist_debug_clock_enable = 0x40000098; +assist_debug_record_enable = 0x4000009c; +clear_super_wdt_reset_flag = 0x400000a0; +disable_default_watchdog = 0x400000a4; +send_packet = 0x400000a8; +recv_packet = 0x400000ac; +GetUartDevice = 0x400000b0; +UartDwnLdProc = 0x400000b4; +Uart_Init = 0x400000b8; +ets_set_user_start = 0x400000bc; +/* Data (.data, .bss, .rodata) */ +ets_rom_layout_p = 0x3ff1fffc; +ets_ops_table_ptr = 0x3fcdfffc; + + +/*************************************** + Group miniz + ***************************************/ + +/* Functions */ +mz_adler32 = 0x400000c0; +mz_crc32 = 0x400000c4; +mz_free = 0x400000c8; +tdefl_compress = 0x400000cc; +tdefl_compress_buffer = 0x400000d0; +tdefl_compress_mem_to_heap = 0x400000d4; +tdefl_compress_mem_to_mem = 0x400000d8; +tdefl_compress_mem_to_output = 0x400000dc; +tdefl_get_adler32 = 0x400000e0; +tdefl_get_prev_return_status = 0x400000e4; +tdefl_init = 0x400000e8; +tdefl_write_image_to_png_file_in_memory = 0x400000ec; +tdefl_write_image_to_png_file_in_memory_ex = 0x400000f0; +tinfl_decompress = 0x400000f4; +tinfl_decompress_mem_to_callback = 0x400000f8; +tinfl_decompress_mem_to_heap = 0x400000fc; +tinfl_decompress_mem_to_mem = 0x40000100; + + +/*************************************** + Group tjpgd + ***************************************/ + +/* Functions */ +PROVIDE( jd_prepare = 0x40000104 ); +PROVIDE( jd_decomp = 0x40000108 ); + + +/*************************************** + Group spiflash_legacy + ***************************************/ + +/* Functions */ +PROVIDE( esp_rom_spiflash_wait_idle = 0x4000010c ); +PROVIDE( esp_rom_spiflash_write_encrypted = 0x40000110 ); +PROVIDE( esp_rom_spiflash_write_encrypted_dest = 0x40000114 ); +PROVIDE( esp_rom_spiflash_write_encrypted_enable = 0x40000118 ); +PROVIDE( esp_rom_spiflash_write_encrypted_disable = 0x4000011c ); +PROVIDE( esp_rom_spiflash_erase_chip = 0x40000120 ); +PROVIDE( esp_rom_spiflash_erase_block = 0x40000124 ); +PROVIDE( esp_rom_spiflash_erase_sector = 0x40000128 ); +PROVIDE( esp_rom_spiflash_write = 0x4000012c ); +PROVIDE( esp_rom_spiflash_read = 0x40000130 ); +PROVIDE( esp_rom_spiflash_config_param = 0x40000134 ); +PROVIDE( esp_rom_spiflash_read_user_cmd = 0x40000138 ); +PROVIDE( esp_rom_spiflash_select_qio_pins = 0x4000013c ); +PROVIDE( esp_rom_spiflash_unlock = 0x40000140 ); +PROVIDE( esp_rom_spi_flash_auto_sus_res = 0x40000144 ); +PROVIDE( esp_rom_spi_flash_send_resume = 0x40000148 ); +PROVIDE( esp_rom_spi_flash_update_id = 0x4000014c ); +PROVIDE( esp_rom_spiflash_config_clk = 0x40000150 ); +PROVIDE( esp_rom_spiflash_config_readmode = 0x40000154 ); +PROVIDE( esp_rom_spiflash_read_status = 0x40000158 ); +PROVIDE( esp_rom_spiflash_read_statushigh = 0x4000015c ); +PROVIDE( esp_rom_spiflash_write_status = 0x40000160 ); +PROVIDE( esp_rom_spiflash_attach = 0x40000164 ); +PROVIDE( spi_flash_get_chip_size = 0x40000168 ); +PROVIDE( spi_flash_guard_set = 0x4000016c ); +PROVIDE( spi_flash_guard_get = 0x40000170 ); +PROVIDE( spi_flash_write_config_set = 0x40000174 ); +PROVIDE( spi_flash_write_config_get = 0x40000178 ); +PROVIDE( spi_flash_safe_write_address_func_set = 0x4000017c ); +PROVIDE( spi_flash_unlock = 0x40000180 ); +PROVIDE( spi_flash_erase_range = 0x40000184 ); +PROVIDE( spi_flash_erase_sector = 0x40000188 ); +PROVIDE( spi_flash_write = 0x4000018c ); +PROVIDE( spi_flash_read = 0x40000190 ); +PROVIDE( spi_flash_write_encrypted = 0x40000194 ); +PROVIDE( spi_flash_read_encrypted = 0x40000198 ); +PROVIDE( spi_flash_mmap_os_func_set = 0x4000019c ); +PROVIDE( spi_flash_mmap_page_num_init = 0x400001a0 ); +PROVIDE( spi_flash_mmap = 0x400001a4 ); +PROVIDE( spi_flash_mmap_pages = 0x400001a8 ); +PROVIDE( spi_flash_munmap = 0x400001ac ); +PROVIDE( spi_flash_mmap_dump = 0x400001b0 ); +PROVIDE( spi_flash_check_and_flush_cache = 0x400001b4 ); +PROVIDE( spi_flash_mmap_get_free_pages = 0x400001b8 ); +PROVIDE( spi_flash_cache2phys = 0x400001bc ); +PROVIDE( spi_flash_phys2cache = 0x400001c0 ); +PROVIDE( spi_flash_disable_cache = 0x400001c4 ); +PROVIDE( spi_flash_restore_cache = 0x400001c8 ); +PROVIDE( spi_flash_cache_enabled = 0x400001cc ); +PROVIDE( spi_flash_enable_cache = 0x400001d0 ); +PROVIDE( spi_cache_mode_switch = 0x400001d4 ); +PROVIDE( spi_common_set_dummy_output = 0x400001d8 ); +PROVIDE( spi_common_set_flash_cs_timing = 0x400001dc ); +PROVIDE( esp_enable_cache_flash_wrap = 0x400001e0 ); +PROVIDE( SPIEraseArea = 0x400001e4 ); +PROVIDE( SPILock = 0x400001e8 ); +PROVIDE( SPIMasterReadModeCnfig = 0x400001ec ); +PROVIDE( SPI_Common_Command = 0x400001f0 ); +PROVIDE( SPI_WakeUp = 0x400001f4 ); +PROVIDE( SPI_block_erase = 0x400001f8 ); +PROVIDE( SPI_chip_erase = 0x400001fc ); +PROVIDE( SPI_init = 0x40000200 ); +PROVIDE( SPI_page_program = 0x40000204 ); +PROVIDE( SPI_read_data = 0x40000208 ); +PROVIDE( SPI_sector_erase = 0x4000020c ); +PROVIDE( SPI_write_enable = 0x40000210 ); +PROVIDE( SelectSpiFunction = 0x40000214 ); +PROVIDE( SetSpiDrvs = 0x40000218 ); +PROVIDE( Wait_SPI_Idle = 0x4000021c ); +PROVIDE( spi_dummy_len_fix = 0x40000220 ); +PROVIDE( Disable_QMode = 0x40000224 ); +PROVIDE( Enable_QMode = 0x40000228 ); +/* Data (.data, .bss, .rodata) */ +PROVIDE( rom_spiflash_legacy_funcs = 0x3fcdfff4 ); +PROVIDE( rom_spiflash_legacy_data = 0x3fcdfff0 ); +PROVIDE( g_flash_guard_ops = 0x3fcdfff8 ); + + +/*************************************** + Group spi_flash_hal + ***************************************/ + +/* Functions */ +PROVIDE( spi_flash_hal_poll_cmd_done = 0x4000022c ); +PROVIDE( spi_flash_hal_device_config = 0x40000230 ); +PROVIDE( spi_flash_hal_configure_host_io_mode = 0x40000234 ); +PROVIDE( spi_flash_hal_common_command = 0x40000238 ); +PROVIDE( spi_flash_hal_read = 0x4000023c ); +PROVIDE( spi_flash_hal_erase_chip = 0x40000240 ); +PROVIDE( spi_flash_hal_erase_sector = 0x40000244 ); +PROVIDE( spi_flash_hal_erase_block = 0x40000248 ); +PROVIDE( spi_flash_hal_program_page = 0x4000024c ); +PROVIDE( spi_flash_hal_set_write_protect = 0x40000250 ); +PROVIDE( spi_flash_hal_host_idle = 0x40000254 ); + + +/*************************************** + Group spi_flash_chips + ***************************************/ + +/* Functions */ +PROVIDE( spi_flash_chip_generic_probe = 0x40000258 ); +PROVIDE( spi_flash_chip_generic_detect_size = 0x4000025c ); +PROVIDE( spi_flash_chip_generic_write = 0x40000260 ); +PROVIDE( spi_flash_chip_generic_write_encrypted = 0x40000264 ); +PROVIDE( spi_flash_chip_generic_set_write_protect = 0x40000268 ); +PROVIDE( spi_flash_common_write_status_16b_wrsr = 0x4000026c ); +PROVIDE( spi_flash_chip_generic_reset = 0x40000270 ); +PROVIDE( spi_flash_chip_generic_erase_chip = 0x40000274 ); +PROVIDE( spi_flash_chip_generic_erase_sector = 0x40000278 ); +PROVIDE( spi_flash_chip_generic_erase_block = 0x4000027c ); +PROVIDE( spi_flash_chip_generic_page_program = 0x40000280 ); +PROVIDE( spi_flash_chip_generic_get_write_protect = 0x40000284 ); +PROVIDE( spi_flash_common_read_status_16b_rdsr_rdsr2 = 0x40000288 ); +PROVIDE( spi_flash_chip_generic_read_reg = 0x4000028c ); +PROVIDE( spi_flash_chip_generic_yield = 0x40000290 ); +PROVIDE( spi_flash_generic_wait_host_idle = 0x40000294 ); +PROVIDE( spi_flash_chip_generic_wait_idle = 0x40000298 ); +PROVIDE( spi_flash_chip_generic_config_host_io_mode = 0x4000029c ); +PROVIDE( spi_flash_chip_generic_read = 0x400002a0 ); +PROVIDE( spi_flash_common_read_status_8b_rdsr2 = 0x400002a4 ); +PROVIDE( spi_flash_chip_generic_get_io_mode = 0x400002a8 ); +PROVIDE( spi_flash_common_read_status_8b_rdsr = 0x400002ac ); +PROVIDE( spi_flash_common_write_status_8b_wrsr = 0x400002b0 ); +PROVIDE( spi_flash_common_write_status_8b_wrsr2 = 0x400002b4 ); +PROVIDE( spi_flash_common_set_io_mode = 0x400002b8 ); +PROVIDE( spi_flash_chip_generic_set_io_mode = 0x400002bc ); +PROVIDE( spi_flash_chip_gd_get_io_mode = 0x400002c0 ); +PROVIDE( spi_flash_chip_gd_probe = 0x400002c4 ); +PROVIDE( spi_flash_chip_gd_set_io_mode = 0x400002c8 ); +/* Data (.data, .bss, .rodata) */ +PROVIDE( spi_flash_chip_generic_config_data = 0x3fcdffec ); + + +/*************************************** + Group memspi_host + ***************************************/ + +/* Functions */ +PROVIDE( memspi_host_read_id_hs = 0x400002cc ); +PROVIDE( memspi_host_read_status_hs = 0x400002d0 ); +PROVIDE( memspi_host_flush_cache = 0x400002d4 ); +PROVIDE( memspi_host_erase_chip = 0x400002d8 ); +PROVIDE( memspi_host_erase_sector = 0x400002dc ); +PROVIDE( memspi_host_erase_block = 0x400002e0 ); +PROVIDE( memspi_host_program_page = 0x400002e4 ); +PROVIDE( memspi_host_read = 0x400002e8 ); +PROVIDE( memspi_host_set_write_protect = 0x400002ec ); +PROVIDE( memspi_host_set_max_read_len = 0x400002f0 ); +PROVIDE( memspi_host_read_data_slicer = 0x400002f4 ); +PROVIDE( memspi_host_write_data_slicer = 0x400002f8 ); + + +/*************************************** + Group esp_flash + ***************************************/ + +/* Functions */ +PROVIDE( esp_flash_chip_driver_initialized = 0x400002fc ); +PROVIDE( esp_flash_read_id = 0x40000300 ); +PROVIDE( esp_flash_get_size = 0x40000304 ); +PROVIDE( esp_flash_erase_chip = 0x40000308 ); +PROVIDE( rom_esp_flash_erase_region = 0x4000030c ); +PROVIDE( esp_flash_get_chip_write_protect = 0x40000310 ); +PROVIDE( esp_flash_set_chip_write_protect = 0x40000314 ); +PROVIDE( esp_flash_get_protectable_regions = 0x40000318 ); +PROVIDE( esp_flash_get_protected_region = 0x4000031c ); +PROVIDE( esp_flash_set_protected_region = 0x40000320 ); +PROVIDE( esp_flash_read = 0x40000324 ); +PROVIDE( esp_flash_write = 0x40000328 ); +PROVIDE( esp_flash_write_encrypted = 0x4000032c ); +PROVIDE( esp_flash_read_encrypted = 0x40000330 ); +PROVIDE( esp_flash_get_io_mode = 0x40000334 ); +PROVIDE( esp_flash_set_io_mode = 0x40000338 ); +PROVIDE( spi_flash_boot_attach = 0x4000033c ); +PROVIDE( spi_flash_dump_counters = 0x40000340 ); +PROVIDE( spi_flash_get_counters = 0x40000344 ); +PROVIDE( spi_flash_op_counters_config = 0x40000348 ); +PROVIDE( spi_flash_reset_counters = 0x4000034c ); +/* Data (.data, .bss, .rodata) */ +PROVIDE( esp_flash_default_chip = 0x3fcdffe8 ); +PROVIDE( esp_flash_api_funcs = 0x3fcdffe4 ); + + +/*************************************** + Group cache + ***************************************/ + +/* Functions */ +PROVIDE( Cache_Get_ICache_Line_Size = 0x400004b0 ); +PROVIDE( Cache_Get_Mode = 0x400004b4 ); +PROVIDE( Cache_Address_Through_IBus = 0x400004b8 ); +PROVIDE( Cache_Address_Through_DBus = 0x400004bc ); +PROVIDE( Cache_Set_Default_Mode = 0x400004c0 ); +PROVIDE( Cache_Enable_Defalut_ICache_Mode = 0x400004c4 ); +PROVIDE( ROM_Boot_Cache_Init = 0x400004c8 ); +PROVIDE( Cache_Invalidate_ICache_Items = 0x400004cc ); +PROVIDE( Cache_Op_Addr = 0x400004d0 ); +PROVIDE( Cache_Invalidate_Addr = 0x400004d4 ); +PROVIDE( Cache_Invalidate_ICache_All = 0x400004d8 ); +PROVIDE( Cache_Mask_All = 0x400004dc ); +PROVIDE( Cache_UnMask_Dram0 = 0x400004e0 ); +PROVIDE( Cache_Suspend_ICache_Autoload = 0x400004e4 ); +PROVIDE( Cache_Resume_ICache_Autoload = 0x400004e8 ); +PROVIDE( Cache_Start_ICache_Preload = 0x400004ec ); +PROVIDE( Cache_ICache_Preload_Done = 0x400004f0 ); +PROVIDE( Cache_End_ICache_Preload = 0x400004f4 ); +PROVIDE( Cache_Config_ICache_Autoload = 0x400004f8 ); +PROVIDE( Cache_Enable_ICache_Autoload = 0x400004fc ); +PROVIDE( Cache_Disable_ICache_Autoload = 0x40000500 ); +PROVIDE( Cache_Enable_ICache_PreLock = 0x40000504 ); +PROVIDE( Cache_Disable_ICache_PreLock = 0x40000508 ); +PROVIDE( Cache_Lock_ICache_Items = 0x4000050c ); +PROVIDE( Cache_Unlock_ICache_Items = 0x40000510 ); +PROVIDE( Cache_Lock_Addr = 0x40000514 ); +PROVIDE( Cache_Unlock_Addr = 0x40000518 ); +PROVIDE( Cache_Disable_ICache = 0x4000051c ); +PROVIDE( Cache_Enable_ICache = 0x40000520 ); +PROVIDE( Cache_Suspend_ICache = 0x40000524 ); +PROVIDE( Cache_Resume_ICache = 0x40000528 ); +PROVIDE( Cache_Freeze_ICache_Enable = 0x4000052c ); +PROVIDE( Cache_Freeze_ICache_Disable = 0x40000530 ); +PROVIDE( Cache_Pms_Lock = 0x40000534 ); +PROVIDE( Cache_Ibus_Pms_Set_Addr = 0x40000538 ); +PROVIDE( Cache_Ibus_Pms_Set_Attr = 0x4000053c ); +PROVIDE( Cache_Dbus_Pms_Set_Addr = 0x40000540 ); +PROVIDE( Cache_Dbus_Pms_Set_Attr = 0x40000544 ); +PROVIDE( Cache_Set_IDROM_MMU_Size = 0x40000548 ); +PROVIDE( Cache_Get_IROM_MMU_End = 0x4000054c ); +PROVIDE( Cache_Get_DROM_MMU_End = 0x40000550 ); +PROVIDE( Cache_Owner_Init = 0x40000554 ); +PROVIDE( Cache_Occupy_ICache_MEMORY = 0x40000558 ); +PROVIDE( Cache_MMU_Init = 0x4000055c ); +PROVIDE( Cache_Ibus_MMU_Set = 0x40000560 ); +PROVIDE( Cache_Dbus_MMU_Set = 0x40000564 ); +PROVIDE( Cache_Count_Flash_Pages = 0x40000568 ); +PROVIDE( Cache_Travel_Tag_Memory = 0x4000056c ); +PROVIDE( Cache_Get_Virtual_Addr = 0x40000570 ); +PROVIDE( Cache_Get_Memory_BaseAddr = 0x40000574 ); +PROVIDE( Cache_Get_Memory_Addr = 0x40000578 ); +PROVIDE( Cache_Get_Memory_value = 0x4000057c ); +/* Data (.data, .bss, .rodata) */ +PROVIDE( rom_cache_op_cb = 0x3fcdffd8 ); +PROVIDE( rom_cache_internal_table_ptr = 0x3fcdffd4 ); + + +/*************************************** + Group clock + ***************************************/ + +/* Functions */ +ets_get_apb_freq = 0x40000580; +ets_get_cpu_frequency = 0x40000584; +ets_update_cpu_frequency = 0x40000588; +ets_get_printf_channel = 0x4000058c; +ets_get_xtal_div = 0x40000590; +ets_set_xtal_div = 0x40000594; +ets_get_xtal_freq = 0x40000598; + + +/*************************************** + Group gpio + ***************************************/ + +/* Functions */ +gpio_input_get = 0x4000059c; +gpio_matrix_in = 0x400005a0; +gpio_matrix_out = 0x400005a4; +gpio_output_disable = 0x400005a8; +gpio_output_enable = 0x400005ac; +gpio_output_set = 0x400005b0; +gpio_pad_hold = 0x400005b4; +gpio_pad_input_disable = 0x400005b8; +gpio_pad_input_enable = 0x400005bc; +gpio_pad_pulldown = 0x400005c0; +gpio_pad_pullup = 0x400005c4; +gpio_pad_select_gpio = 0x400005c8; +gpio_pad_set_drv = 0x400005cc; +gpio_pad_unhold = 0x400005d0; +gpio_pin_wakeup_disable = 0x400005d4; +gpio_pin_wakeup_enable = 0x400005d8; +gpio_bypass_matrix_in = 0x400005dc; + + +/*************************************** + Group interrupts + ***************************************/ + +/* Functions */ +esprv_intc_int_set_priority = 0x400005e0; +esprv_intc_int_set_threshold = 0x400005e4; +esprv_intc_int_enable = 0x400005e8; +esprv_intc_int_disable = 0x400005ec; +esprv_intc_int_set_type = 0x400005f0; +intr_matrix_set = 0x400005f4; +ets_intr_lock = 0x400005f8; +ets_intr_unlock = 0x400005fc; +PROVIDE( intr_handler_set = 0x40000600 ); +ets_isr_attach = 0x40000604; +ets_isr_mask = 0x40000608; +ets_isr_unmask = 0x4000060c; + + +/*************************************** + Group crypto + ***************************************/ + +/* Functions */ +md5_vector = 0x40000610; +MD5Init = 0x40000614; +MD5Update = 0x40000618; +MD5Final = 0x4000061c; +hmac_md5_vector = 0x40000620; +hmac_md5 = 0x40000624; +crc32_le = 0x40000628; +crc32_be = 0x4000062c; +crc16_le = 0x40000630; +crc16_be = 0x40000634; +crc8_le = 0x40000638; +crc8_be = 0x4000063c; +esp_crc8 = 0x40000640; +ets_sha_enable = 0x40000644; +ets_sha_disable = 0x40000648; +ets_sha_get_state = 0x4000064c; +ets_sha_init = 0x40000650; +ets_sha_process = 0x40000654; +ets_sha_starts = 0x40000658; +ets_sha_update = 0x4000065c; +ets_sha_finish = 0x40000660; +ets_sha_clone = 0x40000664; +ets_hmac_enable = 0x40000668; +ets_hmac_disable = 0x4000066c; +ets_hmac_calculate_message = 0x40000670; +ets_hmac_calculate_downstream = 0x40000674; +ets_hmac_invalidate_downstream = 0x40000678; +ets_jtag_enable_temporarily = 0x4000067c; +ets_aes_enable = 0x40000680; +ets_aes_disable = 0x40000684; +ets_aes_setkey = 0x40000688; +ets_aes_block = 0x4000068c; +ets_bigint_enable = 0x40000690; +ets_bigint_disable = 0x40000694; +ets_bigint_multiply = 0x40000698; +ets_bigint_modmult = 0x4000069c; +ets_bigint_modexp = 0x400006a0; +ets_bigint_wait_finish = 0x400006a4; +ets_bigint_getz = 0x400006a8; +ets_ds_enable = 0x400006ac; +ets_ds_disable = 0x400006b0; +ets_ds_start_sign = 0x400006b4; +ets_ds_is_busy = 0x400006b8; +ets_ds_finish_sign = 0x400006bc; +ets_ds_encrypt_params = 0x400006c0; +ets_aes_setkey_dec = 0x400006c4; +ets_aes_setkey_enc = 0x400006c8; +ets_mgf1_sha256 = 0x400006cc; + + +/*************************************** + Group efuse + ***************************************/ + +/* Functions */ +ets_efuse_read = 0x400006d0; +ets_efuse_program = 0x400006d4; +ets_efuse_clear_program_registers = 0x400006d8; +ets_efuse_write_key = 0x400006dc; +ets_efuse_get_read_register_address = 0x400006e0; +ets_efuse_get_key_purpose = 0x400006e4; +ets_efuse_key_block_unused = 0x400006e8; +ets_efuse_find_unused_key_block = 0x400006ec; +ets_efuse_rs_calculate = 0x400006f0; +ets_efuse_count_unused_key_blocks = 0x400006f4; +ets_efuse_secure_boot_enabled = 0x400006f8; +ets_efuse_secure_boot_aggressive_revoke_enabled = 0x400006fc; +ets_efuse_cache_encryption_enabled = 0x40000700; +ets_efuse_download_modes_disabled = 0x40000704; +ets_efuse_find_purpose = 0x40000708; +ets_efuse_flash_opi_5pads_power_sel_vddspi = 0x4000070c; +ets_efuse_force_send_resume = 0x40000710; +ets_efuse_get_flash_delay_us = 0x40000714; +ets_efuse_get_mac = 0x40000718; +ets_efuse_get_spiconfig = 0x4000071c; +ets_efuse_usb_print_is_disabled = 0x40000720; +/*ets_efuse_get_uart_print_channel = 0x40000724;*/ +ets_efuse_usb_serial_jtag_print_is_disabled = 0x40000724; +ets_efuse_get_uart_print_control = 0x40000728; +ets_efuse_get_wp_pad = 0x4000072c; +ets_efuse_legacy_spi_boot_mode_disabled = 0x40000730; +ets_efuse_security_download_modes_enabled = 0x40000734; +ets_efuse_set_timing = 0x40000738; +ets_efuse_jtag_disabled = 0x4000073c; +ets_efuse_usb_download_mode_disabled = 0x40000740; +ets_efuse_usb_module_disabled = 0x40000744; +ets_efuse_usb_device_disabled = 0x40000748; + + +/*************************************** + Group secureboot + ***************************************/ + +/* Functions */ +ets_emsa_pss_verify = 0x4000074c; +ets_rsa_pss_verify = 0x40000750; +ets_secure_boot_verify_bootloader_with_keys = 0x40000754; +ets_secure_boot_verify_signature = 0x40000758; +ets_secure_boot_read_key_digests = 0x4000075c; +ets_secure_boot_revoke_public_key_digest = 0x40000760; + + +/*************************************** + Group usb_uart + ***************************************/ + +/* Functions */ +PROVIDE( usb_uart_rx_one_char = 0x400008cc ); +PROVIDE( usb_uart_rx_one_char_block = 0x400008d0 ); +PROVIDE( usb_uart_tx_flush = 0x400008d4 ); +PROVIDE( usb_uart_tx_one_char = 0x400008d8 ); +/* Data (.data, .bss, .rodata) */ +PROVIDE( g_uart_print = 0x3fcdffd1 ); +PROVIDE( g_usb_print = 0x3fcdffd0 ); + + +/*************************************** + Group bluetooth + ***************************************/ + +/* Functions */ +bt_rf_coex_get_dft_cfg = 0x400008dc; +bt_rf_coex_hooks_p_set = 0x400008e0; +btdm_con_maxevtime_cal_impl = 0x400008e4; +btdm_controller_get_compile_version_impl = 0x400008e8; +btdm_controller_rom_data_init = 0x400008ec; +btdm_dis_privacy_err_report_impl = 0x400008f0; +btdm_disable_adv_delay_impl = 0x400008f4; +btdm_enable_scan_continue_impl = 0x400008f8; +btdm_enable_scan_forever_impl = 0x400008fc; +btdm_get_power_state_impl = 0x40000900; +btdm_get_prevent_sleep_flag_impl = 0x40000904; +btdm_power_state_active_impl = 0x40000908; +btdm_switch_phy_coded_impl = 0x4000090c; +hci_acl_data_handler = 0x40000910; +hci_disconnect_cmd_handler = 0x40000914; +hci_le_con_upd_cmd_handler = 0x40000918; +hci_le_ltk_req_neg_reply_cmd_handler = 0x4000091c; +hci_le_ltk_req_reply_cmd_handler = 0x40000920; +hci_le_rd_chnl_map_cmd_handler = 0x40000924; +hci_le_rd_phy_cmd_handler = 0x40000928; +hci_le_rd_rem_feats_cmd_handler = 0x4000092c; +hci_le_rem_con_param_req_neg_reply_cmd_handler = 0x40000930; +hci_le_rem_con_param_req_reply_cmd_handler = 0x40000934; +hci_le_set_data_len_cmd_handler = 0x40000938; +hci_le_set_phy_cmd_handler = 0x4000093c; +hci_le_start_enc_cmd_handler = 0x40000940; +hci_rd_auth_payl_to_cmd_handler = 0x40000944; +hci_rd_rem_ver_info_cmd_handler = 0x40000948; +hci_rd_rssi_cmd_handler = 0x4000094c; +hci_rd_tx_pwr_lvl_cmd_handler = 0x40000950; +hci_vs_set_pref_slave_evt_dur_cmd_handler = 0x40000954; +hci_vs_set_pref_slave_latency_cmd_handler = 0x40000958; +hci_wr_auth_payl_to_cmd_handler = 0x4000095c; +ll_channel_map_ind_handler = 0x40000960; +ll_connection_param_req_handler = 0x40000964; +ll_connection_param_rsp_handler = 0x40000968; +ll_connection_update_ind_handler = 0x4000096c; +ll_enc_req_handler = 0x40000970; +ll_enc_rsp_handler = 0x40000974; +ll_feature_req_handler = 0x40000978; +ll_feature_rsp_handler = 0x4000097c; +ll_length_req_handler = 0x40000980; +ll_length_rsp_handler = 0x40000984; +ll_min_used_channels_ind_handler = 0x40000988; +ll_pause_enc_req_handler = 0x4000098c; +ll_pause_enc_rsp_handler = 0x40000990; +ll_phy_req_handler = 0x40000994; +ll_phy_rsp_handler = 0x40000998; +ll_phy_update_ind_handler = 0x4000099c; +ll_ping_req_handler = 0x400009a0; +ll_ping_rsp_handler = 0x400009a4; +ll_slave_feature_req_handler = 0x400009a8; +ll_start_enc_req_handler = 0x400009ac; +ll_start_enc_rsp_handler = 0x400009b0; +ll_terminate_ind_handler = 0x400009b4; +ll_version_ind_handler = 0x400009b8; +llc_auth_payl_nearly_to_handler = 0x400009bc; +llc_auth_payl_real_to_handler = 0x400009c0; +llc_encrypt_ind_handler = 0x400009c4; +llc_hci_command_handler_wrapper = 0x400009c8; +llc_ll_connection_param_req_pdu_send = 0x400009cc; +llc_ll_connection_param_rsp_pdu_send = 0x400009d0; +llc_ll_connection_update_ind_pdu_send = 0x400009d4; +llc_ll_enc_req_pdu_send = 0x400009d8; +llc_ll_enc_rsp_pdu_send = 0x400009dc; +llc_ll_feature_req_pdu_send = 0x400009e0; +llc_ll_feature_rsp_pdu_send = 0x400009e4; +llc_ll_length_req_pdu_send = 0x400009e8; +llc_ll_length_rsp_pdu_send = 0x400009ec; +llc_ll_pause_enc_req_pdu_send = 0x400009f0; +llc_ll_pause_enc_rsp_pdu_send = 0x400009f4; +llc_ll_phy_req_pdu_send = 0x400009f8; +llc_ll_phy_rsp_pdu_send = 0x400009fc; +llc_ll_ping_req_pdu_send = 0x40000a00; +llc_ll_ping_rsp_pdu_send = 0x40000a04; +llc_ll_start_enc_req_pdu_send = 0x40000a08; +llc_ll_start_enc_rsp_pdu_send = 0x40000a0c; +llc_ll_terminate_ind_pdu_send = 0x40000a10; +llc_ll_unknown_rsp_pdu_send = 0x40000a14; +llc_llcp_ch_map_update_ind_pdu_send = 0x40000a18; +llc_llcp_phy_upd_ind_pdu_send = 0x40000a1c; +llc_llcp_version_ind_pdu_send = 0x40000a20; +llc_op_ch_map_upd_ind_handler = 0x40000a24; +llc_op_con_upd_ind_handler = 0x40000a28; +llc_op_disconnect_ind_handler = 0x40000a2c; +llc_op_dl_upd_ind_handler = 0x40000a30; +llc_op_encrypt_ind_handler = 0x40000a34; +llc_op_feats_exch_ind_handler = 0x40000a38; +llc_op_le_ping_ind_handler = 0x40000a3c; +llc_op_phy_upd_ind_handler = 0x40000a40; +llc_op_ver_exch_ind_handler = 0x40000a44; +llc_stopped_ind_handler = 0x40000a48; +lld_acl_rx_ind_handler = 0x40000a4c; +lld_acl_tx_cfm_handler = 0x40000a50; +lld_adv_end_ind_handler = 0x40000a54; +lld_adv_rep_ind_handler = 0x40000a58; +lld_ch_map_upd_cfm_handler = 0x40000a5c; +lld_con_estab_ind_handler = 0x40000a60; +lld_con_evt_sd_evt_time_set = 0x40000a64; +lld_con_offset_upd_ind_handler = 0x40000a68; +lld_con_param_upd_cfm_handler = 0x40000a6c; +lld_disc_ind_handler = 0x40000a70; +lld_init_end_ind_handler = 0x40000a74; +lld_llcp_rx_ind_handler_wrapper = 0x40000a78; +lld_llcp_tx_cfm_handler = 0x40000a7c; +lld_per_adv_end_ind_handler = 0x40000a80; +lld_per_adv_rep_ind_handler = 0x40000a84; +lld_per_adv_rx_end_ind_handler = 0x40000a88; +lld_phy_coded_500k_get = 0x40000a8c; +lld_phy_upd_cfm_handler = 0x40000a90; +lld_scan_end_ind_handler = 0x40000a94; +lld_scan_req_ind_handler = 0x40000a98; +lld_sync_start_req_handler = 0x40000a9c; +lld_test_end_ind_handler = 0x40000aa0; +lld_update_rxbuf_handler = 0x40000aa4; +llm_ch_map_update_ind_handler = 0x40000aa8; +llm_hci_command_handler_wrapper = 0x40000aac; +llm_scan_period_to_handler = 0x40000ab0; +r_Add2SelfBigHex256 = 0x40000ab4; +r_AddBigHex256 = 0x40000ab8; +r_AddBigHexModP256 = 0x40000abc; +r_AddP256 = 0x40000ac0; +r_AddPdiv2_256 = 0x40000ac4; +r_GF_Jacobian_Point_Addition256 = 0x40000ac8; +r_GF_Jacobian_Point_Double256 = 0x40000acc; +r_GF_Point_Jacobian_To_Affine256 = 0x40000ad0; +r_MultiplyBigHexByUint32_256 = 0x40000ad4; +r_MultiplyBigHexModP256 = 0x40000ad8; +r_MultiplyByU16ModP256 = 0x40000adc; +r_SubtractBigHex256 = 0x40000ae0; +r_SubtractBigHexMod256 = 0x40000ae4; +r_SubtractBigHexUint32_256 = 0x40000ae8; +r_SubtractFromSelfBigHex256 = 0x40000aec; +r_SubtractFromSelfBigHexSign256 = 0x40000af0; +r_aes_alloc = 0x40000af4; +r_aes_ccm_continue = 0x40000af8; +r_aes_ccm_process_e = 0x40000afc; +r_aes_ccm_xor_128_lsb = 0x40000b00; +r_aes_ccm_xor_128_msb = 0x40000b04; +r_aes_cmac_continue = 0x40000b08; +r_aes_cmac_start = 0x40000b0c; +r_aes_k1_continue = 0x40000b10; +r_aes_k2_continue = 0x40000b14; +r_aes_k3_continue = 0x40000b18; +r_aes_k4_continue = 0x40000b1c; +r_aes_shift_left_128 = 0x40000b20; +r_aes_start = 0x40000b24; +r_aes_xor_128 = 0x40000b28; +r_assert_err = 0x40000b2c; +r_assert_param = 0x40000b30; +r_assert_warn = 0x40000b34; +r_bigHexInversion256 = 0x40000b38; +r_ble_sw_cca_check_isr = 0x40000b3c; +r_ble_util_buf_acl_tx_alloc = 0x40000b40; +r_ble_util_buf_acl_tx_elt_get = 0x40000b44; +r_ble_util_buf_acl_tx_free = 0x40000b48; +r_ble_util_buf_acl_tx_free_in_isr = 0x40000b4c; +r_ble_util_buf_adv_tx_alloc = 0x40000b50; +r_ble_util_buf_adv_tx_free = 0x40000b54; +r_ble_util_buf_adv_tx_free_in_isr = 0x40000b58; +r_ble_util_buf_env_deinit = 0x40000b5c; +r_ble_util_buf_env_init = 0x40000b60; +r_ble_util_buf_get_rx_buf_nb = 0x40000b64; +r_ble_util_buf_get_rx_buf_size = 0x40000b68; +r_ble_util_buf_llcp_tx_alloc = 0x40000b6c; +r_ble_util_buf_llcp_tx_free = 0x40000b70; +r_ble_util_buf_rx_alloc = 0x40000b74; +r_ble_util_buf_rx_alloc_in_isr = 0x40000b78; +r_ble_util_buf_rx_free = 0x40000b7c; +r_ble_util_buf_rx_free_in_isr = 0x40000b80; +r_ble_util_buf_set_rx_buf_nb = 0x40000b84; +r_ble_util_buf_set_rx_buf_size = 0x40000b88; +r_ble_util_data_rx_buf_reset = 0x40000b8c; +r_bt_bb_get_intr_mask = 0x40000b90; +r_bt_bb_intr_clear = 0x40000b94; +r_bt_bb_intr_mask_set = 0x40000b98; +r_bt_rf_coex_cfg_set = 0x40000ba0; +r_bt_rf_coex_conn_dynamic_pti_en_get = 0x40000ba4; +r_bt_rf_coex_ext_adv_dynamic_pti_en_get = 0x40000bac; +r_bt_rf_coex_ext_scan_dynamic_pti_en_get = 0x40000bb0; +r_bt_rf_coex_legacy_adv_dynamic_pti_en_get = 0x40000bb4; +r_bt_rf_coex_per_adv_dynamic_pti_en_get = 0x40000bb8; +r_bt_rf_coex_pti_table_get = 0x40000bbc; +r_bt_rf_coex_st_param_get = 0x40000bc0; +r_bt_rf_coex_st_param_set = 0x40000bc4; +r_bt_rf_coex_sync_scan_dynamic_pti_en_get = 0x40000bc8; +r_bt_rma_apply_rule_cs_fmt = 0x40000bcc; +r_bt_rma_apply_rule_cs_idx = 0x40000bd0; +r_bt_rma_configure = 0x40000bd4; +r_bt_rma_deregister_rule_cs_fmt = 0x40000bd8; +r_bt_rma_deregister_rule_cs_idx = 0x40000bdc; +r_bt_rma_get_ant_by_act = 0x40000be0; +r_bt_rma_init = 0x40000be4; +r_bt_rma_register_rule_cs_fmt = 0x40000be8; +r_bt_rma_register_rule_cs_idx = 0x40000bec; +r_bt_rtp_apply_rule_cs_fmt = 0x40000bf0; +r_bt_rtp_apply_rule_cs_idx = 0x40000bf4; +r_bt_rtp_deregister_rule_cs_fmt = 0x40000bf8; +r_bt_rtp_deregister_rule_cs_idx = 0x40000bfc; +r_bt_rtp_init = 0x40000c04; +r_bt_rtp_register_rule_cs_fmt = 0x40000c08; +r_bt_rtp_register_rule_cs_idx = 0x40000c0c; +r_btdm_isr = 0x40000c10; +r_cali_phase_match_p = 0x40000c20; +r_cmp_abs_time = 0x40000c24; +r_cmp_dest_id = 0x40000c28; +r_cmp_timer_id = 0x40000c2c; +r_co_bdaddr_compare = 0x40000c30; +r_co_ble_pkt_dur_in_us = 0x40000c34; +r_co_list_extract = 0x40000c38; +r_co_list_extract_after = 0x40000c3c; +r_co_list_extract_sublist = 0x40000c40; +r_co_list_find = 0x40000c44; +r_co_list_init = 0x40000c48; +r_co_list_insert_after = 0x40000c4c; +r_co_list_insert_before = 0x40000c50; +r_co_list_merge = 0x40000c54; +r_co_list_pool_init = 0x40000c58; +r_co_list_pop_front = 0x40000c5c; +r_co_list_push_back = 0x40000c60; +r_co_list_push_back_sublist = 0x40000c64; +r_co_list_push_front = 0x40000c68; +r_co_list_size = 0x40000c6c; +r_co_nb_good_le_channels = 0x40000c70; +r_co_util_pack = 0x40000c74; +r_co_util_read_array_size = 0x40000c78; +r_co_util_unpack = 0x40000c7c; +r_dbg_env_deinit = 0x40000c80; +r_dbg_env_init = 0x40000c84; +r_dbg_platform_reset_complete = 0x40000c88; +r_dl_upd_proc_start = 0x40000c8c; +r_dump_data = 0x40000c90; +r_ecc_abort_key256_generation = 0x40000c94; +r_ecc_gen_new_public_key = 0x40000c98; +r_ecc_gen_new_secret_key = 0x40000c9c; +r_ecc_generate_key256 = 0x40000ca0; +r_ecc_get_debug_Keys = 0x40000ca4; +r_ecc_init = 0x40000ca8; +r_ecc_is_valid_point = 0x40000cac; +r_ecc_multiplication_event_handler = 0x40000cb0; +r_ecc_point_multiplication_win_256 = 0x40000cb4; +r_emi_alloc_em_mapping_by_offset = 0x40000cb8; +r_emi_base_reg_lut_show = 0x40000cbc; +r_emi_em_base_reg_show = 0x40000cc0; +r_emi_free_em_mapping_by_offset = 0x40000cc4; +r_emi_get_em_mapping_idx_by_offset = 0x40000cc8; +r_emi_get_mem_addr_by_offset = 0x40000ccc; +r_emi_overwrite_em_mapping_by_offset = 0x40000cd0; +r_esp_vendor_hci_command_handler = 0x40000cd4; +r_get_stack_usage = 0x40000cd8; +r_h4tl_acl_hdr_rx_evt_handler = 0x40000cdc; +r_h4tl_cmd_hdr_rx_evt_handler = 0x40000ce0; +r_h4tl_cmd_pld_rx_evt_handler = 0x40000ce4; +r_h4tl_eif_io_event_post = 0x40000ce8; +r_h4tl_eif_register = 0x40000cec; +r_h4tl_init = 0x40000cf0; +r_h4tl_out_of_sync = 0x40000cf4; +r_h4tl_out_of_sync_check = 0x40000cf8; +r_h4tl_read_hdr = 0x40000cfc; +r_h4tl_read_next_out_of_sync = 0x40000d00; +r_h4tl_read_payl = 0x40000d04; +r_h4tl_read_start = 0x40000d08; +r_h4tl_rx_acl_hdr_extract = 0x40000d0c; +r_h4tl_rx_cmd_hdr_extract = 0x40000d10; +r_h4tl_rx_done = 0x40000d14; +r_h4tl_start = 0x40000d18; +r_h4tl_stop = 0x40000d1c; +r_h4tl_tx_done = 0x40000d20; +r_h4tl_tx_evt_handler = 0x40000d24; +r_h4tl_write = 0x40000d28; +r_hci_acl_tx_data_alloc = 0x40000d2c; +r_hci_acl_tx_data_received = 0x40000d30; +r_hci_basic_cmd_send_2_controller = 0x40000d34; +r_hci_ble_adv_report_filter_check = 0x40000d38; +r_hci_ble_adv_report_tx_check = 0x40000d3c; +r_hci_ble_conhdl_register = 0x40000d40; +r_hci_ble_conhdl_unregister = 0x40000d44; +r_hci_build_acl_data = 0x40000d48; +r_hci_build_cc_evt = 0x40000d4c; +r_hci_build_cs_evt = 0x40000d50; +r_hci_build_evt = 0x40000d54; +r_hci_build_le_evt = 0x40000d58; +r_hci_cmd_get_max_param_size = 0x40000d5c; +r_hci_cmd_received = 0x40000d60; +r_hci_cmd_reject = 0x40000d64; +r_hci_evt_mask_check = 0x40000d68; +r_hci_evt_mask_set = 0x40000d6c; +r_hci_fc_acl_buf_size_set = 0x40000d70; +r_hci_fc_acl_en = 0x40000d74; +r_hci_fc_acl_packet_sent = 0x40000d78; +r_hci_fc_check_host_available_nb_acl_packets = 0x40000d7c; +r_hci_fc_host_nb_acl_pkts_complete = 0x40000d80; +r_hci_fc_init = 0x40000d84; +r_hci_look_for_cmd_desc = 0x40000d88; +r_hci_look_for_evt_desc = 0x40000d8c; +r_hci_look_for_le_evt_desc = 0x40000d90; +r_hci_look_for_le_evt_desc_esp = 0x40000d94; +r_hci_pack_bytes = 0x40000d98; +r_hci_send_2_controller = 0x40000da0; +r_hci_send_2_host = 0x40000da4; +r_hci_tl_c2h_data_flow_on = 0x40000da8; +r_hci_tl_cmd_hdr_rx_evt_handler = 0x40000dac; +r_hci_tl_cmd_pld_rx_evt_handler = 0x40000db0; +r_hci_tl_get_pkt = 0x40000db4; +r_hci_tl_hci_pkt_handler = 0x40000db8; +r_hci_tl_hci_tx_done_evt_handler = 0x40000dbc; +r_hci_tl_inc_nb_h2c_cmd_pkts = 0x40000dc0; +r_hci_tl_save_pkt = 0x40000dc4; +r_hci_tl_send = 0x40000dc8; +r_hci_tx_done = 0x40000dcc; +r_hci_tx_start = 0x40000dd0; +r_hci_tx_trigger = 0x40000dd4; +r_isValidSecretKey_256 = 0x40000dd8; +r_ke_check_malloc = 0x40000ddc; +r_ke_event_callback_set = 0x40000de0; +r_ke_event_clear = 0x40000de4; +r_ke_event_flush = 0x40000de8; +r_ke_event_get = 0x40000dec; +r_ke_event_get_all = 0x40000df0; +r_ke_event_init = 0x40000df4; +r_ke_event_schedule = 0x40000df8; +r_ke_event_set = 0x40000dfc; +r_ke_flush = 0x40000e00; +r_ke_free = 0x40000e04; +r_ke_handler_search = 0x40000e08; +r_ke_init = 0x40000e0c; +r_ke_is_free = 0x40000e10; +r_ke_malloc = 0x40000e14; +r_ke_mem_init = 0x40000e18; +r_ke_mem_is_empty = 0x40000e1c; +r_ke_mem_is_in_heap = 0x40000e20; +r_ke_msg_alloc = 0x40000e24; +r_ke_msg_dest_id_get = 0x40000e28; +r_ke_msg_discard = 0x40000e2c; +r_ke_msg_forward = 0x40000e30; +r_ke_msg_forward_new_id = 0x40000e34; +r_ke_msg_free = 0x40000e38; +r_ke_msg_in_queue = 0x40000e3c; +r_ke_msg_save = 0x40000e40; +r_ke_msg_send = 0x40000e44; +r_ke_msg_send_basic = 0x40000e48; +r_ke_msg_src_id_get = 0x40000e4c; +r_ke_queue_extract = 0x40000e50; +r_ke_queue_insert = 0x40000e54; +r_ke_sleep_check = 0x40000e58; +r_ke_state_get = 0x40000e5c; +r_ke_state_set = 0x40000e60; +r_ke_task_check = 0x40000e64; +r_ke_task_create = 0x40000e68; +r_ke_task_delete = 0x40000e6c; +r_ke_task_handler_get = 0x40000e70; +r_ke_task_init = 0x40000e74; +r_ke_task_msg_flush = 0x40000e78; +r_ke_task_saved_update = 0x40000e7c; +r_ke_time = 0x40000e84; +r_ke_time_cmp = 0x40000e88; +r_ke_time_past = 0x40000e8c; +r_ke_timer_active = 0x40000e90; +r_ke_timer_adjust_all = 0x40000e94; +r_ke_timer_clear = 0x40000e98; +r_ke_timer_init = 0x40000e9c; +r_ke_timer_schedule = 0x40000ea0; +r_ke_timer_set = 0x40000ea4; +r_led_init = 0x40000ea8; +r_led_set_all = 0x40000eac; +r_llc_aes_res_cb = 0x40000eb0; +r_llc_ch_map_up_proc_err_cb = 0x40000eb4; +r_llc_cleanup = 0x40000eb8; +r_llc_cmd_cmp_send = 0x40000ebc; +r_llc_cmd_stat_send = 0x40000ec0; +r_llc_con_move_cbk = 0x40000ec4; +r_llc_con_plan_set_update = 0x40000ec8; +r_llc_con_upd_param_in_range = 0x40000ecc; +r_llc_disconnect = 0x40000ed0; +r_llc_disconnect_end = 0x40000ed4; +r_llc_disconnect_proc_continue = 0x40000ed8; +r_llc_disconnect_proc_err_cb = 0x40000edc; +r_llc_dl_chg_check = 0x40000ee0; +r_llc_dle_proc_err_cb = 0x40000ee4; +r_llc_feats_exch_proc_err_cb = 0x40000ee8; +r_llc_hci_cmd_handler_tab_p_get = 0x40000eec; +r_llc_hci_con_param_req_evt_send = 0x40000ef4; +r_llc_hci_con_upd_info_send = 0x40000ef8; +r_llc_hci_disconnected_dis = 0x40000efc; +r_llc_hci_dl_upd_info_send = 0x40000f00; +r_llc_hci_enc_evt_send = 0x40000f04; +r_llc_hci_feats_info_send = 0x40000f08; +r_llc_hci_le_phy_upd_cmp_evt_send = 0x40000f0c; +r_llc_hci_ltk_request_evt_send = 0x40000f10; +r_llc_hci_nb_cmp_pkts_evt_send = 0x40000f14; +r_llc_hci_version_info_send = 0x40000f18; +r_llc_init_term_proc = 0x40000f1c; +r_llc_iv_skd_rand_gen = 0x40000f20; +r_llc_le_ping_proc_continue = 0x40000f24; +r_llc_le_ping_proc_err_cb = 0x40000f28; +/* r_llc_le_ping_restart = 0x40000f2c; */ +r_llc_le_ping_set = 0x40000f30; +r_llc_ll_pause_enc_rsp_ack_handler = 0x40000f34; +r_llc_ll_reject_ind_ack_handler = 0x40000f38; +r_llc_ll_reject_ind_pdu_send = 0x40000f3c; +r_llc_ll_start_enc_rsp_ack_handler = 0x40000f40; +r_llc_ll_terminate_ind_ack = 0x40000f44; +r_llc_ll_unknown_ind_handler = 0x40000f48; +r_llc_llcp_send = 0x40000f4c; +r_llc_llcp_state_set = 0x40000f50; +r_llc_llcp_trans_timer_set = 0x40000f54; +r_llc_llcp_tx_check = 0x40000f58; +r_llc_loc_ch_map_proc_continue = 0x40000f5c; +r_llc_loc_con_upd_proc_err_cb = 0x40000f64; +r_llc_loc_dl_upd_proc_continue = 0x40000f68; +r_llc_loc_encrypt_proc_continue = 0x40000f6c; +r_llc_loc_encrypt_proc_err_cb = 0x40000f70; +r_llc_loc_feats_exch_proc_continue = 0x40000f74; +r_llc_loc_phy_upd_proc_err_cb = 0x40000f7c; +r_llc_msg_handler_tab_p_get = 0x40000f80; +r_llc_pref_param_compute = 0x40000f84; +r_llc_proc_collision_check = 0x40000f88; +r_llc_proc_err_ind = 0x40000f8c; +r_llc_proc_get = 0x40000f90; +r_llc_proc_id_get = 0x40000f94; +r_llc_proc_reg = 0x40000f98; +r_llc_proc_state_get = 0x40000f9c; +r_llc_proc_state_set = 0x40000fa0; +r_llc_proc_timer_pause_set = 0x40000fa4; +r_llc_proc_timer_set = 0x40000fa8; +r_llc_proc_unreg = 0x40000fac; +r_llc_rem_ch_map_proc_continue = 0x40000fb0; +r_llc_rem_con_upd_proc_err_cb = 0x40000fb8; +r_llc_rem_dl_upd_proc = 0x40000fbc; +r_llc_rem_encrypt_proc_continue = 0x40000fc0; +r_llc_rem_encrypt_proc_err_cb = 0x40000fc4; +r_llc_rem_phy_upd_proc_continue = 0x40000fc8; +r_llc_rem_phy_upd_proc_err_cb = 0x40000fcc; +r_llc_role_get = 0x40000fd0; +r_llc_sk_gen = 0x40000fd4; +r_llc_start = 0x40000fd8; +r_llc_stop = 0x40000fdc; +r_llc_ver_exch_loc_proc_continue = 0x40000fe0; +r_llc_ver_proc_err_cb = 0x40000fe4; +r_llcp_pdu_handler_tab_p_get = 0x40000fe8; +r_lld_aa_gen = 0x40000fec; +r_lld_adv_adv_data_set = 0x40000ff0; +r_lld_adv_adv_data_update = 0x40000ff4; +r_lld_adv_aux_ch_idx_set = 0x40000ff8; +r_lld_adv_aux_evt_canceled_cbk = 0x40000ffc; +r_lld_adv_aux_evt_start_cbk = 0x40001000; +r_lld_adv_coex_check_ext_adv_synced = 0x40001004; +r_lld_adv_coex_env_reset = 0x40001008; +r_lld_adv_duration_update = 0x4000100c; +r_lld_adv_dynamic_pti_process = 0x40001010; +r_lld_adv_end = 0x40001014; +r_lld_adv_evt_canceled_cbk = 0x40001018; +r_lld_adv_evt_start_cbk = 0x4000101c; +r_lld_adv_ext_chain_construct = 0x40001020; +r_lld_adv_ext_pkt_prepare = 0x40001024; +r_lld_adv_frm_cbk = 0x40001028; +r_lld_adv_frm_isr = 0x4000102c; +r_lld_adv_frm_skip_isr = 0x40001030; +r_lld_adv_init = 0x40001034; +r_lld_adv_pkt_rx = 0x40001038; +r_lld_adv_pkt_rx_connect_ind = 0x4000103c; +r_lld_adv_pkt_rx_send_scan_req_evt = 0x40001040; +r_lld_adv_rand_addr_update = 0x40001044; +r_lld_adv_restart = 0x40001048; +r_lld_adv_scan_rsp_data_set = 0x4000104c; +r_lld_adv_scan_rsp_data_update = 0x40001050; +r_lld_adv_set_tx_power = 0x40001054; +r_lld_adv_start = 0x40001058; +r_lld_adv_stop = 0x4000105c; +r_lld_adv_sync_info_set = 0x40001060; +r_lld_adv_sync_info_update = 0x40001064; +r_lld_calc_aux_rx = 0x40001068; +r_lld_cca_alloc = 0x4000106c; +r_lld_cca_data_reset = 0x40001070; +r_lld_cca_free = 0x40001074; +r_lld_ch_assess_data_get = 0x40001078; +r_lld_ch_idx_get = 0x4000107c; +r_lld_ch_map_set = 0x40001080; +r_lld_channel_assess = 0x40001084; +r_lld_con_activity_act_offset_compute = 0x40001088; +r_lld_con_activity_offset_compute = 0x4000108c; +r_lld_con_ch_map_update = 0x40001090; +r_lld_con_cleanup = 0x40001094; +r_lld_con_current_tx_power_get = 0x40001098; +r_lld_con_data_flow_set = 0x4000109c; +r_lld_con_data_len_update = 0x400010a0; +r_lld_con_data_tx = 0x400010a4; +r_lld_con_enc_key_load = 0x400010a8; +r_lld_con_event_counter_get = 0x400010ac; +r_lld_con_evt_canceled_cbk = 0x400010b0; +r_lld_con_evt_duration_min_get = 0x400010b4; +r_lld_con_evt_max_eff_time_cal = 0x400010b8; +r_lld_con_evt_sd_evt_time_get = 0x400010bc; +r_lld_con_evt_start_cbk = 0x400010c0; +r_lld_con_evt_time_update = 0x400010c4; +r_lld_con_free_all_tx_buf = 0x400010c8; +r_lld_con_frm_cbk = 0x400010cc; +r_lld_con_frm_isr = 0x400010d0; +r_lld_con_frm_skip_isr = 0x400010d4; +r_lld_con_init = 0x400010d8; +r_lld_con_llcp_tx = 0x400010dc; +r_lld_con_max_lat_calc = 0x400010e0; +r_lld_con_offset_get = 0x400010e4; +r_lld_con_param_update = 0x400010e8; +r_lld_con_phys_update = 0x400010ec; +r_lld_con_pref_slave_evt_dur_set = 0x400010f0; +r_lld_con_pref_slave_latency_set = 0x400010f4; +r_lld_con_rssi_get = 0x400010f8; +r_lld_con_rx = 0x400010fc; +/* r_lld_con_rx_channel_assess = 0x40001100; */ +r_lld_con_rx_enc = 0x40001104; +r_lld_con_rx_isr = 0x40001108; +r_lld_con_rx_link_info_check = 0x4000110c; +r_lld_con_rx_llcp_check = 0x40001110; +r_lld_con_rx_sync_time_update = 0x40001114; +r_lld_con_set_tx_power = 0x4000111c; +r_lld_con_start = 0x40001120; +r_lld_con_tx = 0x40001128; +r_lld_con_tx_enc = 0x4000112c; +r_lld_con_tx_isr = 0x40001130; +r_lld_con_tx_len_update = 0x40001134; +r_lld_con_tx_len_update_for_intv = 0x40001138; +r_lld_con_tx_len_update_for_rate = 0x4000113c; +r_lld_con_tx_prog = 0x40001140; +r_lld_conn_dynamic_pti_process = 0x40001144; +r_lld_continue_scan_rx_isr_end_process = 0x40001148; +r_lld_ext_scan_dynamic_pti_process = 0x4000114c; +r_lld_hw_cca_end_isr = 0x40001150; +r_lld_hw_cca_evt_handler = 0x40001154; +r_lld_hw_cca_isr = 0x40001158; +r_lld_init_cal_anchor_point = 0x4000115c; +r_lld_init_compute_winoffset = 0x40001160; +r_lld_init_connect_req_pack = 0x40001164; +r_lld_init_end = 0x40001168; +r_lld_init_evt_canceled_cbk = 0x4000116c; +r_lld_init_evt_start_cbk = 0x40001170; +r_lld_init_frm_cbk = 0x40001174; +r_lld_init_frm_eof_isr = 0x40001178; +r_lld_init_frm_skip_isr = 0x4000117c; +r_lld_init_init = 0x40001180; +r_lld_init_process_pkt_rx = 0x40001184; +r_lld_init_process_pkt_rx_adv_ext_ind = 0x40001188; +r_lld_init_process_pkt_rx_adv_ind_or_direct_ind = 0x4000118c; +r_lld_init_process_pkt_rx_aux_connect_rsp = 0x40001190; +r_lld_init_process_pkt_tx = 0x40001194; +r_lld_init_process_pkt_tx_cal_con_timestamp = 0x40001198; +r_lld_init_sched = 0x4000119c; +r_lld_init_set_tx_power = 0x400011a0; +r_lld_init_start = 0x400011a4; +r_lld_init_stop = 0x400011a8; +r_lld_instant_proc_end = 0x400011ac; +r_lld_per_adv_ch_map_update = 0x400011b4; +r_lld_per_adv_chain_construct = 0x400011b8; +r_lld_per_adv_cleanup = 0x400011bc; +r_lld_per_adv_coex_env_reset = 0x400011c0; +r_lld_per_adv_data_set = 0x400011c4; +r_lld_per_adv_data_update = 0x400011c8; +r_lld_per_adv_dynamic_pti_process = 0x400011cc; +r_lld_per_adv_evt_canceled_cbk = 0x400011d0; +r_lld_per_adv_evt_start_cbk = 0x400011d4; +r_lld_per_adv_ext_pkt_prepare = 0x400011d8; +r_lld_per_adv_frm_cbk = 0x400011dc; +r_lld_per_adv_frm_isr = 0x400011e0; +r_lld_per_adv_frm_skip_isr = 0x400011e4; +r_lld_per_adv_init = 0x400011e8; +r_lld_per_adv_init_info_get = 0x400011ec; +r_lld_per_adv_list_add = 0x400011f0; +r_lld_per_adv_list_rem = 0x400011f4; +r_lld_per_adv_set_tx_power = 0x400011fc; +r_lld_per_adv_start = 0x40001200; +r_lld_per_adv_stop = 0x40001204; +r_lld_per_adv_sync_info_get = 0x40001208; +r_lld_process_cca_data = 0x4000120c; +r_lld_ral_search = 0x40001210; +r_lld_read_clock = 0x40001214; +r_lld_res_list_add = 0x40001218; +r_lld_res_list_is_empty = 0x40001220; +r_lld_res_list_local_rpa_get = 0x40001224; +r_lld_res_list_peer_rpa_get = 0x40001228; +r_lld_res_list_peer_update = 0x4000122c; +/* r_lld_res_list_priv_mode_update = 0x40001230; */ +r_lld_reset_reg = 0x40001238; +r_lld_rpa_renew = 0x4000123c; +r_lld_rpa_renew_evt_canceled_cbk = 0x40001240; +r_lld_rpa_renew_evt_start_cbk = 0x40001244; +r_lld_rpa_renew_instant_cbk = 0x40001248; +r_lld_rxdesc_check = 0x4000124c; +r_lld_rxdesc_free = 0x40001250; +r_lld_scan_create_sync = 0x40001254; +r_lld_scan_create_sync_cancel = 0x40001258; +r_lld_scan_end = 0x4000125c; +r_lld_scan_evt_canceled_cbk = 0x40001260; +r_lld_scan_evt_start_cbk = 0x40001264; +r_lld_scan_frm_cbk = 0x40001268; +r_lld_scan_frm_eof_isr = 0x4000126c; +r_lld_scan_frm_rx_isr = 0x40001270; +r_lld_scan_frm_skip_isr = 0x40001274; +r_lld_scan_init = 0x40001278; +r_lld_scan_params_update = 0x4000127c; +r_lld_scan_process_pkt_rx_aux_adv_ind = 0x40001288; +r_lld_scan_process_pkt_rx_aux_chain_ind = 0x4000128c; +r_lld_scan_process_pkt_rx_aux_scan_rsp = 0x40001290; +r_lld_scan_process_pkt_rx_ext_adv = 0x40001294; +r_lld_scan_process_pkt_rx_ext_adv_ind = 0x40001298; +r_lld_scan_process_pkt_rx_legacy_adv = 0x4000129c; +r_lld_scan_restart = 0x400012a0; +r_lld_scan_sched = 0x400012a4; +r_lld_scan_set_tx_power = 0x400012a8; +r_lld_scan_start = 0x400012ac; +r_lld_scan_stop = 0x400012b0; +r_lld_scan_sync_accept = 0x400012b4; +r_lld_scan_sync_info_unpack = 0x400012b8; +r_lld_scan_trunc_ind = 0x400012bc; +r_lld_sw_cca_evt_handler = 0x400012c0; +r_lld_sw_cca_isr = 0x400012c4; +r_lld_sync_ch_map_update = 0x400012c8; +r_lld_sync_cleanup = 0x400012cc; +r_lld_sync_evt_canceled_cbk = 0x400012d0; +r_lld_sync_evt_start_cbk = 0x400012d4; +r_lld_sync_frm_cbk = 0x400012d8; +r_lld_sync_frm_eof_isr = 0x400012dc; +r_lld_sync_frm_rx_isr = 0x400012e0; +r_lld_sync_frm_skip_isr = 0x400012e4; +r_lld_sync_init = 0x400012e8; +r_lld_sync_process_pkt_rx = 0x400012ec; +r_lld_sync_process_pkt_rx_aux_sync_ind = 0x400012f0; +r_lld_sync_process_pkt_rx_pkt_check = 0x400012f4; +r_lld_sync_scan_dynamic_pti_process = 0x400012f8; +r_lld_sync_sched = 0x400012fc; +r_lld_sync_start = 0x40001300; +r_lld_sync_stop = 0x40001304; +r_lld_sync_trunc_ind = 0x40001308; +r_lld_test_cleanup = 0x4000130c; +r_lld_test_evt_canceled_cbk = 0x40001310; +r_lld_test_evt_start_cbk = 0x40001314; +r_lld_test_freq2chnl = 0x40001318; +r_lld_test_frm_cbk = 0x4000131c; +r_lld_test_frm_isr = 0x40001320; +r_lld_test_init = 0x40001324; +r_lld_test_rx_isr = 0x40001328; +r_lld_test_set_tx_power = 0x4000132c; +r_lld_test_start = 0x40001330; +/* r_lld_test_stop = 0x40001334; */ +r_lld_update_rxbuf = 0x40001338; +r_lld_update_rxbuf_isr = 0x4000133c; +r_lld_white_list_add = 0x40001340; +r_lld_white_list_rem = 0x40001344; +r_llm_activity_free_get = 0x40001348; +r_llm_activity_free_set = 0x4000134c; +r_llm_activity_syncing_get = 0x40001350; +r_llm_adv_con_len_check = 0x40001354; +r_llm_adv_hdl_to_id = 0x40001358; +r_llm_adv_rep_flow_control_check = 0x4000135c; +r_llm_adv_rep_flow_control_update = 0x40001360; +r_llm_adv_reports_list_check = 0x40001364; +r_llm_adv_set_all_release = 0x40001368; +r_llm_adv_set_dft_params = 0x4000136c; +r_llm_adv_set_release = 0x40001370; +r_llm_aes_res_cb = 0x40001374; +r_llm_ble_update_adv_flow_control = 0x40001378; +r_llm_ch_map_update = 0x4000137c; +r_llm_cmd_cmp_send = 0x40001380; +r_llm_cmd_stat_send = 0x40001384; +r_llm_dev_list_empty_entry = 0x40001388; +r_llm_dev_list_search = 0x4000138c; +r_llm_env_adv_dup_filt_deinit = 0x40001390; +r_llm_env_adv_dup_filt_init = 0x40001394; +r_llm_init_ble_adv_report_flow_contol = 0x40001398; +r_llm_is_dev_connected = 0x4000139c; +r_llm_is_dev_synced = 0x400013a0; +r_llm_is_non_con_act_ongoing_check = 0x400013a4; +r_llm_is_wl_accessible = 0x400013a8; +r_llm_le_evt_mask_check = 0x400013ac; +r_llm_link_disc = 0x400013b4; +r_llm_master_ch_map_get = 0x400013b8; +r_llm_msg_handler_tab_p_get = 0x400013bc; +r_llm_no_activity = 0x400013c0; +r_llm_per_adv_slot_dur = 0x400013c4; +r_llm_plan_elt_get = 0x400013c8; +r_llm_rx_path_comp_get = 0x400013cc; +r_llm_scan_start = 0x400013d0; +r_llm_scan_sync_acad_attach = 0x400013d4; +r_llm_scan_sync_acad_detach = 0x400013d8; +r_llm_send_adv_lost_event_to_host = 0x400013dc; +r_llm_tx_path_comp_get = 0x400013e0; +r_misc_deinit = 0x400013e4; +r_misc_free_em_buf_in_isr = 0x400013e8; +r_misc_init = 0x400013ec; +r_misc_msg_handler_tab_p_get = 0x400013f0; +r_notEqual256 = 0x400013f4; +r_phy_upd_proc_start = 0x400013f8; +r_platform_reset = 0x400013fc; +r_rf_em_init = 0x40001404; +r_rf_force_agc_enable = 0x40001408; +r_rf_reg_rd = 0x4000140c; +r_rf_reg_wr = 0x40001410; +r_rf_reset = 0x40001414; +r_rf_rssi_convert = 0x40001418; +r_rf_rw_v9_le_disable = 0x4000141c; +r_rf_rw_v9_le_enable = 0x40001420; +r_rf_sleep = 0x40001424; +r_rf_util_cs_fmt_convert = 0x40001430; +r_rw_crypto_aes_ccm = 0x40001434; +r_rw_crypto_aes_encrypt = 0x40001438; +r_rw_crypto_aes_init = 0x4000143c; +r_rw_crypto_aes_k1 = 0x40001440; +r_rw_crypto_aes_k2 = 0x40001444; +r_rw_crypto_aes_k3 = 0x40001448; +r_rw_crypto_aes_k4 = 0x4000144c; +r_rw_crypto_aes_rand = 0x40001450; +r_rw_crypto_aes_result_handler = 0x40001454; +r_rw_crypto_aes_s1 = 0x40001458; +r_rw_cryto_aes_cmac = 0x4000145c; +r_rw_v9_init_em_radio_table = 0x40001460; +r_rwble_sleep_enter = 0x40001468; +r_rwble_sleep_wakeup_end = 0x4000146c; +/* r_rwbtdm_isr_wrapper = 0x40001470; */ +r_rwip_active_check = 0x40001474; +r_rwip_aes_encrypt = 0x40001478; +/* r_rwip_assert = 0x4000147c; */ +r_rwip_crypt_evt_handler = 0x40001480; +r_rwip_crypt_isr_handler = 0x40001484; +r_rwip_eif_get = 0x40001488; +r_rwip_half_slot_2_lpcycles = 0x4000148c; +r_rwip_hus_2_lpcycles = 0x40001490; +r_rwip_isr = 0x40001494; +r_rwip_lpcycles_2_hus = 0x40001498; +r_rwip_prevent_sleep_clear = 0x4000149c; +r_rwip_prevent_sleep_set = 0x400014a0; +r_rwip_schedule = 0x400014a4; +r_rwip_sleep = 0x400014a8; +r_rwip_sw_int_handler = 0x400014ac; +r_rwip_sw_int_req = 0x400014b0; +r_rwip_time_get = 0x400014b4; +r_rwip_timer_10ms_handler = 0x400014b8; +r_rwip_timer_10ms_set = 0x400014bc; +r_rwip_timer_hs_handler = 0x400014c0; +r_rwip_timer_hs_set = 0x400014c4; +r_rwip_timer_hus_handler = 0x400014c8; +r_rwip_timer_hus_set = 0x400014cc; +r_rwip_wakeup = 0x400014d0; +/* r_rwip_wakeup_end = 0x400014d4; */ +r_rwip_wlcoex_set = 0x400014d8; +r_sch_alarm_clear = 0x400014dc; +r_sch_alarm_init = 0x400014e0; +r_sch_alarm_prog = 0x400014e4; +r_sch_alarm_set = 0x400014e8; +r_sch_alarm_timer_isr = 0x400014ec; +r_sch_arb_conflict_check = 0x400014f0; +r_sch_arb_elt_cancel = 0x400014f4; +r_sch_arb_init = 0x400014fc; +r_sch_arb_insert = 0x40001500; +r_sch_arb_prog_timer = 0x40001504; +r_sch_arb_remove = 0x40001508; +r_sch_arb_sw_isr = 0x4000150c; +r_sch_plan_chk = 0x40001510; +r_sch_plan_clock_wrap_offset_update = 0x40001514; +r_sch_plan_init = 0x40001518; +r_sch_plan_interval_req = 0x4000151c; +r_sch_plan_offset_max_calc = 0x40001520; +r_sch_plan_offset_req = 0x40001524; +r_sch_plan_position_range_compute = 0x40001528; +r_sch_plan_rem = 0x4000152c; +r_sch_plan_req = 0x40001530; +r_sch_prog_init = 0x4000153c; +r_sch_prog_push = 0x40001540; +r_sch_prog_rx_isr = 0x40001544; +r_sch_prog_skip_isr = 0x40001548; +r_sch_prog_tx_isr = 0x4000154c; +r_sch_slice_bg_add = 0x40001550; +r_sch_slice_bg_remove = 0x40001554; +r_sch_slice_compute = 0x40001558; +r_sch_slice_fg_add = 0x4000155c; +r_sch_slice_fg_remove = 0x40001560; +r_sch_slice_init = 0x40001564; +r_sch_slice_per_add = 0x40001568; +r_sch_slice_per_remove = 0x4000156c; +r_sdk_config_get_bt_sleep_enable = 0x40001570; +r_sdk_config_get_hl_derived_opts = 0x40001574; +r_sdk_config_get_opts = 0x40001578; +r_sdk_config_get_priv_opts = 0x4000157c; +r_sdk_config_set_bt_sleep_enable = 0x40001580; +r_sdk_config_set_hl_derived_opts = 0x40001584; +r_sdk_config_set_opts = 0x40001588; +r_specialModP256 = 0x4000158c; +r_unloaded_area_init = 0x40001590; +r_vhci_flow_off = 0x40001594; +r_vhci_flow_on = 0x40001598; +r_vhci_notify_host_send_available = 0x4000159c; +r_vhci_send_to_host = 0x400015a0; +r_vnd_hci_command_handler = 0x400015a4; +r_vshci_init = 0x400015a8; +vnd_hci_command_handler_wrapper = 0x400015ac; +/* Data (.data, .bss, .rodata) */ +bt_rf_coex_cfg_p = 0x3fcdffcc; +bt_rf_coex_hooks_p = 0x3fcdffc8; +btdm_env_p = 0x3fcdffc4; +g_rw_controller_task_handle = 0x3fcdffc0; +g_rw_init_sem = 0x3fcdffbc; +g_rw_schd_queue = 0x3fcdffb8; +lld_init_env = 0x3fcdffb4; +lld_rpa_renew_env = 0x3fcdffb0; +lld_scan_env = 0x3fcdffac; +lld_scan_sync_env = 0x3fcdffa8; +lld_test_env = 0x3fcdffa4; +p_ble_util_buf_env = 0x3fcdffa0; +p_lld_env = 0x3fcdff9c; +p_llm_env = 0x3fcdff98; +r_h4tl_eif_p = 0x3fcdff94; +r_hli_funcs_p = 0x3fcdff90; +r_ip_funcs_p = 0x3fcdff8c; +r_modules_funcs_p = 0x3fcdff88; +r_osi_funcs_p = 0x3fcdff84; +r_plf_funcs_p = 0x3fcdff80; +vhci_env_p = 0x3fcdff7c; +aa_gen = 0x3fcdff78; +aes_env = 0x3fcdff6c; +bt_rf_coex_cfg_cb = 0x3fcdff1c; +btdm_pwr_state = 0x3fcdff18; +btdm_slp_err = 0x3fcdff14; +ecc_env = 0x3fcdff0c; +esp_handler = 0x3fcdff04; +esp_vendor_cmd = 0x3fcdfefc; +g_adv_delay_dis = 0x3fcdfef8; +g_conflict_elt = 0x3fcdfef4; +g_eif_api = 0x3fcdfee4; +g_event_empty = 0x3fcdfed8; +g_llc_state = 0x3fcdfecc; +g_llm_state = 0x3fcdfec8; +g_max_evt_env = 0x3fcdfec4; +g_misc_state = 0x3fcdfec0; +g_rma_rule_db = 0x3fcdfea4; +g_rtp_rule_db = 0x3fcdfe88; +g_scan_forever = 0x3fcdfe85; +g_time_msb = 0x3fcdfe84; +h4tl_env = 0x3fcdfe5c; +hci_env = 0x3fcdfe38; +hci_ext_host = 0x3fcdfe34; +hci_fc_env = 0x3fcdfe2c; +hci_tl_env = 0x3fcdfe00; +ke_env = 0x3fcdfdd0; +ke_event_env = 0x3fcdfd90; +ke_task_env = 0x3fcdfd14; +llc_env = 0x3fcdfcec; +lld_adv_env = 0x3fcdfcc4; +lld_con_env = 0x3fcdfc9c; +lld_exp_sync_pos_tab = 0x3fcdfc94; +lld_per_adv_env = 0x3fcdfc6c; +lld_sync_env = 0x3fcdfc44; +llm_le_adv_flow_env = 0x3fcdfc38; +rw_sleep_enable = 0x3fcdfc34; +rwble_env = 0x3fcdfc2c; +rwip_env = 0x3fcdfc10; +rwip_param = 0x3fcdfc04; +rwip_prog_delay = 0x3fcdfc00; +rwip_rf = 0x3fcdfbc8; +sch_alarm_env = 0x3fcdfbc0; +sch_arb_env = 0x3fcdfbac; +sch_plan_env = 0x3fcdfba4; +sch_prog_env = 0x3fcdfaa0; +sch_slice_env = 0x3fcdfa40; +sch_slice_params = 0x3fcdfa38; +timer_env = 0x3fcdfa30; +unloaded_area = 0x3fcdfa2c; +vshci_state = 0x3fcdfa28; +TASK_DESC_LLC = 0x3fcdfa1c; +TASK_DESC_LLM = 0x3fcdfa10; +TASK_DESC_VSHCI = 0x3fcdfa04; +co_default_bdaddr = 0x3fcdf9fc; +dbg_assert_block = 0x3fcdf9f8; +g_bt_plf_log_level = 0x3fcdf9f4; +hci_cmd_desc_tab_vs_esp = 0x3fcdf9d0; +hci_command_handler_tab_esp = 0x3fcdf9b8; +privacy_en = 0x3fcdf9b4; +sdk_cfg_priv_opts = 0x3fcdf96c; +BasePoint_x_256 = 0x3ff1ffdc; +BasePoint_y_256 = 0x3ff1ffbc; +DebugE256PublicKey_x = 0x3ff1ff9c; +DebugE256PublicKey_y = 0x3ff1ff7c; +DebugE256SecretKey = 0x3ff1ff5c; +ECC_4Win_Look_up_table = 0x3ff1f7a0; +LLM_AA_CT1 = 0x3ff1f79c; +LLM_AA_CT2 = 0x3ff1f798; +RF_TX_PW_CONV_TBL = 0x3ff1f790; +TASK_DESC_MISC = 0x3ff1f784; +adv_evt_prop2type = 0x3ff1f768; +adv_evt_type2prop = 0x3ff1f760; +aes_cmac_zero = 0x3ff1f750; +aes_k2_salt = 0x3ff1f740; +aes_k3_id64 = 0x3ff1f738; +aes_k3_salt = 0x3ff1f728; +aes_k4_id6 = 0x3ff1f724; +aes_k4_salt = 0x3ff1f714; +bigHexP256 = 0x3ff1f6e8; +byte_tx_time = 0x3ff1f6e0; +co_null_bdaddr = 0x3ff1f6d8; +co_phy_mask_to_rate = 0x3ff1f6d0; +co_phy_mask_to_value = 0x3ff1f6c8; +co_phy_to_rate = 0x3ff1f6c4; +co_phy_value_to_mask = 0x3ff1f6c0; +co_rate_to_byte_dur_us = 0x3ff1f6b8; +co_rate_to_phy = 0x3ff1f6b0; +co_rate_to_phy_mask = 0x3ff1f6ac; +co_sca2ppm = 0x3ff1f69c; +coef_B = 0x3ff1f670; +connect_req_dur_tab = 0x3ff1f668; +ecc_Jacobian_InfinityPoint256 = 0x3ff1f5e4; +em_base_reg_lut = 0x3ff1f518; +fixed_tx_time = 0x3ff1f510; +h4tl_msgtype2hdrlen = 0x3ff1f508; +hci_cmd_desc_root_tab = 0x3ff1f4d8; +hci_cmd_desc_tab_ctrl_bb = 0x3ff1f46c; +hci_cmd_desc_tab_info_par = 0x3ff1f43c; +hci_cmd_desc_tab_le = 0x3ff1f0a0; +hci_cmd_desc_tab_lk_ctrl = 0x3ff1f088; +hci_cmd_desc_tab_stat_par = 0x3ff1f07c; +hci_cmd_desc_tab_vs = 0x3ff1f040; +hci_evt_desc_tab = 0x3ff1eff8; +hci_evt_le_desc_tab = 0x3ff1ef58; +hci_evt_le_desc_tab_esp = 0x3ff1ef50; +hci_rsvd_evt_msk = 0x3ff1ef48; +lld_aux_phy_to_rate = 0x3ff1ef44; +lld_init_max_aux_dur_tab = 0x3ff1ef3c; +lld_scan_map_legacy_pdu_to_evt_type = 0x3ff1ef34; +lld_scan_max_aux_dur_tab = 0x3ff1ef2c; +lld_sync_max_aux_dur_tab = 0x3ff1ef24; +llm_local_le_feats = 0x3ff1ef1c; +llm_local_le_states = 0x3ff1ef14; +llm_local_supp_cmds = 0x3ff1eeec; +maxSecretKey_256 = 0x3ff1eecc; +max_data_tx_time = 0x3ff1eec4; +one_bits = 0x3ff1eeb4; +rwip_coex_cfg = 0x3ff1eeac; +rwip_priority = 0x3ff1ee94; +veryBigHexP256 = 0x3ff1ee48; + +/* bluetooth hook funcs */ +r_llc_loc_encrypt_proc_continue_hook = 0x40001c60; +r_llc_loc_phy_upd_proc_continue_hook = 0x40001c64; +r_llc_rem_phy_upd_proc_continue_hook = 0x40001c68; +r_lld_scan_frm_eof_isr_hook = 0x40001c6c; +r_lld_scan_evt_start_cbk_hook = 0x40001c70; +r_lld_scan_process_pkt_rx_ext_adv_hook = 0x40001c78; +r_lld_scan_sched_hook = 0x40001c7c; +r_lld_adv_evt_start_cbk_hook = 0x40001c84; +r_lld_adv_aux_evt_start_cbk_hook = 0x40001c88; +r_lld_adv_frm_isr_hook = 0x40001c8c; +r_lld_adv_start_init_evt_param_hook = 0x40001c90; +r_lld_con_evt_canceled_cbk_hook = 0x40001c94; +r_lld_con_frm_isr_hook = 0x40001c98; +r_lld_con_tx_hook = 0x40001c9c; +r_lld_con_rx_hook = 0x40001ca0; +r_lld_con_evt_start_cbk_hook = 0x40001ca4; +r_lld_con_tx_prog_new_packet_hook = 0x40001cac; +r_lld_init_frm_eof_isr_hook = 0x40001cb0; +r_lld_init_evt_start_cbk_hook = 0x40001cb4; +r_lld_init_sched_hook = 0x40001cbc; +r_lld_init_process_pkt_tx_hook = 0x40001cc0; +r_lld_per_adv_evt_start_cbk_hook = 0x40001cc4; +r_lld_per_adv_frm_isr_hook = 0x40001cc8; +r_lld_per_adv_start_hook = 0x40001ccc; +r_lld_sync_frm_eof_isr_hook = 0x40001cd0; +r_lld_sync_evt_start_cbk_hook = 0x40001cd4; +r_lld_sync_start_hook = 0x40001cd8; +r_lld_sync_process_pkt_rx_pkt_check_hook = 0x40001cdc; +r_sch_arb_insert_hook = 0x40001ce0; +r_sch_plan_offset_req_hook = 0x40001ce4; + +/*************************************** + Group rom_pp + ***************************************/ + +/* Functions */ +esp_pp_rom_version_get = 0x400015b0; +RC_GetBlockAckTime = 0x400015b4; +ebuf_list_remove = 0x400015b8; +/*esf_buf_alloc = 0x400015bc;*/ +GetAccess = 0x400015c8; +hal_mac_is_low_rate_enabled = 0x400015cc; +hal_mac_tx_get_blockack = 0x400015d0; +/* hal_mac_tx_set_ppdu = 0x400015d4;*/ +ic_get_trc = 0x400015d8; +/* ic_mac_deinit = 0x400015dc; */ +ic_mac_init = 0x400015e0; +ic_interface_enabled = 0x400015e4; +is_lmac_idle = 0x400015e8; +/*lmacAdjustTimestamp = 0x400015ec;*/ +lmacDiscardAgedMSDU = 0x400015f0; +/*lmacDiscardMSDU = 0x400015f4;*/ +/*lmacEndFrameExchangeSequence = 0x400015f8;*/ +lmacIsIdle = 0x400015fc; +lmacIsLongFrame = 0x40001600; +/*lmacMSDUAged = 0x40001604;*/ +lmacPostTxComplete = 0x40001608; +lmacProcessAllTxTimeout = 0x4000160c; +lmacProcessCollisions = 0x40001610; +lmacProcessRxSucData = 0x40001614; +lmacReachLongLimit = 0x40001618; +lmacReachShortLimit = 0x4000161c; +lmacRecycleMPDU = 0x40001620; +lmacRxDone = 0x40001624; +/*lmacSetTxFrame = 0x40001628;*/ +/*lmacTxFrame = 0x40001630;*/ +mac_tx_set_duration = 0x40001634; +/* mac_tx_set_htsig = 0x40001638;*/ +mac_tx_set_plcp0 = 0x4000163c; +/* mac_tx_set_plcp1 = 0x40001640;*/ +mac_tx_set_plcp2 = 0x40001644; +/* pm_check_state = 0x40001648; */ +pm_disable_dream_timer = 0x4000164c; +pm_disable_sleep_delay_timer = 0x40001650; +/*pm_dream = 0x40001654;*/ +pm_mac_wakeup = 0x40001658; +pm_mac_sleep = 0x4000165c; +pm_enable_active_timer = 0x40001660; +pm_enable_sleep_delay_timer = 0x40001664; +pm_local_tsf_process = 0x40001668; +pm_set_beacon_filter = 0x4000166c; +pm_is_in_wifi_slice_threshold = 0x40001670; +pm_is_waked = 0x40001674; +pm_keep_alive = 0x40001678; +/* pm_on_beacon_rx = 0x4000167c; */ +pm_on_data_rx = 0x40001680; +pm_on_tbtt = 0x40001684; +/* pm_parse_beacon = 0x40001688;*/ +/* pm_process_tim = 0x4000168c; */ +/*pm_rx_beacon_process = 0x40001690;*/ +/* pm_rx_data_process = 0x40001694; */ +/*pm_sleep = 0x40001698;*/ +pm_sleep_for = 0x4000169c; +/* pm_tbtt_process = 0x400016a0; */ +ppAMPDU2Normal = 0x400016a4; +/*ppAssembleAMPDU = 0x400016a8;*/ +ppCalFrameTimes = 0x400016ac; +ppCalSubFrameLength = 0x400016b0; +/*ppCalTxAMPDULength = 0x400016b4;*/ +ppCheckTxAMPDUlength = 0x400016b8; +ppDequeueRxq_Locked = 0x400016bc; +ppDequeueTxQ = 0x400016c0; +ppEmptyDelimiterLength = 0x400016c4; +ppEnqueueRxq = 0x400016c8; +ppEnqueueTxDone = 0x400016cc; +ppGetTxQFirstAvail_Locked = 0x400016d0; +ppGetTxframe = 0x400016d4; +ppProcessRxPktHdr = 0x400016e0; +ppProcessTxQ = 0x400016e4; +ppRecordBarRRC = 0x400016e8; +lmacRequestTxopQueue = 0x400016ec; +lmacReleaseTxopQueue = 0x400016f0; +ppRecycleAmpdu = 0x400016f4; +ppRecycleRxPkt = 0x400016f8; +ppResortTxAMPDU = 0x400016fc; +ppResumeTxAMPDU = 0x40001700; +/* ppRxFragmentProc = 0x40001704; */ +/* ppRxPkt = 0x40001708; */ +ppRxProtoProc = 0x4000170c; +ppSearchTxQueue = 0x40001710; +ppSearchTxframe = 0x40001714; +ppSelectNextQueue = 0x40001718; +ppSubFromAMPDU = 0x4000171c; +ppTask = 0x40001720; +ppTxPkt = 0x40001724; +ppTxProtoProc = 0x40001728; +ppTxqUpdateBitmap = 0x4000172c; +/*pp_coex_tx_request = 0x40001730;*/ +pp_hdrsize = 0x40001734; +pp_post = 0x40001738; +pp_process_hmac_waiting_txq = 0x4000173c; +rcGetAmpduSched = 0x40001740; +rcUpdateRxDone = 0x40001744; +rc_get_trc = 0x40001748; +rc_get_trc_by_index = 0x4000174c; +rcAmpduLowerRate = 0x40001750; +rcampduuprate = 0x40001754; +rcClearCurAMPDUSched = 0x40001758; +rcClearCurSched = 0x4000175c; +rcClearCurStat = 0x40001760; +rcLowerSched = 0x40001768; +rcSetTxAmpduLimit = 0x4000176c; +/* rcTxUpdatePer = 0x40001770;*/ +rcUpdateAckSnr = 0x40001774; +/*rcUpdateRate = 0x40001778;*/ +/* rcUpdateTxDone = 0x4000177c; */ +rcUpdateTxDoneAmpdu2 = 0x40001780; +rcUpSched = 0x40001784; +rssi_margin = 0x40001788; +rx11NRate2AMPDULimit = 0x4000178c; +TRC_AMPDU_PER_DOWN_THRESHOLD = 0x40001790; +TRC_AMPDU_PER_UP_THRESHOLD = 0x40001794; +trc_calc_duration = 0x40001798; +trc_isTxAmpduOperational = 0x4000179c; +trc_onAmpduOp = 0x400017a0; +TRC_PER_IS_GOOD = 0x400017a4; +trc_SetTxAmpduState = 0x400017a8; +trc_tid_isTxAmpduOperational = 0x400017ac; +trcAmpduSetState = 0x400017b0; +/*wDev_AppendRxBlocks = 0x400017b8;*/ +wDev_DiscardFrame = 0x400017bc; +wDev_GetNoiseFloor = 0x400017c0; +wDev_IndicateAmpdu = 0x400017c4; +/*wDev_IndicateFrame = 0x400017c8;*/ +wdev_bank_store = 0x400017cc; +wdev_bank_load = 0x400017d0; +wdev_mac_reg_load = 0x400017d4; +wdev_mac_reg_store = 0x400017d8; +wdev_mac_special_reg_load = 0x400017dc; +wdev_mac_special_reg_store = 0x400017e0; +wdev_mac_wakeup = 0x400017e4; +wdev_mac_sleep = 0x400017e8; +hal_mac_is_dma_enable = 0x400017ec; +/*wDev_ProcessFiq = 0x400017f0;*/ +/*wDev_ProcessRxSucData = 0x400017f4;*/ +wdevProcessRxSucDataAll = 0x400017f8; +wdev_csi_len_align = 0x400017fc; +ppDequeueTxDone_Locked = 0x40001800; +/*pm_tx_data_done_process = 0x40001808;*/ +config_is_cache_tx_buf_enabled = 0x4000180c; +//ppMapWaitTxq = 0x40001810; +ppProcessWaitingQueue = 0x40001814; +ppDisableQueue = 0x40001818; +pm_allow_tx = 0x4000181c; +/* Data (.data, .bss, .rodata) */ +our_instances_ptr = 0x3ff1ee44; +pTxRx = 0x3fcdf968; +lmacConfMib_ptr = 0x3fcdf964; +our_wait_eb = 0x3fcdf960; +our_tx_eb = 0x3fcdf95c; +pp_wdev_funcs = 0x3fcdf958; +g_osi_funcs_p = 0x3fcdf954; +wDevCtrl_ptr = 0x3fcdf950; +g_wdev_last_desc_reset_ptr = 0x3ff1ee40; +wDevMacSleep_ptr = 0x3fcdf94c; +g_lmac_cnt_ptr = 0x3fcdf948; +our_controls_ptr = 0x3ff1ee3c; +pp_sig_cnt_ptr = 0x3fcdf944; +g_eb_list_desc_ptr = 0x3fcdf940; +s_fragment_ptr = 0x3fcdf93c; +if_ctrl_ptr = 0x3fcdf938; +g_intr_lock_mux = 0x3fcdf934; +g_wifi_global_lock = 0x3fcdf930; +s_wifi_queue = 0x3fcdf92c; +pp_task_hdl = 0x3fcdf928; +s_pp_task_create_sem = 0x3fcdf924; +s_pp_task_del_sem = 0x3fcdf920; +g_wifi_menuconfig_ptr = 0x3fcdf91c; +xphyQueue = 0x3fcdf918; +ap_no_lr_ptr = 0x3fcdf914; +rc11BSchedTbl_ptr = 0x3fcdf910; +rc11NSchedTbl_ptr = 0x3fcdf90c; +rcLoRaSchedTbl_ptr = 0x3fcdf908; +BasicOFDMSched_ptr = 0x3fcdf904; +trc_ctl_ptr = 0x3fcdf900; +g_pm_cnt_ptr = 0x3fcdf8fc; +g_pm_ptr = 0x3fcdf8f8; +g_pm_cfg_ptr = 0x3fcdf8f4; +g_esp_mesh_quick_funcs_ptr = 0x3fcdf8f0; +g_txop_queue_status_ptr = 0x3fcdf8ec; +g_mac_sleep_en_ptr = 0x3fcdf8e8; +g_mesh_is_root_ptr = 0x3fcdf8e4; +g_mesh_topology_ptr = 0x3fcdf8e0; +g_mesh_init_ps_type_ptr = 0x3fcdf8dc; +g_mesh_is_started_ptr = 0x3fcdf8d8; +g_config_func = 0x3fcdf8d4; +g_net80211_tx_func = 0x3fcdf8d0; +g_timer_func = 0x3fcdf8cc; +s_michael_mic_failure_cb = 0x3fcdf8c8; +wifi_sta_rx_probe_req = 0x3fcdf8c4; +g_tx_done_cb_func = 0x3fcdf8c0; +g_per_conn_trc = 0x3fcdf874; +s_encap_amsdu_func = 0x3fcdf870; + + +/*************************************** + Group rom_net80211 + ***************************************/ + +/* Functions */ +esp_net80211_rom_version_get = 0x40001820; +ampdu_dispatch = 0x40001824; +ampdu_dispatch_all = 0x40001828; +ampdu_dispatch_as_many_as_possible = 0x4000182c; +ampdu_dispatch_movement = 0x40001830; +ampdu_dispatch_upto = 0x40001834; +chm_is_at_home_channel = 0x40001838; +cnx_node_is_existing = 0x4000183c; +cnx_node_search = 0x40001840; +ic_ebuf_recycle_rx = 0x40001844; +ic_ebuf_recycle_tx = 0x40001848; +ic_reset_rx_ba = 0x4000184c; +ieee80211_align_eb = 0x40001850; +/*ieee80211_ampdu_reorder = 0x40001854;*/ +ieee80211_ampdu_start_age_timer = 0x40001858; +/*ieee80211_encap_esfbuf = 0x4000185c;*/ +ieee80211_is_tx_allowed = 0x40001860; +ieee80211_output_pending_eb = 0x40001864; +/*ieee80211_output_process = 0x40001868;*/ +ieee80211_set_tx_desc = 0x4000186c; +rom_sta_input = 0x40001870; +wifi_get_macaddr = 0x40001874; +wifi_rf_phy_disable = 0x40001878; +wifi_rf_phy_enable = 0x4000187c; +ic_ebuf_alloc = 0x40001880; +/*ieee80211_classify = 0x40001884;*/ +ieee80211_copy_eb_header = 0x40001888; +ieee80211_recycle_cache_eb = 0x4000188c; +ieee80211_search_node = 0x40001890; +roundup2 = 0x40001894; +ieee80211_crypto_encap = 0x40001898; +/* ieee80211_crypto_decap = 0x4000189c; */ +/* ieee80211_decap = 0x400018a0; */ +ieee80211_set_tx_pti = 0x400018a4; +wifi_is_started = 0x400018a8; +/* Data (.data, .bss, .rodata) */ +net80211_funcs = 0x3fcdf86c; +g_scan = 0x3fcdf868; +g_chm = 0x3fcdf864; +g_ic_ptr = 0x3fcdf860; +g_hmac_cnt_ptr = 0x3fcdf85c; +g_tx_cacheq_ptr = 0x3fcdf858; +s_netstack_free = 0x3fcdf854; +mesh_rxcb = 0x3fcdf850; +sta_rxcb = 0x3fcdf84c; + + +/*************************************** + Group rom_coexist + ***************************************/ + +/* Functions */ +esp_coex_rom_version_get = 0x400018ac; +coex_bt_release = 0x400018b0; +coex_bt_request = 0x400018b4; +coex_core_ble_conn_dyn_prio_get = 0x400018b8; +/*coex_core_event_duration_get = 0x400018bc;*/ +coex_core_pti_get = 0x400018c0; +coex_core_release = 0x400018c4; +coex_core_request = 0x400018c8; +coex_core_status_get = 0x400018cc; +/*coex_core_timer_idx_get = 0x400018d0;*/ +coex_event_duration_get = 0x400018d4; +coex_hw_timer_disable = 0x400018d8; +coex_hw_timer_enable = 0x400018dc; +coex_hw_timer_set = 0x400018e0; +coex_schm_interval_set = 0x400018e4; +coex_schm_lock = 0x400018e8; +coex_schm_unlock = 0x400018ec; +coex_status_get = 0x400018f0; +coex_wifi_release = 0x400018f4; +esp_coex_ble_conn_dynamic_prio_get = 0x400018f8; +/* Data (.data, .bss, .rodata) */ +coex_env_ptr = 0x3fcdf848; +coex_pti_tab_ptr = 0x3fcdf844; +coex_schm_env_ptr = 0x3fcdf840; +coexist_funcs = 0x3fcdf83c; +g_coa_funcs_p = 0x3fcdf838; +g_coex_param_ptr = 0x3fcdf834; + + +/*************************************** + Group rom_phy + ***************************************/ + +/* Functions */ +phy_get_romfuncs = 0x400018fc; +rom_abs_temp = 0x40001900; +rom_bb_bss_cbw40_dig = 0x40001904; +rom_bb_wdg_test_en = 0x40001908; +rom_bb_wdt_get_status = 0x4000190c; +rom_bb_wdt_int_enable = 0x40001910; +rom_bb_wdt_rst_enable = 0x40001914; +rom_bb_wdt_timeout_clear = 0x40001918; +rom_cbw2040_cfg = 0x4000191c; +rom_check_noise_floor = 0x40001920; +rom_chip_i2c_readReg = 0x40001924; +rom_chip_i2c_writeReg = 0x40001928; +rom_correct_rf_ana_gain = 0x4000192c; +rom_dc_iq_est = 0x40001930; +rom_disable_agc = 0x40001934; +rom_en_pwdet = 0x40001938; +rom_enable_agc = 0x4000193c; +rom_get_bbgain_db = 0x40001940; +rom_get_data_sat = 0x40001944; +rom_get_i2c_read_mask = 0x40001948; +rom_get_pwctrl_correct = 0x4000194c; +rom_get_rf_gain_qdb = 0x40001950; +rom_i2c_readReg = 0x40001954; +rom_i2c_readReg_Mask = 0x40001958; +rom_i2c_writeReg = 0x4000195c; +rom_i2c_writeReg_Mask = 0x40001960; +/* rom_index_to_txbbgain = 0x40001964; */ +rom_iq_est_disable = 0x40001968; +rom_iq_est_enable = 0x4000196c; +rom_linear_to_db = 0x40001970; +rom_loopback_mode_en = 0x40001974; +rom_mhz2ieee = 0x40001978; +rom_noise_floor_auto_set = 0x4000197c; +rom_pbus_debugmode = 0x40001980; +rom_pbus_force_mode = 0x40001984; +rom_pbus_force_test = 0x40001988; +rom_pbus_rd = 0x4000198c; +rom_pbus_rd_addr = 0x40001990; +rom_pbus_rd_shift = 0x40001994; +rom_pbus_set_dco = 0x40001998; +rom_pbus_set_rxgain = 0x4000199c; +rom_pbus_workmode = 0x400019a0; +rom_pbus_xpd_rx_off = 0x400019a4; +rom_pbus_xpd_rx_on = 0x400019a8; +rom_pbus_xpd_tx_off = 0x400019ac; +/* rom_pbus_xpd_tx_on = 0x400019b0; */ +rom_phy_byte_to_word = 0x400019b4; +rom_phy_disable_cca = 0x400019b8; +rom_phy_enable_cca = 0x400019bc; +rom_phy_get_noisefloor = 0x400019c0; +rom_phy_get_rx_freq = 0x400019c4; +rom_phy_set_bbfreq_init = 0x400019c8; +rom_pow_usr = 0x400019cc; +rom_pwdet_sar2_init = 0x400019d0; +rom_read_hw_noisefloor = 0x400019d4; +rom_read_sar_dout = 0x400019d8; +rom_set_cal_rxdc = 0x400019dc; +rom_set_chan_cal_interp = 0x400019e0; +rom_set_loopback_gain = 0x400019e4; +rom_set_noise_floor = 0x400019e8; +rom_set_rxclk_en = 0x400019ec; +/* rom_set_tx_dig_gain = 0x400019f0; */ +/* rom_set_txcap_reg = 0x400019f4; */ +rom_set_txclk_en = 0x400019f8; +rom_spur_cal = 0x400019fc; +rom_spur_reg_write_one_tone = 0x40001a00; +rom_target_power_add_backoff = 0x40001a04; +rom_tx_pwctrl_bg_init = 0x40001a08; +/* rom_txbbgain_to_index = 0x40001a0c; */ +rom_wifi_11g_rate_chg = 0x40001a10; +rom_write_gain_mem = 0x40001a14; +chip726_phyrom_version = 0x40001a18; +rom_disable_wifi_agc = 0x40001a1c; +rom_enable_wifi_agc = 0x40001a20; +rom_set_tx_gain_table = 0x40001a24; +rom_bt_index_to_bb = 0x40001a28; +rom_bt_bb_to_index = 0x40001a2c; +rom_wr_bt_tx_atten = 0x40001a30; +rom_wr_bt_tx_gain_mem = 0x40001a34; +rom_spur_coef_cfg = 0x40001a38; +rom_bb_bss_cbw40 = 0x40001a3c; +rom_set_cca = 0x40001a40; +rom_tx_paon_set = 0x40001a44; +rom_i2cmst_reg_init = 0x40001a48; +rom_iq_corr_enable = 0x40001a4c; +rom_fe_reg_init = 0x40001a50; +/* rom_agc_reg_init = 0x40001a54; */ +/* rom_bb_reg_init = 0x40001a58; */ +rom_mac_enable_bb = 0x40001a5c; +rom_bb_wdg_cfg = 0x40001a60; +rom_force_txon = 0x40001a64; +rom_fe_txrx_reset = 0x40001a68; +rom_set_rx_comp = 0x40001a6c; +/* rom_set_pbus_reg = 0x40001a70; */ +rom_write_chan_freq = 0x40001a74; +/* rom_phy_xpd_rf = 0x40001a78; */ +rom_set_xpd_sar = 0x40001a7c; +rom_write_dac_gain2 = 0x40001a80; +rom_rtc_sar2_init = 0x40001a84; +rom_get_target_power_offset = 0x40001a88; +/* rom_write_txrate_power_offset = 0x40001a8c; */ +rom_get_rate_fcc_index = 0x40001a90; +rom_get_rate_target_power = 0x40001a94; +rom_write_wifi_dig_gain = 0x40001a98; +rom_bt_correct_rf_ana_gain = 0x40001a9c; +rom_pkdet_vol_start = 0x40001aa0; +rom_read_sar2_code = 0x40001aa4; +rom_get_sar2_vol = 0x40001aa8; +rom_get_pll_vol = 0x40001aac; +rom_get_phy_target_power = 0x40001ab0; +/* rom_temp_to_power = 0x40001ab4; */ +rom_phy_track_pll_cap = 0x40001ab8; +rom_phy_pwdet_always_en = 0x40001abc; +rom_phy_pwdet_onetime_en = 0x40001ac0; +rom_get_i2c_mst0_mask = 0x40001ac4; +rom_get_i2c_hostid = 0x40001ac8; +rom_enter_critical_phy = 0x40001acc; +rom_exit_critical_phy = 0x40001ad0; +rom_chip_i2c_readReg_org = 0x40001ad4; +rom_i2c_paral_set_mst0 = 0x40001ad8; +rom_i2c_paral_set_read = 0x40001adc; +rom_i2c_paral_read = 0x40001ae0; +rom_i2c_paral_write = 0x40001ae4; +rom_i2c_paral_write_num = 0x40001ae8; +rom_i2c_paral_write_mask = 0x40001aec; +rom_bb_bss_cbw40_ana = 0x40001af0; +rom_chan_to_freq = 0x40001af4; +/* rom_open_i2c_xpd = 0x40001af8; */ +rom_dac_rate_set = 0x40001afc; +/* rom_tsens_read_init = 0x40001b00; */ +/* rom_tsens_code_read = 0x40001b04; */ +rom_tsens_index_to_dac = 0x40001b08; +rom_tsens_index_to_offset = 0x40001b0c; +/* rom_tsens_dac_cal = 0x40001b10; */ +rom_code_to_temp = 0x40001b14; +rom_write_pll_cap_mem = 0x40001b18; +rom_pll_correct_dcap = 0x40001b1c; +rom_phy_en_hw_set_freq = 0x40001b20; +rom_phy_dis_hw_set_freq = 0x40001b24; +/* rom_pll_vol_cal = 0x40001b28; */ +memset = 0x40000354; +memcpy = 0x40000358; +memmove = 0x4000035c; +memcmp = 0x40000360; + +strncmp = 0x40000370; +strncpy = 0x40000368; +strcpy = 0x40000364; + +abs = 0x40000424; + +PROVIDE(cache_dbus_mmu_set = 0x40000564); + +PROVIDE( strcat = 0x400003d8 ); +PROVIDE( strcmp = 0x4000036c ); +PROVIDE( strchr = 0x400003e0 ); +PROVIDE( strlcpy = 0x400003f0 ); +PROVIDE( strstr = 0x40000378 ); +PROVIDE( strcasecmp = 0x400003d0 ); + +PROVIDE( memchr = 0x400003c8 ); + +/** + * ROM APIs + */ +PROVIDE ( esp_rom_crc32_le = crc32_le ); +PROVIDE ( esp_rom_crc16_le = crc16_le ); +PROVIDE ( esp_rom_crc8_le = crc8_le ); +PROVIDE ( esp_rom_crc32_be = crc32_be ); +PROVIDE ( esp_rom_crc16_be = crc16_be ); +PROVIDE ( esp_rom_crc8_be = crc8_be ); + +PROVIDE ( esp_rom_gpio_pad_select_gpio = gpio_pad_select_gpio ); +PROVIDE ( esp_rom_gpio_pad_pullup_only = gpio_pad_pullup ); +PROVIDE ( esp_rom_gpio_pad_set_drv = gpio_pad_set_drv ); +PROVIDE ( esp_rom_gpio_pad_unhold = gpio_pad_unhold ); +PROVIDE ( esp_rom_gpio_connect_in_signal = gpio_matrix_in ); +PROVIDE ( esp_rom_gpio_connect_out_signal = gpio_matrix_out ); + +PROVIDE ( esp_rom_efuse_mac_address_crc8 = esp_crc8 ); +PROVIDE ( esp_rom_efuse_get_flash_gpio_info = ets_efuse_get_spiconfig ); +PROVIDE ( esp_rom_efuse_is_secure_boot_enabled = ets_efuse_secure_boot_enabled ); +PROVIDE ( esp_rom_efuse_get_flash_wp_gpio = ets_efuse_get_wp_pad ); + +PROVIDE ( esp_rom_uart_flush_tx = uart_tx_flush ); +PROVIDE ( esp_rom_uart_tx_one_char = uart_tx_one_char ); +PROVIDE ( esp_rom_uart_tx_wait_idle = uart_tx_wait_idle ); +PROVIDE ( esp_rom_uart_rx_one_char = uart_rx_one_char ); +PROVIDE ( esp_rom_uart_rx_string = UartRxString ); + +PROVIDE ( esp_rom_output_flush_tx = uart_tx_flush ); +PROVIDE ( esp_rom_output_tx_one_char = uart_tx_one_char ); +PROVIDE ( esp_rom_output_tx_wait_idle = uart_tx_wait_idle ); +PROVIDE ( esp_rom_output_rx_one_char = uart_rx_one_char ); +PROVIDE ( esp_rom_output_rx_string = UartRxString ); + +PROVIDE ( esp_rom_md5_init = MD5Init ); +PROVIDE ( esp_rom_md5_update = MD5Update ); +PROVIDE ( esp_rom_md5_final = MD5Final ); + +PROVIDE ( esp_rom_software_reset_system = software_reset ); +PROVIDE ( esp_rom_software_reset_cpu = software_reset_cpu ); + +PROVIDE ( esp_rom_printf = ets_printf ); +PROVIDE ( esp_rom_delay_us = ets_delay_us ); +PROVIDE ( esp_rom_get_reset_reason = rtc_get_reset_reason ); +PROVIDE ( esp_rom_route_intr_matrix = intr_matrix_set ); +PROVIDE ( esp_rom_get_cpu_ticks_per_us = ets_get_cpu_frequency ); +PROVIDE ( esp_rom_set_cpu_ticks_per_us = ets_update_cpu_frequency ); + +PROVIDE ( esp_rom_spiflash_clear_bp = esp_rom_spiflash_unlock ); +PROVIDE ( esp_rom_spiflash_write_enable = SPI_write_enable ); +PROVIDE ( esp_rom_spiflash_erase_area = SPIEraseArea ); + +PROVIDE ( esp_rom_spiflash_fix_dummylen = spi_dummy_len_fix ); +PROVIDE ( esp_rom_spiflash_set_drvs = SetSpiDrvs); +PROVIDE ( esp_rom_spiflash_select_padsfunc = SelectSpiFunction ); +PROVIDE ( esp_rom_spiflash_common_cmd = SPI_Common_Command ); + +PROVIDE ( esp_rom_regi2c_read = rom_i2c_readReg ); +PROVIDE ( esp_rom_regi2c_read_mask = rom_i2c_readReg_Mask ); +PROVIDE ( esp_rom_regi2c_write = rom_i2c_writeReg ); +PROVIDE ( esp_rom_regi2c_write_mask = rom_i2c_writeReg_Mask ); + +/* +ESP32C3 ECO3 ROM address table +Version 3 API's imported from the ROM +*/ + +/*esf_buf_alloc_dynamic = 0x400015c0;*/ +/*esf_buf_recycle = 0x400015c4;*/ +/*lmacTxDone = 0x4000162c;*/ +/*ppMapTxQueue = 0x400016d8;*/ +/*rcGetSched = 0x40001764;*/ +wDevCheckBlockError = 0x400017b4; +/*ppProcTxDone = 0x40001804;*/ +/*sta_input = rom_sta_input;*/ + +/*************************************** + Group rom_phy + ***************************************/ + +/* Functions */ +rom_index_to_txbbgain = 0x40001964; +rom_pbus_xpd_tx_on = 0x400019b0; +rom_set_tx_dig_gain = 0x400019f0; +rom_set_txcap_reg = 0x400019f4; +rom_txbbgain_to_index = 0x40001a0c; +rom_agc_reg_init = 0x40001a54; +rom_bb_reg_init = 0x40001a58; +rom_set_pbus_reg = 0x40001a70; +rom_phy_xpd_rf = 0x40001a78; +rom_write_txrate_power_offset = 0x40001a8c; +rom_temp_to_power = 0x40001ab4; +rom_open_i2c_xpd = 0x40001af8; +rom_tsens_read_init = 0x40001b00; +rom_tsens_code_read = 0x40001b04; +rom_tsens_dac_cal = 0x40001b10; +rom_pll_vol_cal = 0x40001b28; + +/*************************************** + Group eco3_wifi + ***************************************/ + +/* Functions */ +wdev_is_data_in_rxlist = 0x40001b2c; +ppProcTxCallback = 0x40001b30; +ieee80211_gettid = 0x40001b34; + + +/*************************************** + Group eco3_bluetooth + ***************************************/ + +/* Functions */ +r_lld_legacy_adv_dynamic_pti_get = 0x40001b38; +r_lld_legacy_adv_dynamic_pti_process = 0x40001b3c; +r_lld_ext_adv_dynamic_pti_get = 0x40001b40; +r_lld_ext_adv_dynamic_aux_pti_process = 0x40001b44; +r_lld_ext_adv_dynamic_pti_process = 0x40001b48; +r_lld_adv_ext_pkt_prepare_set = 0x40001b4c; +r_lld_adv_ext_chain_connectable_construct = 0x40001b54; +r_lld_adv_pkt_rx_connect_post = 0x40001b5c; +r_lld_adv_start_init_evt_param = 0x40001b60; +r_lld_adv_start_set_cs = 0x40001b64; +/* r_lld_adv_start_update_filter_policy = 0x40001b68; */ +r_lld_adv_start_schedule_asap = 0x40001b6c; +r_lld_con_tx_prog_new_packet_coex = 0x40001b70; +r_lld_per_adv_dynamic_pti_get = 0x40001b78; +r_lld_per_adv_evt_start_chm_upd = 0x40001b7c; +r_lld_ext_scan_dynamic_pti_get = 0x40001b80; +r_lld_sync_insert = 0x40001b88; +r_sch_prog_ble_push = 0x40001b8c; +r_sch_prog_bt_push = 0x40001b90; +r_lld_init_evt_end_type_set = 0x40001b94; +r_lld_init_evt_end_type_get = 0x40001b98; +r_lld_adv_direct_adv_use_rpa_addr_state_set = 0x40001b9c; +r_lld_adv_direct_adv_use_rpa_addr_state_get = 0x40001ba0; +r_lld_init_evt_end_type_check_state_set = 0x40001ba4; +r_lld_init_evt_end_type_check_state_get = 0x40001ba8; + + +/*************************************** + Group eco3_phy + ***************************************/ + +/* Functions */ +phy_bbpll_en_usb = 0x400021e0; +rom_wrtie_pll_cap = 0x40001bac; +rom_set_tx_gain_mem = 0x40001bb0; +rom_bt_tx_dig_gain = 0x40001bb4; +rom_bt_get_tx_gain = 0x40001bb8; +rom_get_chan_target_power = 0x40001bbc; +rom_get_tx_gain_value = 0x40001bc0; +rom_wifi_tx_dig_gain = 0x40001bc4; +rom_wifi_get_tx_gain = 0x40001bc8; +rom_fe_i2c_reg_renew = 0x40001bcc; +rom_wifi_agc_sat_gain = 0x40001bd0; +rom_i2c_master_reset = 0x40001bd4; +rom_bt_filter_reg = 0x40001bd8; +rom_phy_bbpll_cal = 0x40001bdc; +rom_i2c_sar2_init_code = 0x40001be0; +rom_phy_param_addr = 0x40001be4; +rom_phy_reg_init = 0x40001be8; +rom_set_chan_reg = 0x40001bec; +rom_phy_wakeup_init = 0x40001bf0; +rom_phy_i2c_init1 = 0x40001bf4; +rom_tsens_temp_read = 0x40001bf8; +rom_bt_track_pll_cap = 0x40001bfc; +rom_wifi_track_pll_cap = 0x40001c00; +rom_wifi_set_tx_gain = 0x40001c04; +rom_txpwr_cal_track = 0x40001c08; +rom_tx_pwctrl_background = 0x40001c0c; +rom_bt_set_tx_gain = 0x40001c10; +rom_noise_check_loop = 0x40001c14; +rom_phy_close_rf = 0x40001c18; +rom_phy_xpd_tsens = 0x40001c1c; +rom_phy_freq_mem_backup = 0x40001c20; +rom_phy_ant_init = 0x40001c24; +rom_bt_track_tx_power = 0x40001c28; +rom_wifi_track_tx_power = 0x40001c2c; +rom_phy_dig_reg_backup = 0x40001c30; +chip726_phyrom_version_num = 0x40001c34; +/* Data (.data, .bss, .rodata) */ +phy_param_rom = 0x3fcdf830; + +/*************************************** + Group eco3_esp_flash + ***************************************/ + +/* Functions */ +PROVIDE( esp_flash_read_chip_id = 0x40001c38 ); +PROVIDE( detect_spi_flash_chip = 0x40001c3c ); +PROVIDE( esp_rom_spiflash_write_disable = 0x40001c40 ); + +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +esf_buf_alloc = 0x400015bc; +esf_buf_alloc_dynamic = 0x400015c0; +esf_buf_recycle = 0x400015c4; +/*hal_mac_tx_set_ppdu = 0x400015d4;*/ +ic_mac_deinit = 0x400015dc; +lmacDiscardMSDU = 0x400015f4; +/*lmacSetTxFrame = 0x40001628;*/ +lmacTxDone = 0x4000162c; +lmacTxFrame = 0x40001630; +mac_tx_set_htsig = 0x40001638; +mac_tx_set_plcp1 = 0x40001640; +pm_check_state = 0x40001648; +/*pm_on_beacon_rx = 0x4000167c;*/ +/*pm_parse_beacon = 0x40001688;*/ +pm_process_tim = 0x4000168c; +pm_rx_beacon_process = 0x40001690; +pm_rx_data_process = 0x40001694; +/* pm_sleep = 0x40001698;*/ +/* pm_tbtt_process = 0x400016a0;*/ +ppMapTxQueue = 0x400016d8; +ppProcTxSecFrame = 0x400016dc; +/*ppRxFragmentProc = 0x40001704;*/ +/* rcGetSched = 0x40001764;*/ +rcTxUpdatePer = 0x40001770; +rcUpdateTxDone = 0x4000177c; +wDevCheckBlockError = 0x400017b4; +/* wDev_IndicateFrame = 0x400017c8;*/ +wDev_ProcessFiq = 0x400017f0; +/*wDev_ProcessRxSucData = 0x400017f4;*/ +/*ppProcTxDone = 0x40001804;*/ +/*pm_tx_data_done_process = 0x40001808;*/ +ppMapWaitTxq = 0x40001810; +/*ieee80211_encap_esfbuf = 0x4000185c;*/ +/*sta_input = 0x40001870;*/ +ieee80211_crypto_decap = 0x4000189c; +ieee80211_decap = 0x400018a0; +/*coex_core_timer_idx_get = 0x400018d0;*/ +rom1_chip_i2c_readReg = 0x40001924; +rom1_chip_i2c_writeReg = 0x40001928; +rom_index_to_txbbgain = 0x40001964; +rom_pbus_xpd_tx_on = 0x400019b0; +rom1_set_noise_floor = 0x400019e8; +rom_set_tx_dig_gain = 0x400019f0; +rom_set_txcap_reg = 0x400019f4; +rom_txbbgain_to_index = 0x40001a0c; +rom1_disable_wifi_agc = 0x40001a1c; +rom1_enable_wifi_agc = 0x40001a20; +rom1_tx_paon_set = 0x40001a44; +rom_agc_reg_init = 0x40001a54; +rom_bb_reg_init = 0x40001a58; +rom1_set_pbus_reg = 0x40001a70; +rom_phy_xpd_rf = 0x40001a78; +rom_write_txrate_power_offset = 0x40001a8c; +rom1_get_rate_fcc_index = 0x40001a90; +rom1_read_sar2_code = 0x40001aa4; +rom2_temp_to_power1 = 0x40001ab4; +rom1_get_i2c_hostid = 0x40001ac8; +rom_open_i2c_xpd = 0x40001af8; +rom2_tsens_read_init1 = 0x40001b00; +rom_tsens_code_read = 0x40001b04; +rom_tsens_dac_cal = 0x40001b10; +rom1_phy_en_hw_set_freq = 0x40001b20; +rom1_phy_dis_hw_set_freq = 0x40001b24; +rom_pll_vol_cal = 0x40001b28; + +rom1_bt_get_tx_gain = 0x40001bb8; +rom1_get_chan_target_power = 0x40001bbc; +rom2_get_tx_gain_value1 = 0x40001bc0; +rom1_wifi_tx_dig_gain = 0x40001bc4; +rom1_wifi_get_tx_gain = 0x40001bc8; +rom1_fe_i2c_reg_renew = 0x40001bcc; +rom1_i2c_master_reset = 0x40001bd4; +rom1_phy_wakeup_init = 0x40001bf0; +rom1_phy_i2c_init1 = 0x40001bf4; +rom1_tsens_temp_read = 0x40001bf8; +rom1_bt_track_pll_cap = 0x40001bfc; +rom1_wifi_set_tx_gain = 0x40001c04; +rom1_txpwr_cal_track = 0x40001c08; +rom1_bt_set_tx_gain = 0x40001c10; +rom1_phy_close_rf = 0x40001c18; + + +/*************************************** + Group eco7_uart + ***************************************/ + +/* Functions */ +uart_tx_switch = 0x40001c44; + + +/*************************************** + Group eco7_bluetooth + ***************************************/ + +/* Functions */ +r_lld_con_count_get = 0x40001c48; +r_lld_update_con_offset = 0x40001c4c; +r_lld_con_update_last_clock = 0x40001c50; +r_lld_con_llcp_ind_info_clear = 0x40001c54; +r_lld_con_update_terminte_info_init = 0x40001c58; +r_lld_con_terminate_max_evt_update = 0x40001c5c; +r_llc_pref_param_compute_eco = 0x40001ce8; +r_llc_hci_con_upd_info_send_eco = 0x40001cec; +r_llc_rem_encrypt_proc_continue_eco = 0x40001cf0; +r_llc_start_eco = 0x40001cf8; +r_lld_ext_adv_dynamic_aux_pti_process_eco = 0x40001cfc; +r_lld_adv_start_eco = 0x40001d04; +r_lld_con_evt_canceled_cbk_eco = 0x40001d08; +r_lld_con_evt_time_update_eco = 0x40001d0c; +r_lld_con_start_eco = 0x40001d10; +r_lld_con_frm_isr_eco = 0x40001d14; +r_lld_con_tx_eco = 0x40001d18; +r_lld_ext_scan_dynamic_pti_process_eco = 0x40001d28; +r_lld_scan_frm_eof_isr_eco = 0x40001d2c; +r_lld_sync_start_eco = 0x40001d30; +r_lld_sync_insert_eco = 0x40001d34; +r_llm_adv_rep_flow_control_update_eco = 0x40001d38; +r_llm_env_adv_dup_filt_init_eco = 0x40001d3c; +r_llm_env_adv_dup_filt_deinit_eco = 0x40001d40; +r_llm_adv_rep_flow_control_check_eco = 0x40001d44; +r_llm_scan_start_eco = 0x40001d48; +r_llm_update_duplicate_scan_count = 0x40001d4c; +r_llc_hci_command_handler_pre = 0x40001d50; +r_llc_hci_command_handler_get = 0x40001d54; +r_llc_hci_command_handler_search = 0x40001d58; +r_llc_llcp_pdu_handler_get_overwrite = 0x40001d5c; +r_llc_llcp_pdu_handler_pre = 0x40001d60; +r_llc_llcp_pdu_handler_end = 0x40001d64; +r_llc_con_conflict_check = 0x40001d6c; +r_sch_prog_hw_reset_try = 0x40001d70; +r_sch_prog_et_state_reset = 0x40001d74; +r_sch_prog_end_isr_handler = 0x40001d78; +r_sch_plan_conflict_check = 0x40001d7c; +r_rwble_isr_hw_fixed = 0x40001d80; +r_bt_bb_recorrect_is_dead = 0x40001d84; +r_bt_bb_restart_hw_recorrect = 0x40001d88; +r_ke_task_handler_pre = 0x40001da0; +r_ke_task_handler_end = 0x40001da4; +r_lld_scan_frm_skip_isr_eco = 0x40001db0; +r_lld_ext_scan_dynamic_pti_reset = 0x40001db4; +r_llc_rem_phy_upd_proc_continue_eco = 0x40001db8; +r_llm_get_preferred_phys = 0x40001dbc; +r_lld_hw_cca_isr_eco = 0x40001dc0; +r_lld_sw_cca_isr_eco = 0x40001dc4; +r_lld_cca_chan_prn_e = 0x40001dc8; +r_lld_cca_chan_prn_s = 0x40001dcc; +r_lld_cca_chan_sel_remap = 0x40001dd0; +r_lld_cca_chan_sel_1 = 0x40001dd4; +r_lld_cca_chan_sel_2 = 0x40001dd8; +r_lld_cca_set_thresh = 0x40001ddc; +r_lld_cca_con_start = 0x40001de0; +r_lld_cca_con_end = 0x40001de4; +r_lld_cca_chm_restore = 0x40001de8; +r_lld_cca_chan_unused_check = 0x40001dec; +r_lld_cca_chm_update_check = 0x40001df0; +r_lld_cca_busy_mode_handle = 0x40001df4; +r_lld_cca_lbt_handle = 0x40001df8; +r_lld_cca_scst_timeout_check = 0x40001dfc; +r_lld_cca_chan_avl_timeout_check = 0x40001e00; + +r_lld_con_start_hook = 0x40001ca8; + +/* ble Functions eco */ +r_bt_bb_isr = 0x40000b9c; +r_bt_rf_coex_conn_phy_coded_data_time_limit_en_get = 0x40000ba8; +r_bt_rtp_get_txpwr_idx_by_act = 0x40000c00; +r_btdm_task_post = 0x40000c14; +r_btdm_task_post_from_isr = 0x40000c18; +r_btdm_task_recycle = 0x40000c1c; +r_hci_register_vendor_desc_tab = 0x40000d9c; +r_ke_task_schedule = 0x40000e80; +r_llc_hci_command_handler = 0x40000ef0; +r_llc_loc_con_upd_proc_continue = 0x40000f60; +r_llc_loc_phy_upd_proc_continue = 0x40000f78; +r_llc_rem_con_upd_proc_continue = 0x40000fb4; +r_lld_con_sched = 0x40001118; +r_lld_con_stop = 0x40001124; +r_lld_llcp_rx_ind_handler = 0x400011b0; +r_lld_per_adv_sched = 0x400011f8; +r_lld_scan_process_pkt_rx_adv_rep = 0x40001284; +r_register_esp_vendor_cmd_handler = 0x40001400; +r_rf_txpwr_cs_get = 0x40001428; +r_rf_txpwr_dbm_get = 0x4000142c; +r_sch_arb_event_start_isr = 0x400014f8; +r_sch_plan_set = 0x40001534; +r_sch_prog_end_isr = 0x40001538; +r_lld_adv_ext_chain_scannable_construct = 0x40001b58; + +r_lld_scan_process_pkt_rx = 0x40001280; +r_llm_le_features_get = 0x400013b0; + +/* ble functions rename */ +r_lld_init_start_hack = 0x400011a4; + +/* ble functions disable */ +/* +r_lld_adv_frm_isr_eco = 0x40001d00; +r_lld_res_list_clear = 0x40004638; +r_lld_res_list_rem = 0x40004680; +r_lld_adv_start_hook = 0x40001c80; +r_lld_con_evt_start_cbk_eco = 0x40001d1c; +r_lld_con_tx_prog_new_packet = 0x40001b74; +r_lld_adv_ext_chain_none_construct = 0x40001b50; +r_llc_llcp_send_eco = 0x40001cf4; +r_llc_llcp_channel_map_ind_ack = 0x40001d68; +r_rwble_isr = 0x40001464; +r_lld_scan_start_eco = 0x40001d24; +r_lld_scan_try_sched_eco = 0x40001dac; +r_lld_scan_start_hook = 0x40001c74; +r_lld_init_start_hook = 0x40001cb8; +r_lld_scan_evt_start_cbk_eco = 0x40001d20; +r_ke_task_handler_get_overwrite = 0x40001da8; +*/ + + +/*************************************** + Group eco7_phy + ***************************************/ + +/* Functions */ +rom2_pll_cap_mem_update = 0x40001e04; +rom2_phy_i2c_enter_critical = 0x40001e08; +rom2_phy_i2c_exit_critical = 0x40001e0c; +rom2_rfpll_cap_correct = 0x40001e10; +rom2_write_pll_cap = 0x40001e14; +rom2_read_pll_cap = 0x40001e18; +rom2_tester_wifi_cali = 0x40001e1c; +rom2_wait_hw_freq_busy = 0x40001e20; +rom2_rfpll_cap_track = 0x40001e24; +rom2_ulp_code_track = 0x40001e28; +rom2_ulp_ext_code_set = 0x40001e2c; +rom2_phy_set_tsens_power = 0x40001e30; +rom2_phy_get_tsens_value = 0x40001e34; +rom_mac_tx_chan_offset = 0x40001e38; +rom_rx_gain_force = 0x40001e3c; + +/* ROM function interface esp32c3.rom.libgcc.ld for esp32c3 + * + * + * Generated from ./interface-esp32c3.yml md5sum 93b28a9e1fe42d212018eb4336849208 + * + * Compatible with ROM where ECO version equal or greater to 0. + * + * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT. + */ + +/*************************************** + Group libgcc + ***************************************/ + +/* Functions */ +__absvdi2 = 0x40000764; +__absvsi2 = 0x40000768; +__adddf3 = 0x4000076c; +__addsf3 = 0x40000770; +__addvdi3 = 0x40000774; +__addvsi3 = 0x40000778; +__ashldi3 = 0x4000077c; +__ashrdi3 = 0x40000780; +__bswapdi2 = 0x40000784; +__bswapsi2 = 0x40000788; +__clear_cache = 0x4000078c; +__clrsbdi2 = 0x40000790; +__clrsbsi2 = 0x40000794; +__clzdi2 = 0x40000798; +__clzsi2 = 0x4000079c; +__cmpdi2 = 0x400007a0; +__ctzdi2 = 0x400007a4; +__ctzsi2 = 0x400007a8; +__divdc3 = 0x400007ac; +__divdf3 = 0x400007b0; +__divdi3 = 0x400007b4; +__divsc3 = 0x400007b8; +__divsf3 = 0x400007bc; +__divsi3 = 0x400007c0; +__eqdf2 = 0x400007c4; +__eqsf2 = 0x400007c8; +__extendsfdf2 = 0x400007cc; +__ffsdi2 = 0x400007d0; +__ffssi2 = 0x400007d4; +__fixdfdi = 0x400007d8; +__fixdfsi = 0x400007dc; +__fixsfdi = 0x400007e0; +__fixsfsi = 0x400007e4; +__fixunsdfsi = 0x400007e8; +__fixunssfdi = 0x400007ec; +__fixunssfsi = 0x400007f0; +__floatdidf = 0x400007f4; +__floatdisf = 0x400007f8; +__floatsidf = 0x400007fc; +__floatsisf = 0x40000800; +__floatundidf = 0x40000804; +__floatundisf = 0x40000808; +__floatunsidf = 0x4000080c; +__floatunsisf = 0x40000810; +__gcc_bcmp = 0x40000814; +__gedf2 = 0x40000818; +__gesf2 = 0x4000081c; +__gtdf2 = 0x40000820; +__gtsf2 = 0x40000824; +__ledf2 = 0x40000828; +__lesf2 = 0x4000082c; +__lshrdi3 = 0x40000830; +__ltdf2 = 0x40000834; +__ltsf2 = 0x40000838; +__moddi3 = 0x4000083c; +__modsi3 = 0x40000840; +__muldc3 = 0x40000844; +__muldf3 = 0x40000848; +__muldi3 = 0x4000084c; +__mulsc3 = 0x40000850; +__mulsf3 = 0x40000854; +__mulsi3 = 0x40000858; +__mulvdi3 = 0x4000085c; +__mulvsi3 = 0x40000860; +__nedf2 = 0x40000864; +__negdf2 = 0x40000868; +__negdi2 = 0x4000086c; +__negsf2 = 0x40000870; +__negvdi2 = 0x40000874; +__negvsi2 = 0x40000878; +__nesf2 = 0x4000087c; +__paritysi2 = 0x40000880; +__popcountdi2 = 0x40000884; +__popcountsi2 = 0x40000888; +__powidf2 = 0x4000088c; +__powisf2 = 0x40000890; +__subdf3 = 0x40000894; +__subsf3 = 0x40000898; +__subvdi3 = 0x4000089c; +__subvsi3 = 0x400008a0; +__truncdfsf2 = 0x400008a4; +__ucmpdi2 = 0x400008a8; +__udivdi3 = 0x400008ac; +__udivmoddi4 = 0x400008b0; +__udivsi3 = 0x400008b4; +__udiv_w_sdiv = 0x400008b8; +__umoddi3 = 0x400008bc; +__umodsi3 = 0x400008c0; +__unorddf2 = 0x400008c4; +__unordsf2 = 0x400008c8; + +/* ROM version variables for esp32c3 + * + * These addresses should be compatible with any ROM version for this chip. + * + * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT. + */ +_rom_chip_id = 0x40000010; +_rom_eco_version = 0x40000014; diff --git a/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld index 732003c77..359acde8d 100644 --- a/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld @@ -5,9 +5,9 @@ SECTIONS _irom_start = .; KEEP(*(microzig_flash_start)) *(.text*) - } > IROM - . = ALIGN(8); + . = ALIGN(8); + } > IROM _irom_size = . - _irom_start; @@ -15,6 +15,10 @@ SECTIONS .rodata _drom_start : AT(_irom_size) { *(.rodata*) + *(.srodata*) + + /* wifi rodata */ + *(.rodata_wlog_*.*) } > DROM _drom_size = . - _drom_start; @@ -23,6 +27,21 @@ SECTIONS { microzig_ram_text_start = .; KEEP(*(.ram_text)) + + *(.rwtext*) + + /* wifi rwtext */ + *(.wifi0iram .wifi0iram.*) + *(.wifirxiram .wifirxiram.*) + *(.wifislprxiram .wifislprxiram.*) + *(.wifislpiram .wifislpiram.*) + *(.phyiram .phyiram.*) + *(.iram1 .iram1.*) + *(.wifiextrairam.* ) + *(.coexiram.* ) + + . = ALIGN(8); + microzig_ram_text_end = .; } > IRAM @@ -35,6 +54,9 @@ SECTIONS microzig_data_start = . - _iram_size; *(.sdata*) *(.data*) + + /* wifi data */ + *(.dram1*) microzig_data_end = .; } > DRAM diff --git a/port/espressif/esp/ld/esp32_c3/flashless_sections.ld b/port/espressif/esp/ld/esp32_c3/flashless_sections.ld index 9a3d237c3..4b2f70230 100644 --- a/port/espressif/esp/ld/esp32_c3/flashless_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/flashless_sections.ld @@ -7,7 +7,17 @@ SECTIONS KEEP(*(microzig_ram_start)) *(.text*) KEEP(*(.ram_text)) + *(.rwtext*) + /* wifi rwtext */ + *(.wifi0iram .wifi0iram.*) + *(.wifirxiram .wifirxiram.*) + *(.wifislprxiram .wifislprxiram.*) + *(.wifislpiram .wifislpiram.*) + *(.phyiram .phyiram.*) + *(.iram1 .iram1.*) + *(.wifiextrairam.* ) + *(.coexiram.* ) } > IRAM . = ALIGN(8); @@ -19,7 +29,15 @@ SECTIONS microzig_data_start = .; *(.sdata*) *(.data*) + + /* wifi rodata */ + *(.rodata_wlog_*.*) + + /* wifi data */ + *(.dram1 .dram1.*) + *(.rodata*) + *(.srodata*) } > DRAM .bss (NOLOAD) : diff --git a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld index 4d00f60d9..e73d848f9 100644 --- a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld @@ -1,9 +1,37 @@ SECTIONS { - .irom.skip (NOLOAD) : + .rodata.skip (NOLOAD) : { /* Skip 0x20 bytes for the esp image format mapping header. */ . += 0x20; + } > DROM + + .rodata : ALIGN(4) + { + . = ALIGN(4); + KEEP(*(.app_desc)) + *(.rodata*) + *(.srodata*) + . = ALIGN(4); + } > DROM + + .rodata.wifi : ALIGN(4) + { + . = ALIGN(4); + *(.rodata_wlog_*.*) + . = ALIGN(4); + } > DROM + + .text.dummy (NOLOAD) : ALIGN(4) + { + . = ALIGN(ALIGNOF(.rodata)); + . = ALIGN(ALIGNOF(.rodata.wifi)); + + . = . + SIZEOF(.rodata.skip); + . = . + SIZEOF(.rodata); + . = . + SIZEOF(.rodata.wifi); + + . = ALIGN(0x10000) + 0x20; } > IROM .text : @@ -20,47 +48,64 @@ SECTIONS . += 16; } > IROM - .drom.dummy (NOLOAD) : - { - . = ORIGIN(DROM) + 0x20 + SIZEOF(.text); - - /* Add alignment of MMU page size + 0x20 bytes for the mapping header. */ - . = ALIGN(0x10000) + 0x20; - } > DROM - - .rodata : - { - KEEP(*(.app_desc)) - *(.rodata*) - } > DROM - - .ram_text : + .ram_text : ALIGN(4) { + . = ALIGN(4); KEEP(*(.ram_text)) + . = ALIGN(4); /* TODO: in the case of memory protection there should be some alignment * and offset done here (NOLOAD) */ } > IRAM - .dram.dummy (NOLOAD) : + .ram_text.wifi : ALIGN(4) + { + . = ALIGN(4); + *( .wifi0iram .wifi0iram.*) + *( .wifirxiram .wifirxiram.*) + *( .wifislprxiram .wifislprxiram.*) + *( .wifislpiram .wifislpiram.*) + *( .phyiram .phyiram.*) + *( .iram1 .iram1.*) + *( .wifiextrairam.* ) + *( .coexiram.* ) + . = ALIGN(4); + } > IRAM + + .data.dummy (NOLOAD) : ALIGN(4) { - . = ORIGIN(DRAM) + SIZEOF(.ram_text); + . = . + SIZEOF(.ram_text) + SIZEOF(.ram_text.wifi); } > DRAM - .data : + .data : ALIGN(4) { microzig_data_start = .; + . = ALIGN (4); + *(.sdata*) *(.data*) + microzig_data_end = .; + . = ALIGN (4); } > DRAM - .bss (NOLOAD) : + .data.wifi : ALIGN(4) + { + . = ALIGN (4); + *(.dram1*) + . = ALIGN (4); + } + + .bss (NOLOAD) : ALIGN (4) { microzig_bss_start = .; + . = ALIGN (4); + *(.sbss*) *(.bss*) + microzig_bss_end = .; + . = ALIGN (4); } > DRAM PROVIDE(__global_pointer$ = microzig_data_start + 0x800); diff --git a/port/espressif/esp/ld/esp32_c3/rom_functions.ld b/port/espressif/esp/ld/esp32_c3/rom_functions.ld index 1a915e469..4f2d278c5 100644 --- a/port/espressif/esp/ld/esp32_c3/rom_functions.ld +++ b/port/espressif/esp/ld/esp32_c3/rom_functions.ld @@ -1,26 +1,3 @@ -/* Taken from https://github.com/esp-rs/esp-hal/tree/537ac73c3ad7e056c430474ff78cb1ae8df668d0 */ - -memset = 0x40000354; -memcpy = 0x40000358; -memmove = 0x4000035c; -memcmp = 0x40000360; - -strncmp = 0x40000370; -strncpy = 0x40000368; -strcpy = 0x40000364; - -abs = 0x40000424; - -PROVIDE(cache_dbus_mmu_set = 0x40000564); - -PROVIDE( strcat = 0x400003d8 ); -PROVIDE( strcmp = 0x4000036c ); -PROVIDE( strchr = 0x400003e0 ); -PROVIDE( strlcpy = 0x400003f0 ); -PROVIDE( strstr = 0x40000378 ); -PROVIDE( strcasecmp = 0x400003d0 ); - -PROVIDE( memchr = 0x400003c8 ); /** * ROM APIs */ @@ -82,6 +59,7 @@ PROVIDE ( esp_rom_regi2c_read = rom_i2c_readReg ); PROVIDE ( esp_rom_regi2c_read_mask = rom_i2c_readReg_Mask ); PROVIDE ( esp_rom_regi2c_write = rom_i2c_writeReg ); PROVIDE ( esp_rom_regi2c_write_mask = rom_i2c_writeReg_Mask ); + /* ESP32C3 ECO3 ROM address table Version 3 API's imported from the ROM @@ -211,246 +189,7 @@ phy_param_rom = 0x3fcdf830; PROVIDE( esp_flash_read_chip_id = 0x40001c38 ); PROVIDE( detect_spi_flash_chip = 0x40001c3c ); PROVIDE( esp_rom_spiflash_write_disable = 0x40001c40 ); -/* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -esf_buf_alloc = 0x400015bc; -esf_buf_alloc_dynamic = 0x400015c0; -esf_buf_recycle = 0x400015c4; -/*hal_mac_tx_set_ppdu = 0x400015d4;*/ -ic_mac_deinit = 0x400015dc; -lmacDiscardMSDU = 0x400015f4; -/*lmacSetTxFrame = 0x40001628;*/ -lmacTxDone = 0x4000162c; -lmacTxFrame = 0x40001630; -mac_tx_set_htsig = 0x40001638; -mac_tx_set_plcp1 = 0x40001640; -pm_check_state = 0x40001648; -/*pm_on_beacon_rx = 0x4000167c;*/ -/*pm_parse_beacon = 0x40001688;*/ -pm_process_tim = 0x4000168c; -pm_rx_beacon_process = 0x40001690; -pm_rx_data_process = 0x40001694; -/* pm_sleep = 0x40001698;*/ -/* pm_tbtt_process = 0x400016a0;*/ -ppMapTxQueue = 0x400016d8; -ppProcTxSecFrame = 0x400016dc; -/*ppRxFragmentProc = 0x40001704;*/ -/* rcGetSched = 0x40001764;*/ -rcTxUpdatePer = 0x40001770; -rcUpdateTxDone = 0x4000177c; -wDevCheckBlockError = 0x400017b4; -/* wDev_IndicateFrame = 0x400017c8;*/ -wDev_ProcessFiq = 0x400017f0; -/*wDev_ProcessRxSucData = 0x400017f4;*/ -/*ppProcTxDone = 0x40001804;*/ -/*pm_tx_data_done_process = 0x40001808;*/ -ppMapWaitTxq = 0x40001810; -/*ieee80211_encap_esfbuf = 0x4000185c;*/ -/*sta_input = 0x40001870;*/ -ieee80211_crypto_decap = 0x4000189c; -ieee80211_decap = 0x400018a0; -/*coex_core_timer_idx_get = 0x400018d0;*/ -rom1_chip_i2c_readReg = 0x40001924; -rom1_chip_i2c_writeReg = 0x40001928; -rom_index_to_txbbgain = 0x40001964; -rom_pbus_xpd_tx_on = 0x400019b0; -rom1_set_noise_floor = 0x400019e8; -rom_set_tx_dig_gain = 0x400019f0; -rom_set_txcap_reg = 0x400019f4; -rom_txbbgain_to_index = 0x40001a0c; -rom1_disable_wifi_agc = 0x40001a1c; -rom1_enable_wifi_agc = 0x40001a20; -rom1_tx_paon_set = 0x40001a44; -rom_agc_reg_init = 0x40001a54; -rom_bb_reg_init = 0x40001a58; -rom1_set_pbus_reg = 0x40001a70; -rom_phy_xpd_rf = 0x40001a78; -rom_write_txrate_power_offset = 0x40001a8c; -rom1_get_rate_fcc_index = 0x40001a90; -rom1_read_sar2_code = 0x40001aa4; -rom2_temp_to_power1 = 0x40001ab4; -rom1_get_i2c_hostid = 0x40001ac8; -rom_open_i2c_xpd = 0x40001af8; -rom2_tsens_read_init1 = 0x40001b00; -rom_tsens_code_read = 0x40001b04; -rom_tsens_dac_cal = 0x40001b10; -rom1_phy_en_hw_set_freq = 0x40001b20; -rom1_phy_dis_hw_set_freq = 0x40001b24; -rom_pll_vol_cal = 0x40001b28; - -rom1_bt_get_tx_gain = 0x40001bb8; -rom1_get_chan_target_power = 0x40001bbc; -rom2_get_tx_gain_value1 = 0x40001bc0; -rom1_wifi_tx_dig_gain = 0x40001bc4; -rom1_wifi_get_tx_gain = 0x40001bc8; -rom1_fe_i2c_reg_renew = 0x40001bcc; -rom1_i2c_master_reset = 0x40001bd4; -rom1_phy_wakeup_init = 0x40001bf0; -rom1_phy_i2c_init1 = 0x40001bf4; -rom1_tsens_temp_read = 0x40001bf8; -rom1_bt_track_pll_cap = 0x40001bfc; -rom1_wifi_set_tx_gain = 0x40001c04; -rom1_txpwr_cal_track = 0x40001c08; -rom1_bt_set_tx_gain = 0x40001c10; -rom1_phy_close_rf = 0x40001c18; - - -/*************************************** - Group eco7_uart - ***************************************/ - -/* Functions */ -uart_tx_switch = 0x40001c44; - - -/*************************************** - Group eco7_bluetooth - ***************************************/ -/* Functions */ -r_lld_con_count_get = 0x40001c48; -r_lld_update_con_offset = 0x40001c4c; -r_lld_con_update_last_clock = 0x40001c50; -r_lld_con_llcp_ind_info_clear = 0x40001c54; -r_lld_con_update_terminte_info_init = 0x40001c58; -r_lld_con_terminate_max_evt_update = 0x40001c5c; -r_llc_pref_param_compute_eco = 0x40001ce8; -r_llc_hci_con_upd_info_send_eco = 0x40001cec; -r_llc_rem_encrypt_proc_continue_eco = 0x40001cf0; -r_llc_start_eco = 0x40001cf8; -r_lld_ext_adv_dynamic_aux_pti_process_eco = 0x40001cfc; -r_lld_adv_start_eco = 0x40001d04; -r_lld_con_evt_canceled_cbk_eco = 0x40001d08; -r_lld_con_evt_time_update_eco = 0x40001d0c; -r_lld_con_start_eco = 0x40001d10; -r_lld_con_frm_isr_eco = 0x40001d14; -r_lld_con_tx_eco = 0x40001d18; -r_lld_ext_scan_dynamic_pti_process_eco = 0x40001d28; -r_lld_scan_frm_eof_isr_eco = 0x40001d2c; -r_lld_sync_start_eco = 0x40001d30; -r_lld_sync_insert_eco = 0x40001d34; -r_llm_adv_rep_flow_control_update_eco = 0x40001d38; -r_llm_env_adv_dup_filt_init_eco = 0x40001d3c; -r_llm_env_adv_dup_filt_deinit_eco = 0x40001d40; -r_llm_adv_rep_flow_control_check_eco = 0x40001d44; -r_llm_scan_start_eco = 0x40001d48; -r_llm_update_duplicate_scan_count = 0x40001d4c; -r_llc_hci_command_handler_pre = 0x40001d50; -r_llc_hci_command_handler_get = 0x40001d54; -r_llc_hci_command_handler_search = 0x40001d58; -r_llc_llcp_pdu_handler_get_overwrite = 0x40001d5c; -r_llc_llcp_pdu_handler_pre = 0x40001d60; -r_llc_llcp_pdu_handler_end = 0x40001d64; -r_llc_con_conflict_check = 0x40001d6c; -r_sch_prog_hw_reset_try = 0x40001d70; -r_sch_prog_et_state_reset = 0x40001d74; -r_sch_prog_end_isr_handler = 0x40001d78; -r_sch_plan_conflict_check = 0x40001d7c; -r_rwble_isr_hw_fixed = 0x40001d80; -r_bt_bb_recorrect_is_dead = 0x40001d84; -r_bt_bb_restart_hw_recorrect = 0x40001d88; -r_ke_task_handler_pre = 0x40001da0; -r_ke_task_handler_end = 0x40001da4; -r_lld_scan_frm_skip_isr_eco = 0x40001db0; -r_lld_ext_scan_dynamic_pti_reset = 0x40001db4; -r_llc_rem_phy_upd_proc_continue_eco = 0x40001db8; -r_llm_get_preferred_phys = 0x40001dbc; -r_lld_hw_cca_isr_eco = 0x40001dc0; -r_lld_sw_cca_isr_eco = 0x40001dc4; -r_lld_cca_chan_prn_e = 0x40001dc8; -r_lld_cca_chan_prn_s = 0x40001dcc; -r_lld_cca_chan_sel_remap = 0x40001dd0; -r_lld_cca_chan_sel_1 = 0x40001dd4; -r_lld_cca_chan_sel_2 = 0x40001dd8; -r_lld_cca_set_thresh = 0x40001ddc; -r_lld_cca_con_start = 0x40001de0; -r_lld_cca_con_end = 0x40001de4; -r_lld_cca_chm_restore = 0x40001de8; -r_lld_cca_chan_unused_check = 0x40001dec; -r_lld_cca_chm_update_check = 0x40001df0; -r_lld_cca_busy_mode_handle = 0x40001df4; -r_lld_cca_lbt_handle = 0x40001df8; -r_lld_cca_scst_timeout_check = 0x40001dfc; -r_lld_cca_chan_avl_timeout_check = 0x40001e00; - -r_lld_con_start_hook = 0x40001ca8; - -/* ble Functions eco */ -r_bt_bb_isr = 0x40000b9c; -r_bt_rf_coex_conn_phy_coded_data_time_limit_en_get = 0x40000ba8; -r_bt_rtp_get_txpwr_idx_by_act = 0x40000c00; -r_btdm_task_post = 0x40000c14; -r_btdm_task_post_from_isr = 0x40000c18; -r_btdm_task_recycle = 0x40000c1c; -r_hci_register_vendor_desc_tab = 0x40000d9c; -r_ke_task_schedule = 0x40000e80; -r_llc_hci_command_handler = 0x40000ef0; -r_llc_loc_con_upd_proc_continue = 0x40000f60; -r_llc_loc_phy_upd_proc_continue = 0x40000f78; -r_llc_rem_con_upd_proc_continue = 0x40000fb4; -r_lld_con_sched = 0x40001118; -r_lld_con_stop = 0x40001124; -r_lld_llcp_rx_ind_handler = 0x400011b0; -r_lld_per_adv_sched = 0x400011f8; -r_lld_scan_process_pkt_rx_adv_rep = 0x40001284; -r_register_esp_vendor_cmd_handler = 0x40001400; -r_rf_txpwr_cs_get = 0x40001428; -r_rf_txpwr_dbm_get = 0x4000142c; -r_sch_arb_event_start_isr = 0x400014f8; -r_sch_plan_set = 0x40001534; -r_sch_prog_end_isr = 0x40001538; -r_lld_adv_ext_chain_scannable_construct = 0x40001b58; - -r_lld_scan_process_pkt_rx = 0x40001280; -r_llm_le_features_get = 0x400013b0; - -/* ble functions rename */ -r_lld_init_start_hack = 0x400011a4; - -/* ble functions disable */ -/* -r_lld_adv_frm_isr_eco = 0x40001d00; -r_lld_res_list_clear = 0x40004638; -r_lld_res_list_rem = 0x40004680; -r_lld_adv_start_hook = 0x40001c80; -r_lld_con_evt_start_cbk_eco = 0x40001d1c; -r_lld_con_tx_prog_new_packet = 0x40001b74; -r_lld_adv_ext_chain_none_construct = 0x40001b50; -r_llc_llcp_send_eco = 0x40001cf4; -r_llc_llcp_channel_map_ind_ack = 0x40001d68; -r_rwble_isr = 0x40001464; -r_lld_scan_start_eco = 0x40001d24; -r_lld_scan_try_sched_eco = 0x40001dac; -r_lld_scan_start_hook = 0x40001c74; -r_lld_init_start_hook = 0x40001cb8; -r_lld_scan_evt_start_cbk_eco = 0x40001d20; -r_ke_task_handler_get_overwrite = 0x40001da8; -*/ - - -/*************************************** - Group eco7_phy - ***************************************/ - -/* Functions */ -rom2_pll_cap_mem_update = 0x40001e04; -rom2_phy_i2c_enter_critical = 0x40001e08; -rom2_phy_i2c_exit_critical = 0x40001e0c; -rom2_rfpll_cap_correct = 0x40001e10; -rom2_write_pll_cap = 0x40001e14; -rom2_read_pll_cap = 0x40001e18; -rom2_tester_wifi_cali = 0x40001e1c; -rom2_wait_hw_freq_busy = 0x40001e20; -rom2_rfpll_cap_track = 0x40001e24; -rom2_ulp_code_track = 0x40001e28; -rom2_ulp_ext_code_set = 0x40001e2c; -rom2_phy_set_tsens_power = 0x40001e30; -rom2_phy_get_tsens_value = 0x40001e34; -rom_mac_tx_chan_offset = 0x40001e38; -rom_rx_gain_force = 0x40001e3c; /* ROM function interface esp32c3.rom.ld for esp32c3 * * @@ -2383,6 +2122,7 @@ rom_pll_correct_dcap = 0x40001b1c; rom_phy_en_hw_set_freq = 0x40001b20; rom_phy_dis_hw_set_freq = 0x40001b24; /* rom_pll_vol_cal = 0x40001b28; */ + /* ROM function interface esp32c3.rom.libgcc.ld for esp32c3 * * @@ -2488,6 +2228,7 @@ __umoddi3 = 0x400008bc; __umodsi3 = 0x400008c0; __unorddf2 = 0x400008c4; __unordsf2 = 0x400008c8; + /* ROM version variables for esp32c3 * * These addresses should be compatible with any ROM version for this chip. @@ -2496,3 +2237,25 @@ __unordsf2 = 0x400008c8; */ _rom_chip_id = 0x40000010; _rom_eco_version = 0x40000014; + +memset = 0x40000354; +memcpy = 0x40000358; +memmove = 0x4000035c; +memcmp = 0x40000360; + +strncmp = 0x40000370; +strncpy = 0x40000368; +strcpy = 0x40000364; + +abs = 0x40000424; + +PROVIDE(cache_dbus_mmu_set = 0x40000564); + +PROVIDE( strcat = 0x400003d8 ); +PROVIDE( strcmp = 0x4000036c ); +PROVIDE( strchr = 0x400003e0 ); +PROVIDE( strlcpy = 0x400003f0 ); +PROVIDE( strstr = 0x40000378 ); +PROVIDE( strcasecmp = 0x400003d0 ); + +PROVIDE( memchr = 0x400003c8 ); diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index d9ab4962c..f71ef6625 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -230,6 +230,7 @@ pub const interrupt = struct { pub const nop = riscv32_common.nop; pub const wfi = riscv32_common.wfi; +pub const fence = riscv32_common.fence; pub const startup_logic = switch (cpu_config.boot_mode) { .direct => struct { @@ -683,14 +684,20 @@ fn _vector_table() align(256) linksection(".ram_text") callconv(.naked) void { ); } -fn unhandled(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { +fn unhandled(frame: *TrapFrame) linksection(".ram_text") callconv(.c) void { const mcause = csr.mcause.read(); if (mcause.is_interrupt != 0) { std.log.err("unhandled interrupt {} occurred!", .{mcause.code}); } else { const exception: Exception = @enumFromInt(mcause.code); - std.log.err("exception {s} occurred!", .{@tagName(exception)}); + std.log.err("exception {s} occurred at {x}!", .{@tagName(exception), frame.pc}); + + switch (exception) { + .InstructionFault => std.log.err("faulting address: {x}", .{csr.mtval.read_raw()}), + .LoadFault => std.log.err("faulting address: {x}", .{csr.mtval.read_raw()}), + else => {}, + } } @panic("unhandled trap"); diff --git a/port/espressif/esp/src/hal.zig b/port/espressif/esp/src/hal.zig index 28c379d07..3d281520b 100644 --- a/port/espressif/esp/src/hal.zig +++ b/port/espressif/esp/src/hal.zig @@ -6,6 +6,7 @@ pub const compatibility = @import("hal/compatibility.zig"); pub const drivers = @import("hal/drivers.zig"); pub const gpio = @import("hal/gpio.zig"); pub const i2c = @import("hal/i2c.zig"); +pub const radio = @import("hal/radio.zig"); pub const rng = @import("hal/rng.zig"); pub const rom = @import("hal/rom.zig"); pub const spi = @import("hal/spi.zig"); @@ -21,7 +22,7 @@ comptime { } /// Clock config applied by the default `init()` function of the hal. -pub const clock_config: clocks.Config = .default; +pub const clock_config: clocks.Config = .init_comptime(160_000_000); pub fn init() void { init_sequence(clock_config); diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig new file mode 100644 index 000000000..aaee112d0 --- /dev/null +++ b/port/espressif/esp/src/hal/radio.zig @@ -0,0 +1,190 @@ +const std = @import("std"); +const log = std.log.scoped(.esp_radio); +const Allocator = std.mem.Allocator; + +const microzig = @import("microzig"); +const TrapFrame = microzig.cpu.TrapFrame; +const time = microzig.drivers.time; +const hal = microzig.hal; +const systimer = hal.systimer; +const peripherals = microzig.chip.peripherals; +const SYSTEM = peripherals.SYSTEM; +const RTC_CNTL = peripherals.RTC_CNTL; +const APB_CTRL = peripherals.APB_CTRL; + +const c = @import("esp-wifi-driver"); + +pub const wifi = @import("radio/wifi.zig"); +pub const bluetooth = @import("radio/bluetooth.zig"); + +const osi = @import("radio/osi.zig"); +const timer = @import("radio/timer.zig"); +const multitasking = @import("radio/multitasking.zig"); + +/// You should enable interrupts after/before this. +pub fn init(allocator: Allocator) Allocator.Error!void { + // TODO: check that clock frequency is higher or equal to 80mhz + + { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + enable_wifi_power_domain_and_init_clocks(); + // phy_mem_init(); // only sets some global variable on esp32c3 + + osi.allocator = allocator; + + setup_timer_periodic_alarm(); + setup_interrupts(); + + // TODO: errdefer deinit + try multitasking.init(allocator); + try timer.init(allocator); + } + + multitasking.yield_task(); + + log.debug("initialization complete", .{}); + + // TODO: config + wifi.c_result(c.esp_wifi_internal_set_log_level(c.WIFI_LOG_VERBOSE)) catch { + log.warn("failed to set wifi internal log level", .{}); + }; +} + +// TODO: deinit + +// TODO: maybe this can be moved in an efuse hal +pub fn read_mac() [6]u8 { + const EFUSE = microzig.chip.peripherals.EFUSE; + + var mac: [6]u8 = undefined; + + const low_32_bits: u32 = EFUSE.RD_MAC_SPI_SYS_0.read().MAC_0; + const high_16_bits: u16 = EFUSE.RD_MAC_SPI_SYS_1.read().MAC_1; + @memcpy(mac[0..4], std.mem.asBytes(&low_32_bits)); + @memcpy(mac[4..6], std.mem.asBytes(&high_16_bits)); + + return mac; +} + +fn enable_wifi_power_domain_and_init_clocks() void { + const system_wifibb_rst: u32 = 1 << 0; + const system_fe_rst: u32 = 1 << 1; + const system_wifimac_rst: u32 = 1 << 2; + const system_btbb_rst: u32 = 1 << 3; // bluetooth baseband + const system_btmac_rst: u32 = 1 << 4; // deprecated + const system_rw_btmac_rst: u32 = 1 << 9; // bluetooth mac + const system_rw_btmac_reg_rst: u32 = 1 << 11; // bluetooth mac registers + const system_btbb_reg_rst: u32 = 1 << 13; // bluetooth baseband registers + + const modem_reset_field_when_pu: u32 = system_wifibb_rst | + system_fe_rst | + system_wifimac_rst | + system_btbb_rst | + system_btmac_rst | + system_rw_btmac_rst | + system_rw_btmac_reg_rst | + system_btbb_reg_rst; + + RTC_CNTL.DIG_PWC.modify(.{ + .WIFI_FORCE_PD = 0, + .BT_FORCE_PD = 0, + }); + + APB_CTRL.WIFI_RST_EN.write(.{ .WIFI_RST = APB_CTRL.WIFI_RST_EN.read().WIFI_RST | modem_reset_field_when_pu }); + APB_CTRL.WIFI_RST_EN.write(.{ .WIFI_RST = APB_CTRL.WIFI_RST_EN.read().WIFI_RST & ~modem_reset_field_when_pu }); + + RTC_CNTL.DIG_ISO.modify(.{ + .WIFI_FORCE_ISO = 0, + .BT_FORCE_ISO = 0, + }); + + const system_wifi_clk_i2c_clk_en: u32 = 1 << 5; + const system_wifi_clk_unused_bit12: u32 = 1 << 12; + const wifi_bt_sdio_clk: u32 = system_wifi_clk_i2c_clk_en | system_wifi_clk_unused_bit12; + const system_wifi_clk_en: u32 = 0x00FB9FCF; + + RTC_CNTL.DIG_ISO.modify(.{ + .WIFI_FORCE_ISO = 0, + .BT_FORCE_ISO = 0, + }); + + RTC_CNTL.DIG_PWC.modify(.{ + .WIFI_FORCE_PD = 0, + .BT_FORCE_PD = 0, + }); + + APB_CTRL.WIFI_CLK_EN.write(.{ + .WIFI_CLK_EN = APB_CTRL.WIFI_CLK_EN.read().WIFI_CLK_EN & + ~wifi_bt_sdio_clk | + system_wifi_clk_en, + }); +} + +fn setup_interrupts() void { + // TODO: which interrupts are used should be configurable. + + microzig.cpu.interrupt.set_priority_threshold(.zero); + + microzig.cpu.interrupt.map(.wifi_mac, .interrupt1); + microzig.cpu.interrupt.map(.wifi_pwr, .interrupt1); + microzig.cpu.interrupt.map(.systimer_target0, .interrupt2); + microzig.cpu.interrupt.map(.from_cpu_intr0, .interrupt3); + + inline for (&.{ .interrupt1, .interrupt2, .interrupt3 }) |int| { + microzig.cpu.interrupt.set_type(int, .level); + microzig.cpu.interrupt.set_priority(int, .lowest); + } + + inline for (&.{ .interrupt2, .interrupt3 }) |int| { + microzig.cpu.interrupt.enable(int); + } +} + +// TODO: config (even other timers) +const timer_alarm: systimer.Alarm = .alarm0; +const preemt_interval: time.Duration = .from_ms(10); + +fn setup_timer_periodic_alarm() void { + // unit0 is already enabled as it is used by `hal.time`. + timer_alarm.set_unit(.unit0); + + // sets the period to one second. + timer_alarm.set_period(@intCast(preemt_interval.to_us() * systimer.ticks_per_us())); + + // to enable period mode you have to first clear the mode bit. + timer_alarm.set_mode(.target); + timer_alarm.set_mode(.period); + + timer_alarm.set_interrupt_enabled(true); + timer_alarm.set_enabled(true); +} + +pub const interrupt_handlers = struct { + pub fn wifi_xxx(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { + const handler = osi.wifi_interrupt_handler; + + log.debug("interrupt WIFI_xxx {} {?}", .{ + handler.f, + handler.arg, + }); + + handler.f(handler.arg); + } + + pub fn timer(trap_frame: *TrapFrame) linksection(".ram_text") callconv(.c) void { + timer_alarm.clear_interrupt(); + + multitasking.switch_task(trap_frame); + } + + pub fn software(trap_frame: *TrapFrame) linksection(".ram_text") callconv(.c) void { + // TODO: config + SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ + .CPU_INTR_FROM_CPU_0 = 0, + }); + + multitasking.switch_task(trap_frame); + } +}; diff --git a/port/espressif/esp/src/hal/radio/bluetooth.zig b/port/espressif/esp/src/hal/radio/bluetooth.zig new file mode 100644 index 000000000..e69de29bb diff --git a/port/espressif/esp/src/hal/radio/global_state.zig b/port/espressif/esp/src/hal/radio/global_state.zig new file mode 100644 index 000000000..e69de29bb diff --git a/port/espressif/esp/src/hal/radio/libc_dummy_include/assert.h b/port/espressif/esp/src/hal/radio/libc_dummy_include/assert.h new file mode 100644 index 000000000..e69de29bb diff --git a/port/espressif/esp/src/hal/radio/libc_dummy_include/stdio.h b/port/espressif/esp/src/hal/radio/libc_dummy_include/stdio.h new file mode 100644 index 000000000..c5aea2c49 --- /dev/null +++ b/port/espressif/esp/src/hal/radio/libc_dummy_include/stdio.h @@ -0,0 +1,5 @@ +#include + +typedef uint32_t size_t; + +typedef void FILE; diff --git a/port/espressif/esp/src/hal/radio/libc_dummy_include/stdlib.h b/port/espressif/esp/src/hal/radio/libc_dummy_include/stdlib.h new file mode 100644 index 000000000..e69de29bb diff --git a/port/espressif/esp/src/hal/radio/libc_dummy_include/sys/queue.h b/port/espressif/esp/src/hal/radio/libc_dummy_include/sys/queue.h new file mode 100644 index 000000000..e69de29bb diff --git a/port/espressif/esp/src/hal/radio/multitasking.zig b/port/espressif/esp/src/hal/radio/multitasking.zig new file mode 100644 index 000000000..bd8a7217b --- /dev/null +++ b/port/espressif/esp/src/hal/radio/multitasking.zig @@ -0,0 +1,95 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const microzig = @import("microzig"); +const TrapFrame = microzig.cpu.TrapFrame; +const SYSTEM = microzig.chip.peripherals.SYSTEM; + +pub const Task = struct { + trap_frame: TrapFrame, + stack: []u8, + semaphore: u32 = 0, + next: *Task, + + pub fn create( + allocator: std.mem.Allocator, + entry: *const fn (param: ?*anyopaque) callconv(.c) noreturn, + param: ?*anyopaque, + stack_size: usize, + ) Allocator.Error!*Task { + const stack: []u8 = try allocator.alloc(u8, stack_size); + errdefer allocator.free(stack); + + const task: *Task = try allocator.create(Task); + errdefer allocator.destroy(task); + + const trap_frame: TrapFrame = blk: { + const stack_top_addr: usize = @intFromPtr(stack.ptr) + stack.len; + + var frame: TrapFrame = undefined; + @memset(std.mem.asBytes(&frame), 0); + + frame.pc = @intFromPtr(entry); + frame.a0 = @intFromPtr(param); + frame.sp = stack_top_addr - stack_top_addr % 16; + + break :blk frame; + }; + + task.* = .{ + .trap_frame = trap_frame, + .stack = stack, + .next = task, // loop back to this task + }; + + return task; + } + + pub fn destroy(task: *Task, allocator: Allocator) void { + allocator.free(task.stack); + allocator.destroy(task); + } +}; + +var main_task: *Task = undefined; +/// SAFETY: we don't need optionals because there will always be the main task +/// running. It can't be deleted. +pub var current_task: *Task = undefined; + +pub fn init(allocator: Allocator) !void { + const task: *Task = try allocator.create(Task); + task.* = .{ + .trap_frame = undefined, + .stack = &.{}, + .next = task, // loop back to this task + }; + current_task = task; + main_task = task; +} + +pub fn schedule_task(task: *Task) void { + task.next = current_task.next; + current_task.next = task; +} + +pub fn switch_task(trap_frame: *TrapFrame) void { + copy_context(¤t_task.trap_frame, trap_frame); + + // check if we need to delete any task + + current_task = current_task.next; + + copy_context(trap_frame, ¤t_task.trap_frame); +} + +fn copy_context(dst: *TrapFrame, src: *const TrapFrame) void { + const size = @offsetOf(TrapFrame, "mstatus"); + @memcpy(std.mem.asBytes(dst)[0..size], std.mem.asBytes(src)[0..size]); +} + +pub fn yield_task() void { + // TODO: config + SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ + .CPU_INTR_FROM_CPU_0 = 1, + }); +} diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig new file mode 100644 index 000000000..a1cb78b1d --- /dev/null +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -0,0 +1,1431 @@ +const std = @import("std"); +const log = std.log.scoped(.esp_radio_osi); + +const microzig = @import("microzig"); +const enter_critical_section = microzig.interrupt.enter_critical_section; +const time = microzig.drivers.time; +const peripherals = microzig.chip.peripherals; +const APB_CTRL = peripherals.APB_CTRL; +const hal = microzig.hal; + +const multitasking = @import("multitasking.zig"); +const timer = @import("timer.zig"); +const wifi = @import("wifi.zig"); + +const c = @import("esp-wifi-driver"); + +// TODO: config +const coex_enabled: bool = false; + +pub var allocator: std.mem.Allocator = undefined; + +pub var wifi_interrupt_handler: struct { + f: *const fn (?*anyopaque) callconv(.c) void, + arg: ?*anyopaque, +} = undefined; + +extern fn vsnprintf(buffer: [*c]u8, len: usize, fmt: [*c]const u8, va_list: std.builtin.VaList) callconv(.c) void; + +const log_esp_wifi_driver_internal = std.log.scoped(.esp_wifi_driver_internal); + +fn syslog(fmt: ?[*:0]const u8, va_list: std.builtin.VaList) callconv(.c) void { + var buf: [512:0]u8 = undefined; + vsnprintf(&buf, 512, fmt, va_list); + log_esp_wifi_driver_internal.debug("{s}", .{std.mem.span((&buf).ptr)}); +} + +// ----- exports ----- + +pub export var WIFI_EVENT: c.esp_event_base_t = "WIFI_EVENT"; + +pub export fn strlen(str: ?[*:0]const u8) callconv(.c) usize { + const s = str orelse return 0; + + return std.mem.len(s); +} + +pub export fn strnlen(str: ?[*:0]const u8, _: usize) callconv(.c) usize { + const s = str orelse return 0; + + // TODO Actually provide a complete implementation. + return std.mem.len(s); +} + +pub export fn strrchr(_: ?[*:0]const u8, _: u32) callconv(.c) usize { + @panic("strrchr"); +} + +pub export fn strdup(_: ?[*:0]const u8) callconv(.c) ?[*:0]const u8 { + @panic("strdup"); +} + +pub export fn atoi(_: ?[*:0]const u8) callconv(.c) i32 { + @panic("atoi"); +} + +pub export fn strcasecmp(_: ?[*:0]const u8, _: ?[*:0]const u8) callconv(.c) i32 { + @panic("atoi"); +} + +pub export fn mktime(_: ?*const anyopaque) callconv(.c) i64 { + @panic("mktime"); +} + +pub export fn __assert_func( + file: ?[*:0]const u8, + line: u32, + func: ?[*:0]const u8, + failed_expr: ?[*:0]const u8, +) void { + log.err("assertion `{s}` failed: file `{s}`, line {}, function `{s}`", .{ + failed_expr orelse "", + file orelse "", + line, + func orelse "", + }); + @panic("assertion failed"); +} + +pub export fn malloc(len: usize) callconv(.c) ?*anyopaque { + log.debug("malloc {}", .{len}); + + const buf = allocator.alloc(u8, @sizeOf(usize) + len) catch { + log.warn("failed to allocate memory: {}", .{len}); + return null; + }; + + const alloc_len: *usize = @alignCast(@ptrCast(buf.ptr)); + alloc_len.* = len; + return @ptrFromInt(@intFromPtr(buf.ptr) + @sizeOf(usize)); +} + +pub export fn calloc(number: usize, size: usize) callconv(.c) ?*anyopaque { + const total_size: usize = number * size; + if (malloc(total_size)) |ptr| { + @memset(@as([*]u8, @ptrCast(ptr))[0..total_size], 0); + return ptr; + } + return null; +} + +pub export fn free(ptr: ?*anyopaque) callconv(.c) void { + log.debug("free {?}", .{ptr}); + + std.debug.assert(ptr != null); + + const buf_ptr: [*]u8 = @ptrFromInt(@intFromPtr(ptr) - @sizeOf(usize)); + const buf_len: *usize = @alignCast(@ptrCast(buf_ptr)); + allocator.free(buf_ptr[0 .. @sizeOf(usize) + buf_len.*]); +} + +pub export fn esp_wifi_free_internal_heap(_: ?*anyopaque) callconv(.c) void { + @panic("esp_wifi_free_internal_heap"); +} + +pub export fn esp_wifi_allocate_from_internal_ram() callconv(.c) void { + @panic("esp_wifi_allocate_from_internal_ram"); +} + +pub export fn esp_wifi_deallocate_from_internal_ram() callconv(.c) void { + @panic("esp_wifi_deallocate_from_internal_ram"); +} + +pub export fn _putchar(byte: u8) callconv(.c) void { + // NOTE: not interrupt safe + + const static = struct { + var buf: [256]u8 = undefined; + var bytes_written: usize = 0; + }; + + static.buf[static.bytes_written] = byte; + static.bytes_written += 1; + + if (static.bytes_written >= 256) { + log_esp_wifi_driver_internal.debug("_putchar: {s}", .{&static.buf}); + static.bytes_written = 0; + } +} + +pub export fn puts(ptr: ?*anyopaque) callconv(.c) void { + // NOTE: not interrupt safe + + const s: []const u8 = std.mem.span(@as([*:0]const u8, @ptrCast(ptr))); + log_esp_wifi_driver_internal.debug("{s}", .{s}); +} + +pub export fn rtc_printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); +} + +pub export fn phy_printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); +} + +pub export fn coexist_printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); +} + +pub export fn net80211_printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); +} + +pub export fn pp_printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); +} + +pub export fn gettimeofday(tv: ?*c.timeval, _: ?*anyopaque) i32 { + if (tv) |time_val| { + const usec = hal.time.get_time_since_boot().to_us(); + time_val.tv_sec = usec / 1_000_000; + time_val.tv_usec = @intCast(usec % 1_000_000); + } + + return 0; +} + +pub export fn sleep(time_sec: c_uint) callconv(.c) c_int { + hal.rom.delay_us(time_sec * 1_000_000); + return 0; +} + +pub export fn usleep(time_us: u32) callconv(.c) c_int { + hal.rom.delay_us(time_us); + return 0; +} + +pub export fn esp_fill_random(buf: [*c]u8, len: usize) callconv(.c) void { + log.debug("esp_fill_random {any} {}", .{ buf, len }); + + hal.rng.read(buf[0..len]); +} + +comptime { + _ = WIFI_EVENT; + + _ = strlen; + _ = strnlen; + _ = strrchr; + _ = strdup; + _ = atoi; + _ = strcasecmp; + _ = mktime; + _ = __assert_func; + _ = malloc; + _ = calloc; + _ = free; + _ = esp_wifi_free_internal_heap; + _ = esp_wifi_allocate_from_internal_ram; + _ = esp_wifi_deallocate_from_internal_ram; + _ = _putchar; + _ = puts; + _ = rtc_printf; + _ = phy_printf; + _ = coexist_printf; + _ = net80211_printf; + _ = pp_printf; + _ = gettimeofday; + _ = sleep; + _ = usleep; + _ = esp_fill_random; +} + +// ----- end of exports ----- + +pub fn env_is_chip() callconv(.c) bool { + return true; +} + +pub fn set_intr(cpu_no: i32, intr_source: u32, intr_num: u32, intr_prio: i32) callconv(.c) void { + log.debug("set_intr {} {} {} {}", .{ cpu_no, intr_source, intr_num, intr_prio }); + + // esp32c3 + // these are already set up + // possible calls: + // INFO - set_intr 0 2 1 1 (WIFI_PWR) + // INFO - set_intr 0 0 1 1 (WIFI_MAC) + // + // we don't need to do anything as these interrupts are already setup in + // `radio.init` +} + +pub fn clear_intr(intr_source: u32, intr_num: u32) callconv(.c) void { + log.debug("clear_intr {} {}", .{ intr_source, intr_num }); +} + +pub fn set_isr( + n: i32, + f: ?*anyopaque, + arg: ?*anyopaque, +) callconv(.c) void { + log.debug("set_isr {} {?} {?}", .{ n, f, arg }); + + // esp32c3 specific + + switch (n) { + 0, 1 => { + // don't need critical section because we enable the interrupt + // bellow. + + microzig.cpu.interrupt.disable(.interrupt1); + + wifi_interrupt_handler = .{ + .f = @alignCast(@ptrCast(f)), + .arg = arg, + }; + + microzig.cpu.interrupt.enable(.interrupt1); + }, + else => @panic("invalid interrupt number"), + } +} + +// TODO: I don't think we need to do anything here. +pub fn ints_on(mask: u32) callconv(.c) void { + log.debug("ints_on {}", .{mask}); +} + +pub fn ints_off(mask: u32) callconv(.c) void { + std.debug.panic("ints_off {}", .{mask}); +} + +pub fn is_from_isr() callconv(.c) bool { + return true; +} + +pub fn spin_lock_create() callconv(.c) ?*anyopaque { + log.debug("spin_lock_create", .{}); + return semphr_create(1, 1); +} + +pub fn spin_lock_delete(ptr: ?*anyopaque) callconv(.c) void { + log.debug("spin_lock_delete {?}", .{ptr}); + return semphr_delete(ptr); +} + +pub fn wifi_int_disable(mux_ptr: ?*anyopaque) callconv(.c) u32 { + log.debug("wifi_int_disable {?}", .{mux_ptr}); + + const ints_enabled = microzig.cpu.interrupt.globally_enabled(); + if (ints_enabled) { + microzig.cpu.interrupt.disable_interrupts(); + } + return @intFromBool(ints_enabled); +} + +pub fn wifi_int_restore(mux_ptr: ?*anyopaque, state: u32) callconv(.c) void { + log.debug("wifi_int_restore {?} {}", .{ mux_ptr, state }); + + // check if interrupts were enabled before. + if (state != 0) { + microzig.cpu.interrupt.enable_interrupts(); + } +} + +pub fn task_yield_from_isr() callconv(.c) void { + log.debug("task_yield_from_isr", .{}); + + multitasking.yield_task(); +} + +pub fn semphr_create(max_value: u32, init_value: u32) callconv(.c) ?*anyopaque { + log.debug("semphr_create {} {}", .{ max_value, init_value }); + + const sem = allocator.create(u32) catch { + log.warn("failed to allocate semaphore", .{}); + return null; + }; + sem.* = init_value; + + log.debug(">>>> semaphore create: {*}", .{sem}); + + return sem; +} + +pub fn semphr_delete(ptr: ?*anyopaque) callconv(.c) void { + log.debug("semphr_delete {?}", .{ptr}); + + allocator.destroy(@as(*u32, @alignCast(@ptrCast(ptr)))); +} + +pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { + log.debug("semphr_take {?} {}", .{ ptr, tick }); + + const forever = tick == c.OSI_FUNCS_TIME_BLOCKING; + const timeout = tick; + const start = hal.time.get_time_since_boot(); + + const sem: *u32 = @alignCast(@ptrCast(ptr)); + + while (true) { + const res: bool = blk: { + const cs = enter_critical_section(); + defer cs.leave(); + + microzig.cpu.fence(); + + const cnt = sem.*; + if (cnt > 0) { + sem.* = cnt - 1; + break :blk true; + } else { + break :blk false; + } + }; + + if (res) { + log.debug(">>>> return from semaphore take: {*}", .{sem}); + return 1; + } + + if (!forever and hal.time.get_time_since_boot().diff(start).to_us() > timeout) { + log.debug(">>>> return from semaphore take with timeout: {*}", .{sem}); + break; + } + + multitasking.yield_task(); + } + + return 0; +} + +pub fn semphr_give(ptr: ?*anyopaque) callconv(.c) i32 { + log.debug("semphr_give {?}", .{ptr}); + + const sem: *u32 = @alignCast(@ptrCast(ptr)); + + const cs = enter_critical_section(); + defer cs.leave(); + + const cnt = sem.*; + sem.* = cnt + 1; + return 1; +} + +pub fn wifi_thread_semphr_get() callconv(.c) ?*anyopaque { + return @ptrCast(&multitasking.current_task.semaphore); +} + +const Mutex = struct { + locking_task: usize, + count: u32, + recursive: bool, +}; + +// TODO: idk if we're gonna need to implement this +pub fn mutex_create() callconv(.c) ?*anyopaque { + @panic("mutex_create: not implemented"); +} + +pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { + log.debug("recursive_mutex_create", .{}); + + const mutex = allocator.create(Mutex) catch { + log.warn("failed to allocate recursive mutex", .{}); + return null; + }; + + mutex.* = .{ + .locking_task = 0xffff_ffff, + .count = 0, + .recursive = true, + }; + + microzig.cpu.fence(); + + log.debug(">>>> mutex create: {*}", .{mutex}); + + return mutex; +} + +pub fn mutex_delete(ptr: ?*anyopaque) callconv(.c) void { + log.debug("mutex_delete {?}", .{ptr}); + + const mutex: *Mutex = @alignCast(@ptrCast(ptr)); + allocator.destroy(mutex); +} + +pub fn mutex_lock(ptr: ?*anyopaque) callconv(.c) i32 { + log.debug("mutex lock {?}", .{ptr}); + + const mutex: *Mutex = @alignCast(@ptrCast(ptr)); + const cur_task_id: usize = @intFromPtr(multitasking.current_task); + + while (true) { + const mutex_locked = blk: { + const cs = enter_critical_section(); + defer cs.leave(); + + if (mutex.count == 0) { + mutex.locking_task = cur_task_id; + mutex.count += 1; + break :blk true; + } else if (mutex.locking_task == cur_task_id) { + mutex.count += 1; + break :blk true; + } else { + break :blk false; + } + }; + + microzig.cpu.fence(); + + if (mutex_locked) { + log.debug(">>>> return from mutex lock: {*}", .{mutex}); + return 1; + } + + multitasking.yield_task(); + } +} + +pub fn mutex_unlock(ptr: ?*anyopaque) callconv(.c) i32 { + log.debug("mutex unlock {?}", .{ptr}); + + const mutex: *Mutex = @alignCast(@ptrCast(ptr)); + + const cs = enter_critical_section(); + defer cs.leave(); + + microzig.cpu.fence(); + + if (mutex.count > 0) { + mutex.count -= 1; + return 1; + } else { + return 0; + } +} + +pub const Queue = struct { + len: usize = 0, + capacity: usize, + item_len: usize, + read_index: usize = 0, + write_index: usize = 0, + storage: []u8, + + pub fn create(capacity: usize, item_len: usize) error{OutOfMemory}!*Queue { + const queue = try allocator.create(Queue); + errdefer allocator.destroy(queue); + + queue.* = .{ + .capacity = capacity, + .item_len = item_len, + .storage = try allocator.alloc(u8, capacity * item_len), + }; + + return queue; + } + + pub fn destroy(self: *Queue) void { + allocator.free(self.storage); + allocator.destroy(self); + } + + pub fn get(self: Queue, index: usize) []u8 { + const item_start = self.item_len * index; + return self.storage[item_start..][0..self.item_len]; + } + + pub fn enqueue(self: *Queue, item: [*]const u8) error{QueueFull}!void { + if (self.len >= self.capacity) { + return error.QueueFull; + } + + const slot = self.get(self.write_index); + @memcpy(slot, item[0..self.item_len]); + log.debug(">>>> queue send: {any}", .{item[0..self.item_len]}); + self.write_index = (self.write_index + 1) % self.capacity; + self.len += 1; + } + + pub fn dequeue(self: *Queue, item: [*]u8) error{QueueEmpty}!void { + if (self.len == 0) { + return error.QueueEmpty; + } + + const slot = self.get(self.read_index); + @memcpy(item[0..self.item_len], slot); + log.debug(">>>> queue recv: {any}", .{item[0..self.item_len]}); + self.read_index = (self.read_index + 1) % self.capacity; + self.len -= 1; + } +}; + +pub fn queue_create(capacity: u32, item_len: u32) callconv(.c) ?*anyopaque { + log.debug("queue_create {} {}", .{ capacity, item_len }); + + const new_cap, const new_item_len = if (capacity != 3 and item_len != 4) + .{ capacity, item_len } + else blk: { + log.warn("fixing queue item len", .{}); + break :blk .{ 3, 8 }; + }; + + const queue = Queue.create(new_cap, new_item_len) catch { + log.warn("failed to allocate queue", .{}); + return null; + }; + + log.debug(">>>> queue create: {*}", .{queue}); + + return queue; +} + +pub fn queue_delete(ptr: ?*anyopaque) callconv(.c) void { + log.debug("queue_delete {?}", .{ptr}); + + const queue: *Queue = @alignCast(@ptrCast(ptr)); + queue.destroy(); +} + +// NOTE: here we ignore the timeout. The rust version doesn't use it. +fn queue_send_common(ptr: ?*anyopaque, item_ptr: ?*anyopaque) callconv(.c) i32 { + const queue: *Queue = @alignCast(@ptrCast(ptr)); + const item: [*]const u8 = @alignCast(@ptrCast(item_ptr)); + + const cs = enter_critical_section(); + defer cs.leave(); + + queue.enqueue(item) catch { + log.warn("failed to add item to queue", .{}); + return 0; + }; + + return 1; +} + +pub fn queue_send(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) callconv(.c) i32 { + log.debug("queue_send {?} {?} {}", .{ ptr, item_ptr, block_time_tick }); + + return queue_send_common(ptr, item_ptr); +} + +pub fn queue_send_from_isr(ptr: ?*anyopaque, item_ptr: ?*anyopaque, _hptw: ?*anyopaque) callconv(.c) i32 { + log.debug("queue_send_from_isr {?} {?} {?}", .{ ptr, item_ptr, _hptw }); + + @as(*u32, @alignCast(@ptrCast(_hptw))).* = 1; + return queue_send_common(ptr, item_ptr); +} + +pub fn queue_send_to_back() callconv(.c) void { + @panic("queue_send_to_back: not implemented"); +} + +pub fn queue_send_to_front() callconv(.c) void { + @panic("queue_send_to_front: not implemented"); +} + +pub fn queue_recv(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) callconv(.c) i32 { + log.debug("queue_recv {?} {?} {}", .{ ptr, item_ptr, block_time_tick }); + + const forever = block_time_tick == c.OSI_FUNCS_TIME_BLOCKING; + const timeout = block_time_tick; + const start = hal.time.get_time_since_boot(); + + const queue: *Queue = @alignCast(@ptrCast(ptr)); + const item: [*]u8 = @alignCast(@ptrCast(item_ptr)); + + while (true) { + const cs = enter_critical_section(); + defer cs.leave(); + + if (queue.dequeue(item)) |_| { + log.debug(">>>> return from queue recv: {*}", .{queue}); + + return 1; + } else |_| {} + + if (!forever and hal.time.get_time_since_boot().diff(start).to_us() > timeout) { + log.warn(">>>> return from queue recv from timeout: {*}", .{queue}); + return -1; + } + + multitasking.yield_task(); + } +} + +pub fn queue_msg_waiting(ptr: ?*anyopaque) callconv(.c) u32 { + log.debug("queue_msg_waiting {?}", .{ptr}); + + const queue: *Queue = @alignCast(@ptrCast(ptr)); + return queue.len; +} + +pub fn event_group_create() callconv(.c) void { + @panic("event_group_create: not implemented"); +} + +pub fn event_group_delete() callconv(.c) void { + @panic("event_group_delete: not implemented"); +} + +pub fn event_group_set_bits() callconv(.c) void { + @panic("event_group_set_bits: not implemented"); +} + +pub fn event_group_clear_bits() callconv(.c) void { + @panic("event_group_clear_bits: not implemented"); +} + +pub fn event_group_wait_bits() callconv(.c) void { + @panic("event_group_wait_bits: not implemented"); +} + +fn task_create_common( + task_func: ?*anyopaque, + name: [*c]const u8, + stack_depth: u32, + param: ?*anyopaque, + prio: u32, + task_handle: ?*anyopaque, + core_id: u32, +) i32 { + _ = name; // autofix + _ = prio; // autofix + _ = core_id; // autofix + + const task: *multitasking.Task = multitasking.Task.create( + allocator, + @alignCast(@ptrCast(task_func)), + param, + stack_depth, + ) catch { + log.warn("failed to create task", .{}); + return 0; + }; + + { + const cs = enter_critical_section(); + cs.leave(); + + multitasking.schedule_task(task); + } + + @as(*usize, @alignCast(@ptrCast(task_handle))).* = @intFromPtr(task); + + return 1; +} + +pub fn task_create_pinned_to_core( + task_func: ?*anyopaque, + name: [*c]const u8, + stack_depth: u32, + param: ?*anyopaque, + prio: u32, + task_handle: ?*anyopaque, + core_id: u32, +) callconv(.c) i32 { + log.debug("task_create_pinned_to_core {?} {s} {} {?} {} {?} {}", .{ + task_func, + name, + stack_depth, + param, + prio, + task_handle, + core_id, + }); + + return task_create_common(task_func, name, stack_depth, param, prio, task_handle, core_id); +} + +pub fn task_create( + task_func: ?*anyopaque, + name: [*c]const u8, + stack_depth: u32, + param: ?*anyopaque, + prio: u32, + task_handle: ?*anyopaque, +) callconv(.c) i32 { + log.debug("task_create {?} {s} {} {?} {} {?}", .{ + task_func, + name, + stack_depth, + param, + prio, + task_handle, + }); + + return task_create_common(task_func, name, stack_depth, param, prio, task_handle, 0); +} + +pub fn task_delete() callconv(.c) void { + @panic("task_delete: not implemented"); +} + +pub fn task_delay(tick: u32) callconv(.c) void { + log.debug("task_delay {}", .{tick}); + + const start = hal.time.get_time_since_boot(); + const delay: time.Duration = .from_us(tick); + + while (hal.time.get_time_since_boot().diff(start).less_than(delay)) { + multitasking.yield_task(); + } +} + +// NOTE: we could probably use milliseconds directly. +pub fn task_ms_to_tick(ms: u32) callconv(.c) i32 { + // NOTE: idk about bitcast here. Seems weird that it returns i32. + return @intCast(ms * 1_000); +} + +pub fn task_get_current_task() callconv(.c) ?*anyopaque { + return multitasking.current_task; +} + +pub fn task_get_max_priority() callconv(.c) i32 { + return -1; +} + +pub fn event_post( + base: [*c]const u8, + id: i32, + data: ?*anyopaque, + data_size: usize, + ticks_to_wait: u32, +) callconv(.c) i32 { + _ = base; // autofix + _ = data; // autofix + _ = data_size; // autofix + _ = ticks_to_wait; // autofix + + const event: wifi.Event = @enumFromInt(id); + log.info("received event: {s}", .{@tagName(event)}); + + return 0; +} + +pub fn get_free_heap_size() callconv(.c) void { + @panic("get_free_heap_size: not implemented"); +} + +pub fn rand() callconv(.c) u32 { + return hal.rng.random_u32(); +} + +pub fn dport_access_stall_other_cpu_start_wrap() callconv(.c) void { + log.debug("dport_access_stall_other_cpu_start_wrap", .{}); +} + +pub fn dport_access_stall_other_cpu_end_wrap() callconv(.c) void { + log.debug("dport_access_stall_other_cpu_end_wrap", .{}); +} + +pub fn wifi_apb80m_request() callconv(.c) void { + log.debug("wifi_apb80m_request", .{}); +} + +pub fn wifi_apb80m_release() callconv(.c) void { + log.debug("wifi_apb80m_release", .{}); +} + +const CONFIG_ESP32_PHY_MAX_TX_POWER: u8 = 20; + +fn limit(val: u8, low: u8, high: u8) u8 { + return if (val < low) low else if (val > high) high else val; +} + +const phy_init_data_default: c.esp_phy_init_data_t = .{ + .params = .{ + 0x00, + 0x00, + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x50), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x50), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x50), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x4c), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x4c), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x48), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x4c), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x48), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x48), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x44), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x4a), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x46), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x46), + limit(CONFIG_ESP32_PHY_MAX_TX_POWER * 4, 0, 0x42), + 0x00, + 0x00, + 0x00, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0x74, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + }, +}; + +fn clocks_phy_set_enabled(enable: bool) void { + const system_wifi_clk_wifi_bt_common_m: u32 = 0x78078F; + + var wifi_clk_en = APB_CTRL.WIFI_CLK_EN.read().WIFI_CLK_EN; + if (enable) { + wifi_clk_en |= system_wifi_clk_wifi_bt_common_m; + } else { + wifi_clk_en &= ~system_wifi_clk_wifi_bt_common_m; + } + APB_CTRL.WIFI_CLK_EN.write(.{ .WIFI_CLK_EN = wifi_clk_en }); +} + +pub fn phy_disable() callconv(.c) void { + log.debug("phy_disable", .{}); + clocks_phy_set_enabled(false); +} + +pub fn phy_enable() callconv(.c) void { + log.debug("phy_enable", .{}); + + const cs = enter_critical_section(); + defer cs.leave(); + + clocks_phy_set_enabled(true); + + // TODO: fully implement phy enable + var cal_data: c.esp_phy_calibration_data_t = std.mem.zeroes(c.esp_phy_calibration_data_t); + + const phy_version = c.get_phy_version_str(); + log.debug("phy_version {s}", .{phy_version}); + + _ = c.register_chipv7_phy(&phy_init_data_default, &cal_data, c.PHY_RF_CAL_PARTIAL); +} + +pub fn phy_update_country_info(country: [*c]const u8) callconv(.c) c_int { + log.debug("phy_update_country_info {s}", .{country}); + return -1; +} + +pub fn read_mac(mac: [*c]u8, typ: c_uint) callconv(.c) c_int { + log.debug("read_mac {*} {}", .{ mac, typ }); + + const mac_tmp: [6]u8 = hal.radio.read_mac(); + @memcpy(mac[0..6], &mac_tmp); + + // idk what this means + switch (typ) { + // esp_mac_wifi_softap + 1 => { + const tmp = mac[0]; + var i: u6 = 0; + while (i < 64) : (i += 1) { + mac[0] |= 0x02; + mac[0] ^= @intCast(i << 2); + + if (mac[0] != tmp) { + break; + } + } + }, + // esp_mac_bt + 2 => { + const tmp = mac[0]; + var i: u6 = 0; + while (i < 64) : (i += 1) { + mac[0] |= 0x02; + mac[0] ^= @intCast(i << 2); + + if (mac[0] != tmp) { + break; + } + } + mac[5] += 1; + }, + else => {}, + } + + return 0; +} + +pub fn timer_arm(ets_timer_ptr: ?*anyopaque, ms: u32, repeat: bool) callconv(.c) void { + timer_arm_us(ets_timer_ptr, ms * 1_000, repeat); +} + +pub fn timer_disarm(ets_timer_ptr: ?*anyopaque) callconv(.c) void { + log.debug("timer_disarm {?}", .{ets_timer_ptr}); + + const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); + + // TODO: locking + const cs = enter_critical_section(); + defer cs.leave(); + + if (timer.find(ets_timer)) |tim| { + tim.deadline = .init_absolute(null); + } else { + log.warn("timer not found based on ets_timer", .{}); + } +} + +pub fn timer_done(ets_timer_ptr: ?*anyopaque) callconv(.c) void { + log.debug("timer_done {?}", .{ets_timer_ptr}); + + const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); + + // TODO: locking + const cs = enter_critical_section(); + defer cs.leave(); + + if (timer.find(ets_timer)) |tim| { + ets_timer.priv = null; + ets_timer.expire = 0; + timer.remove(allocator, tim); + } else { + log.warn("timer not found based on ets_timer", .{}); + } +} + +pub fn timer_setfn(ets_timer_ptr: ?*anyopaque, callback_ptr: ?*anyopaque, arg: ?*anyopaque) callconv(.c) void { + log.debug("timer_setfn {?} {?} {?}", .{ ets_timer_ptr, callback_ptr, arg }); + + const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); + const callback: timer.CallbackFn = @alignCast(@ptrCast(callback_ptr)); + + // TODO: locking + const cs = enter_critical_section(); + defer cs.leave(); + + if (timer.find(ets_timer)) |tim| { + tim.callback = callback; + tim.arg = arg; + tim.deadline = .init_absolute(null); + + ets_timer.expire = 0; + } else { + ets_timer.next = null; + ets_timer.period = 0; + ets_timer.func = null; + ets_timer.priv = null; + + timer.add(allocator, ets_timer, callback, arg) catch { + log.warn("failed to allocate timer", .{}); + }; + } +} + +pub fn timer_arm_us(ets_timer_ptr: ?*anyopaque, us: u32, repeat: bool) callconv(.c) void { + log.debug("timer_arm_us {?} {} {}", .{ ets_timer_ptr, us, repeat }); + + const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); + + // TODO: locking + const cs = enter_critical_section(); + defer cs.leave(); + + if (timer.find(ets_timer)) |tim| { + const period: time.Duration = .from_us(us); + tim.deadline = .init_relative(hal.time.get_time_since_boot(), period); + tim.periodic = if (repeat) period else null; + } else { + log.warn("timer not found based on ets_timer", .{}); + } +} + +pub fn wifi_reset_mac() callconv(.c) void { + log.debug("wifi_reset_mac", .{}); + + APB_CTRL.WIFI_RST_EN.write(.{ .WIFI_RST = APB_CTRL.WIFI_RST_EN.read().WIFI_RST | (1 << 2) }); + APB_CTRL.WIFI_RST_EN.write(.{ .WIFI_RST = APB_CTRL.WIFI_RST_EN.read().WIFI_RST & ~@as(u32, 1 << 2) }); +} + +pub fn wifi_clock_enable() callconv(.c) void { + log.debug("wifi_clock_enable", .{}); + + // no op on esp32c3 +} + +pub fn wifi_clock_disable() callconv(.c) void { + log.debug("wifi_clock_disable", .{}); + + // no op on esp32c3 +} + +pub fn wifi_rtc_enable_iso() callconv(.c) void { + @panic("wifi_rtc_enable_iso: not implemented"); +} + +pub fn wifi_rtc_disable_iso() callconv(.c) void { + @panic("wifi_rtc_disable_iso: not implemented"); +} + +pub fn esp_timer_get_time() callconv(.c) i64 { + log.debug("esp_timer_get_time", .{}); + + // TODO: or bit cast? + return @intCast(hal.time.get_time_since_boot().to_us()); +} + +pub fn nvs_set_i8() callconv(.c) void { + @panic("nvs_set_i8: not implemented"); +} + +pub fn nvs_get_i8() callconv(.c) void { + @panic("nvs_get_i8: not implemented"); +} + +pub fn nvs_set_u8() callconv(.c) void { + @panic("nvs_set_u8: not implemented"); +} + +pub fn nvs_get_u8() callconv(.c) void { + @panic("nvs_get_u8: not implemented"); +} + +pub fn nvs_set_u16() callconv(.c) void { + @panic("nvs_set_u16: not implemented"); +} + +pub fn nvs_get_u16() callconv(.c) void { + @panic("nvs_get_u16: not implemented"); +} + +pub fn nvs_open() callconv(.c) void { + @panic("nvs_open: not implemented"); +} + +pub fn nvs_close() callconv(.c) void { + @panic("nvs_close: not implemented"); +} + +pub fn nvs_commit() callconv(.c) void { + @panic("nvs_commit: not implemented"); +} + +pub fn nvs_set_blob() callconv(.c) void { + @panic("nvs_set_blob: not implemented"); +} + +pub fn nvs_get_blob() callconv(.c) void { + @panic("nvs_get_blob: not implemented"); +} + +pub fn nvs_erase_key() callconv(.c) void { + @panic("nvs_erase_key: not implemented"); +} + +pub fn get_random(buf: [*c]u8, len: usize) callconv(.c) c_int { + hal.rng.read(buf[0..len]); + return 0; +} + +pub fn get_time() callconv(.c) void { + @panic("get_time: not implemented"); +} + +pub fn random() callconv(.c) c_ulong { + return hal.rng.random_u32(); +} + +pub fn slowclk_cal_get() callconv(.c) u32 { + // NOTE: esp32c3 specific + return 28639; +} + +pub fn log_write(_: c_uint, _: [*c]const u8, fmt: [*c]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); +} + +pub fn log_writev(_: c_uint, _: [*c]const u8, fmt: [*c]const u8, va_list: c.va_list) callconv(.c) void { + syslog(fmt, @ptrCast(va_list)); +} + +pub fn log_timestamp() callconv(.c) u32 { + return @truncate(hal.time.get_time_since_boot().to_us() * 1_000); +} + +pub const malloc_internal = malloc; + +pub fn realloc_internal() callconv(.c) void { + @panic("realloc_internal: not implemented"); +} + +pub const calloc_internal = calloc; + +pub fn zalloc_internal(len: usize) callconv(.c) ?*anyopaque { + if (malloc(len)) |ptr| { + @memset(@as([*]u8, @ptrCast(ptr))[0..len], 0); + return ptr; + } + + return null; +} + +pub const wifi_malloc = malloc; + +pub fn wifi_realloc() callconv(.c) void { + @panic("wifi_realloc: not implemented"); +} + +pub const wifi_calloc = calloc; +pub const wifi_zalloc = zalloc_internal; + +var wifi_queue_handle: *Queue = undefined; + +pub fn wifi_create_queue(capacity: c_int, item_len: c_int) callconv(.c) ?*anyopaque { + log.debug("wifi_create_queue {} {}", .{ capacity, item_len }); + + wifi_queue_handle = Queue.create(@intCast(capacity), @intCast(item_len)) catch { + log.warn("failed to allocate wifi queue", .{}); + return null; + }; + + log.debug(">>>> wifi queue create: {*}", .{wifi_queue_handle}); + + return @ptrCast(&wifi_queue_handle); +} + +pub fn wifi_delete_queue(ptr: ?*anyopaque) callconv(.c) void { + log.debug("wifi_delete_queue {?}", .{ptr}); + + // if (wifi_queue_handle != ptr) { + // @panic("unknown queue when trying to delete wifi queue"); + // } + + const queue: *Queue = @alignCast(@ptrCast(ptr)); + queue.destroy(); +} + +pub fn coex_init() callconv(.c) c_int { + log.debug("coex_init", .{}); + + return if (coex_enabled) c.coex_init() else 0; +} + +pub fn coex_deinit() callconv(.c) void { + log.debug("coex_deinit", .{}); + + if (coex_enabled) c.coex_deinit(); +} + +pub fn coex_enable() callconv(.c) c_int { + log.debug("coex_enable", .{}); + + return if (coex_enabled) c.coex_enable() else 0; +} + +pub fn coex_disable() callconv(.c) void { + log.debug("coex_disable", .{}); + + if (coex_enabled) c.coex_disable(); +} + +pub fn coex_status_get() callconv(.c) u32 { + log.debug("coex_status_get", .{}); + + return if (coex_enabled) c.coex_status_get() else 0; +} + +pub fn coex_condition_set() callconv(.c) void { + @panic("coex_condition_set"); +} + +pub fn coex_wifi_request( + event: u32, + latency: u32, + duration: u32, +) callconv(.c) c_int { + log.debug("coex_wifi_request", .{}); + + return if (coex_enabled) c.coex_wifi_request(event, latency, duration) else 0; +} + +pub fn coex_wifi_release(event: u32) callconv(.c) c_int { + log.debug("coex_wifi_release", .{}); + + return if (coex_enabled) c.coex_wifi_release(event) else 0; +} + +pub fn coex_wifi_channel_set( + primary: u8, + secondary: u8, +) callconv(.c) c_int { + log.debug("coex_wifi_channel_set", .{}); + + return if (coex_enabled) c.coex_wifi_channel_set(primary, secondary) else 0; +} + +pub fn coex_event_duration_get(event: u32, duration: [*c]u32) callconv(.c) c_int { + log.debug("coex_event_duration_get", .{}); + + return if (coex_enabled) c.coex_event_duration_get(event, duration) else 0; +} + +pub fn coex_pti_get(event: u32, pti: [*c]u8) callconv(.c) c_int { + log.debug("coex_pti_get", .{}); + + return if (coex_enabled) c.coex_pti_get(event, pti) else 0; +} + +pub fn coex_schm_status_bit_clear(@"type": u32, status: u32) callconv(.c) void { + log.debug("coex_schm_status_bit_clear", .{}); + + if (coex_enabled) c.coex_schm_status_bit_clear(@"type", status); +} + +pub fn coex_schm_status_bit_set(@"type": u32, status: u32) callconv(.c) void { + log.debug("coex_schm_status_bit_set", .{}); + + if (coex_enabled) c.coex_schm_status_bit_set(@"type", status); +} + +pub fn coex_schm_interval_set(interval: u32) callconv(.c) c_int { + log.debug("coex_schm_interval_set", .{}); + + return if (coex_enabled) c.coex_schm_interval_set(interval) else 0; +} + +pub fn coex_schm_interval_get() callconv(.c) u32 { + log.debug("coex_schm_interval_get", .{}); + + return if (coex_enabled) c.coex_schm_interval_get() else 0; +} + +pub fn coex_schm_curr_period_get() callconv(.c) u8 { + log.debug("coex_schm_curr_period_get", .{}); + + return if (coex_enabled) c.coex_schm_curr_period_get() else 0; +} + +pub fn coex_schm_curr_phase_get() callconv(.c) ?*anyopaque { + log.debug("coex_schm_curr_phase_get", .{}); + + return if (coex_enabled) c.coex_schm_curr_phase_get() else null; +} + +pub fn coex_schm_process_restart() callconv(.c) c_int { + log.debug("coex_schm_process_restart", .{}); + + return if (coex_enabled) c.coex_schm_process_restart() else 0; +} + +pub fn coex_schm_register_cb( + @"type": c_int, + callback: ?*const fn (c_int) callconv(.c) c_int, +) callconv(.c) c_int { + log.debug("coex_schm_register_cb", .{}); + + return if (coex_enabled) c.coex_schm_register_callback(@"type", callback) else 0; +} + +pub fn coex_register_start_cb(cb: ?*const fn () callconv(.c) c_int) callconv(.c) c_int { + log.debug("coex_register_start_cb", .{}); + + return if (coex_enabled) c.coex_register_start_cb(cb) else 0; +} + +extern fn ext_coex_schm_flexible_period_set(period: u8) i32; +pub fn coex_schm_flexible_period_set(period: u8) callconv(.c) c_int { + log.debug("coex_schm_flexible_period_set {}", .{period}); + + return if (coex_enabled) ext_coex_schm_flexible_period_set(period) else 0; +} + +extern fn ext_coex_schm_flexible_period_get() u8; +pub fn coex_schm_flexible_period_get() callconv(.c) u8 { + log.debug("coex_schm_flexible_period_get", .{}); + + return if (coex_enabled) ext_coex_schm_flexible_period_get() else 0; +} diff --git a/port/espressif/esp/src/hal/radio/timer.zig b/port/espressif/esp/src/hal/radio/timer.zig new file mode 100644 index 000000000..b75f170d0 --- /dev/null +++ b/port/espressif/esp/src/hal/radio/timer.zig @@ -0,0 +1,106 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const microzig = @import("microzig"); +const time = microzig.drivers.time; +const hal = microzig.hal; + +const multitasking = @import("multitasking.zig"); + +const c = @import("esp-wifi-driver"); + +pub const CallbackFn = *const fn (?*anyopaque) callconv(.c) void; + +pub const Timer = struct { + ets_timer: *c.ets_timer, + callback: CallbackFn, + arg: ?*anyopaque, + deadline: time.Deadline, + periodic: ?time.Duration, +}; + +const TimerList = std.SinglyLinkedList(Timer); +const TimerListNode = TimerList.Node; + +var timer_list: TimerList = .{}; + +pub fn init(allocator: Allocator) !void { + const task = try multitasking.Task.create(allocator, timer_task, null, 8192); + multitasking.schedule_task(task); +} + +pub fn add(allocator: Allocator, ets_timer: *c.ets_timer, callback: CallbackFn, arg: ?*anyopaque) !void { + const timer: Timer = .{ + .ets_timer = ets_timer, + .callback = callback, + .arg = arg, + .deadline = .init_absolute(null), + .periodic = null, + }; + + const node = try allocator.create(TimerListNode); + node.* = .{ + .data = timer, + }; + + timer_list.prepend(node); +} + +pub fn remove(allocator: Allocator, timer: *Timer) void { + _ = allocator; // autofix + _ = timer; // autofix + // const node: *TimerListNode = @fieldParentPtr("data", timer); + // timer_list.remove(node); + // allocator.destroy(node); +} + +pub fn find(ets_timer: *c.ets_timer) ?*Timer { + var current_node = timer_list.first; + while (current_node) |node| : (current_node = node.next) { + const timer = &node.data; + if (timer.ets_timer == ets_timer) { + return timer; + } + } + return null; +} + +fn find_next_due(now: time.Absolute) ?*Timer { + var current_node = timer_list.first; + while (current_node) |node| : (current_node = node.next) { + const timer = &node.data; + if (timer.deadline.is_reached_by(now)) { + return timer; + } + } + return null; +} + +fn timer_task(_: ?*anyopaque) callconv(.c) noreturn { + while (true) { + const now = hal.time.get_time_since_boot(); + + const maybe_call, const arg = blk: { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + if (find_next_due(now)) |tim| { + if (tim.periodic) |period| { + tim.deadline = .init_relative(now, period); + } else { + tim.deadline = .init_absolute(null); + } + + break :blk .{ tim.callback, tim.arg }; + } else { + break :blk .{ null, null }; + } + }; + + if (maybe_call) |callback| { + callback(arg); + } else { + multitasking.yield_task(); + } + } +} diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig new file mode 100644 index 000000000..db177b7bb --- /dev/null +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -0,0 +1,750 @@ +const std = @import("std"); +const log = std.log.scoped(.esp_radio_wifi); + +const microzig = @import("microzig"); + +const osi = @import("osi.zig"); + +pub const c = @import("esp-wifi-driver"); + +pub const InternalError = error{InternalError}; + +var inited: bool = false; + +pub fn init() InternalError!void { + if (inited) { + @panic("wifi already initialized"); + } + + init_config.wpa_crypto_funcs = c.g_wifi_default_wpa_crypto_funcs; + init_config.feature_caps = g_wifi_feature_caps; + + // TODO: if coex enabled + if (false) try c_result(c.coex_init()); + + try c_result(c.esp_wifi_init_internal(&init_config)); + + try c_result(c.esp_wifi_set_mode(c.WIFI_MODE_NULL)); + + try c_result(c.esp_supplicant_init()); + + try c_result(c.esp_wifi_set_tx_done_cb(tx_done_cb)); + + try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_STA, recv_cb_sta)); + try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_AP, recv_cb_ap)); + + { + // TODO: config + const country_code: [3]u8 = .{ 'C', 'N', 0 }; + const country: c.wifi_country_t = .{ + .cc = country_code, + .schan = 1, + .nchan = 13, + .max_tx_power = 20, + .policy = c.WIFI_COUNTRY_POLICY_MANUAL, + }; + try c_result(c.esp_wifi_set_country(&country)); + } + + try set_power_save_mode(.none); + + inited = true; +} + +pub fn callback(_: ?*anyopaque) callconv(.c) void { + log.info("hello world", .{}); +} + +pub fn deinit() void { + if (!inited) { + @panic("trying to deinit the wifi controller but it isn't initialized"); + } + + _ = c.esp_wifi_stop(); + _ = c.esp_wifi_deinit_internal(); + _ = c.esp_supplicant_deinit(); + + inited = false; +} + +pub const SetConfigError = InternalError || error{ + InvalidConfig, +}; + +pub const ClientConfig = struct { + /// The SSID of the Wi-Fi network. + ssid: []const u8, + + /// The BSSID (MAC address) of the client. + bssid: ?[6]u8 = null, + + /// Authentication config for the Wi-Fi connection. + auth: ?AuthConfig = null, + + /// The Wi-Fi channel to connect to. + channel: u8 = 0, + + scan_method: ScanMethod = .fast, + + listen_interval: u16 = 3, + + failure_retry_cnt: u8 = 1, +}; + +pub fn set_client_config(config: ClientConfig) SetConfigError!void { + if (config.ssid.len > 32) { + return error.InvalidConfig; + } + + if (config.auth) |auth| { + if (auth.password.len > 64) { + return error.InvalidConfig; + } + } + + var sta_cfg: wifi_sta_config_t = .{ + .scan_method = @intFromEnum(config.scan_method), + .bssid_set = config.bssid != null, + .bssid = config.bssid orelse @splat(0), + .channel = config.channel, + // TODO: config + .listen_interval = config.listen_interval, + .sort_method = c.WIFI_CONNECT_AP_BY_SIGNAL, + .threshold = .{ + .rssi = -99, + .authmode = if (config.auth) |auth| @intFromEnum(auth.method) else c.WIFI_AUTH_OPEN, + }, + .pmf_cfg = .{ + .capable = true, + .required = false, + }, + .failure_retry_cnt = config.failure_retry_cnt, + }; + + @memcpy(sta_cfg.ssid[0..config.ssid.len], config.ssid); + if (config.auth) |auth| { + @memcpy(sta_cfg.password[0..auth.password.len], auth.password); + } + + var tmp: wifi_config_t = .{ .sta = sta_cfg }; + try c_result(esp_wifi_set_config(c.WIFI_IF_STA, &tmp)); +} + +pub const AccessPointConfig = struct { + /// The SSID of the access point. + ssid: []const u8, + + /// Whether the SSID is hidden or visible. + ssid_hidden: bool, + + /// The channel the access point will operate on. + channel: u8, + + /// The secondary channel configuration. + secondary_channel: ?u8, + + /// The set of protocols supported by the access point. + protocols: []const Protocol, + + /// Authentication config to be used by the access point. + auth: ?AuthConfig, + + /// The maximum number of connections allowed on the access point. + max_connections: u8, +}; + +pub fn set_access_point_config(config: AccessPointConfig) SetConfigError!void { + if (config.ssid.len > 32) { + return error.InvalidConfig; + } + + if (config.auth) |auth| { + if (auth.password.len > 64) { + return error.InvalidConfig; + } + } + + var ap_cfg: wifi_ap_config_t = .{ + .ssid_len = @intCast(config.ssid.len), + .channel = config.channel, + .authmode = if (config.auth) |auth| @intFromEnum(auth.method) else c.WIFI_AUTH_OPEN, + .ssid_hidden = @intFromBool(config.ssid_hidden), + .max_connection = config.max_connections, + .beacon_interval = 100, + .pairwise_cipher = c.WIFI_CIPHER_TYPE_CCMP, + .ftm_responder = false, + .pmf_cfg = .{ + .capable = true, + .required = false, + }, + .sae_pwe_h2e = 0, + .csa_count = 3, + .dtim_period = 2, + }; + + @memcpy(ap_cfg.ssid[0..config.ssid.len], config.ssid); + if (config.auth) |auth| { + @memcpy(ap_cfg.password[0..auth.password.len], auth.password); + } + + var tmp: wifi_config_t = .{ .ap = ap_cfg }; + try c_result(esp_wifi_set_config(c.WIFI_IF_AP, &tmp)); +} + +pub const AuthConfig = struct { + password: []const u8, + method: Method, + + pub const Method = enum(u32) { + /// Wired Equivalent Privacy (WEP) authentication. + wep = c.WIFI_AUTH_WEP, + + /// Wi-Fi Protected Access (WPA) authentication. + wpa = c.WIFI_AUTH_WPA_PSK, + + /// Wi-Fi Protected Access 2 (WPA2) Personal authentication (default). + wpa2_personal = c.WIFI_AUTH_WPA2_PSK, + + /// WPA/WPA2 Personal authentication (supports both). + wpa_wpa2_personal = c.WIFI_AUTH_WPA_WPA2_PSK, + + /// WPA2 Enterprise authentication. + wpa2_enterprise = c.WIFI_AUTH_WPA2_ENTERPRISE, + + /// WPA3 Personal authentication. + wpa3_personal = c.WIFI_AUTH_WPA3_PSK, + + /// WPA2/WPA3 Personal authentication (supports both). + wpa2_wpa3_personal = c.WIFI_AUTH_WPA2_WPA3_PSK, + + /// WLAN Authentication and Privacy Infrastructure (WAPI). + wapi_personal = c.WIFI_AUTH_WAPI_PSK, + }; +}; + +pub const ScanMethod = enum(u32) { + fast = c.WIFI_FAST_SCAN, + all_channel = c.WIFI_ALL_CHANNEL_SCAN, +}; + +pub const PowerSaveMode = enum(u32) { + none = c.WIFI_PS_NONE, + min = c.WIFI_PS_MIN_MODEM, + max = c.WIFI_PS_MAX_MODEM, +}; + +pub fn set_power_save_mode(mode: PowerSaveMode) InternalError!void { + try c_result(c.esp_wifi_set_ps(@intFromEnum(mode))); +} + +pub const WifiMode = enum(u32) { + sta = c.WIFI_MODE_STA, + ap = c.WIFI_MODE_AP, + ap_sta = c.WIFI_MODE_APSTA, +}; + +pub fn get_mode() InternalError!WifiMode { + var mode: c.wifi_mode_t = undefined; + try c_result(c.esp_wifi_get_mode(&mode)); + return @enumFromInt(mode); +} + +pub fn set_mode(mode: WifiMode) InternalError!void { + try c_result(c.esp_wifi_set_mode(@intFromEnum(mode))); +} + +pub const Protocol = enum(u8) { + /// 802.11b protocol. + P802D11B = c.WIFI_PROTOCOL_11B, + + /// 802.11b/g protocol. + P802D11BG = c.WIFI_PROTOCOL_11B | c.WIFI_PROTOCOL_11G, + + /// 802.11b/g/n protocol (default). + P802D11BGN = c.WIFI_PROTOCOL_11B | c.WIFI_PROTOCOL_11G | c.WIFI_PROTOCOL_11N, + + /// 802.11b/g/n long-range (LR) protocol. + P802D11BGNLR = c.WIFI_PROTOCOL_11B | c.WIFI_PROTOCOL_11G | c.WIFI_PROTOCOL_11N | c.WIFI_PROTOCOL_LR, + + /// 802.11 long-range (LR) protocol. + P802D11LR = c.WIFI_PROTOCOL_LR, + + /// 802.11b/g/n/ax protocol. + P802D11BGNAX = c.WIFI_PROTOCOL_11B | c.WIFI_PROTOCOL_11G | c.WIFI_PROTOCOL_11N | c.WIFI_PROTOCOL_11AX, +}; + +fn set_protocol(protocols: []const Protocol) InternalError!void { + var combined: u8 = 0; + for (protocols) |protocol| { + combined |= @intFromEnum(protocol); + } + + const mode = try get_mode(); + switch (mode) { + .sta => { + try c_result(c.esp_wifi_set_protocol(c.WIFI_IF_STA, combined)); + }, + .ap => { + try c_result(c.esp_wifi_set_protocol(c.WIFI_IF_AP, combined)); + }, + .ap_sta => { + try c_result(c.esp_wifi_set_protocol(c.WIFI_IF_STA, combined)); + try c_result(c.esp_wifi_set_protocol(c.WIFI_IF_AP, combined)); + }, + } +} + +pub fn start() InternalError!void { + try c_result(c.esp_wifi_start()); +} + +pub const Event = enum(i32) { + /// Wi-Fi is ready for operation. + WifiReady = 0, + /// Scan operation has completed. + ScanDone, + /// Station mode started. + StaStart, + /// Station mode stopped. + StaStop, + /// Station connected to a network. + StaConnected, + /// Station disconnected from a network. + StaDisconnected, + /// Station authentication mode changed. + StaAuthmodeChange, + + /// Station WPS succeeds in enrollee mode. + StaWpsErSuccess, + /// Station WPS fails in enrollee mode. + StaWpsErFailed, + /// Station WPS timeout in enrollee mode. + StaWpsErTimeout, + /// Station WPS pin code in enrollee mode. + StaWpsErPin, + /// Station WPS overlap in enrollee mode. + StaWpsErPbcOverlap, + + /// Soft-AP start. + ApStart, + /// Soft-AP stop. + ApStop, + /// A station connected to Soft-AP. + ApStaconnected, + /// A station disconnected from Soft-AP. + ApStadisconnected, + /// Received probe request packet in Soft-AP interface. + ApProbereqrecved, + + /// Received report of FTM procedure. + FtmReport, + + /// AP's RSSI crossed configured threshold. + StaBssRssiLow, + /// Status indication of Action Tx operation. + ActionTxStatus, + /// Remain-on-Channel operation complete. + RocDone, + + /// Station beacon timeout. + StaBeaconTimeout, + + /// Connectionless module wake interval has started. + ConnectionlessModuleWakeIntervalStart, + + /// Soft-AP WPS succeeded in registrar mode. + ApWpsRgSuccess, + /// Soft-AP WPS failed in registrar mode. + ApWpsRgFailed, + /// Soft-AP WPS timed out in registrar mode. + ApWpsRgTimeout, + /// Soft-AP WPS pin code in registrar mode. + ApWpsRgPin, + /// Soft-AP WPS overlap in registrar mode. + ApWpsRgPbcOverlap, + + /// iTWT setup. + ItwtSetup, + /// iTWT teardown. + ItwtTeardown, + /// iTWT probe. + ItwtProbe, + /// iTWT suspended. + ItwtSuspend, + /// TWT wakeup event. + TwtWakeup, + /// bTWT setup. + BtwtSetup, + /// bTWT teardown. + BtwtTeardown, + + /// NAN (Neighbor Awareness Networking) discovery has started. + NanStarted, + /// NAN discovery has stopped. + NanStopped, + /// NAN service discovery match found. + NanSvcMatch, + /// Replied to a NAN peer with service discovery match. + NanReplied, + /// Received a follow-up message in NAN. + NanReceive, + /// Received NDP (Neighbor Discovery Protocol) request from a NAN peer. + NdpIndication, + /// NDP confirm indication. + NdpConfirm, + /// NAN datapath terminated indication. + NdpTerminated, + /// Wi-Fi home channel change, doesn't occur when scanning. + HomeChannelChange, + + /// Received Neighbor Report response. + StaNeighborRep, +}; + +var wifi_tx_in_flight: usize = 0; + +fn tx_done_cb( + _: u8, + _: [*c]u8, + _: [*c]u16, + _: bool, +) callconv(.c) void { + log.debug("tx_done_cb", .{}); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + if (wifi_tx_in_flight > 0) { + wifi_tx_in_flight -= 1; + } +} + +fn recv_cb_sta(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_err_t { + log.debug("recv_cb_sta {?} {} {?}", .{buf, len, eb}); + + return c.ESP_OK; +} + +fn recv_cb_ap(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_err_t { + _ = buf; // autofix + _ = len; // autofix + _ = eb; // autofix + + log.debug("recv_cb_ap", .{}); + + @panic("recv_cb_ap"); + + // return c.ESP_OK; +} + +// I pupulated this with the defaults from rust. Some of it should be configurable. +var init_config: c.wifi_init_config_t = .{ + .osi_funcs = &g_wifi_osi_funcs, + // .wpa_crypto_funcs = c.g_wifi_default_wpa_crypto_funcs, + .static_rx_buf_num = 3, //10, + .dynamic_rx_buf_num = 3, //32, + .tx_buf_type = c.CONFIG_ESP_WIFI_TX_BUFFER_TYPE, + .static_tx_buf_num = 0, + .dynamic_tx_buf_num = 3, //32, + .rx_mgmt_buf_type = c.CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER, + .rx_mgmt_buf_num = c.CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF, + .cache_tx_buf_num = c.WIFI_CACHE_TX_BUFFER_NUM, + .csi_enable = 0, // TODO: WiFi channel state information enable flag. + .ampdu_rx_enable = 1, + .ampdu_tx_enable = 1, + .amsdu_tx_enable = 0, + .nvs_enable = 0, + .nano_enable = 0, + .rx_ba_win = 6, + .wifi_task_core_id = 0, + .beacon_max_len = c.WIFI_SOFTAP_BEACON_MAX_LEN, + .mgmt_sbuf_num = c.WIFI_MGMT_SBUF_NUM, + .feature_caps = c.WIFI_FEATURE_CAPS, + .sta_disconnected_pm = false, + .espnow_max_encrypt_num = c.CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM, + .tx_hetb_queue_num = 3, + .dump_hesigb_enable = false, + .magic = c.WIFI_INIT_CONFIG_MAGIC, +}; + +const wifi_enable_wpa3_sae: u64 = 1 << 0; +const wifi_enable_enterprise: u64 = 1 << 7; +// const wifi_ftm_initiator: u64 = 1 << 2; +// const wifi_ftm_responder: u64 = 1 << 3; +// const wifi_enable_gcmp: u64 = 1 << 4; +// const wifi_enable_gmac: u64 = 1 << 5; +// const wifi_enable_11r: u64 = 1 << 6; + +export var g_wifi_feature_caps: u64 = wifi_enable_wpa3_sae | wifi_enable_enterprise; + +export var g_wifi_osi_funcs: c.wifi_osi_funcs_t = .{ + ._version = c.ESP_WIFI_OS_ADAPTER_VERSION, + ._env_is_chip = osi.env_is_chip, + ._set_intr = osi.set_intr, + ._clear_intr = osi.clear_intr, + ._set_isr = osi.set_isr, + ._ints_on = osi.ints_on, + ._ints_off = osi.ints_off, + ._is_from_isr = osi.is_from_isr, + ._spin_lock_create = osi.spin_lock_create, + ._spin_lock_delete = osi.spin_lock_delete, + ._wifi_int_disable = osi.wifi_int_disable, + ._wifi_int_restore = osi.wifi_int_restore, + ._task_yield_from_isr = osi.task_yield_from_isr, + ._semphr_create = osi.semphr_create, + ._semphr_delete = osi.semphr_delete, + ._semphr_take = osi.semphr_take, + ._semphr_give = osi.semphr_give, + ._wifi_thread_semphr_get = osi.wifi_thread_semphr_get, + ._mutex_create = osi.mutex_create, + ._recursive_mutex_create = osi.recursive_mutex_create, + ._mutex_delete = osi.mutex_delete, + ._mutex_lock = osi.mutex_lock, + ._mutex_unlock = osi.mutex_unlock, + ._queue_create = osi.queue_create, + ._queue_delete = osi.queue_delete, + ._queue_send = osi.queue_send, + ._queue_send_from_isr = osi.queue_send_from_isr, + ._queue_send_to_back = @ptrCast(&osi.queue_send_to_back), + ._queue_send_to_front = @ptrCast(&osi.queue_send_to_front), + ._queue_recv = osi.queue_recv, + ._queue_msg_waiting = osi.queue_msg_waiting, + ._event_group_create = @ptrCast(&osi.event_group_create), + ._event_group_delete = @ptrCast(&osi.event_group_delete), + ._event_group_set_bits = @ptrCast(&osi.event_group_set_bits), + ._event_group_clear_bits = @ptrCast(&osi.event_group_clear_bits), + ._event_group_wait_bits = @ptrCast(&osi.event_group_wait_bits), + ._task_create_pinned_to_core = osi.task_create_pinned_to_core, + ._task_create = osi.task_create, + ._task_delete = @ptrCast(&osi.task_delete), + ._task_delay = osi.task_delay, + ._task_ms_to_tick = osi.task_ms_to_tick, + ._task_get_current_task = osi.task_get_current_task, + ._task_get_max_priority = osi.task_get_max_priority, + ._malloc = osi.malloc, + ._free = osi.free, + ._event_post = osi.event_post, + ._get_free_heap_size = @ptrCast(&osi.get_free_heap_size), + ._rand = osi.rand, + ._dport_access_stall_other_cpu_start_wrap = osi.dport_access_stall_other_cpu_start_wrap, + ._dport_access_stall_other_cpu_end_wrap = osi.dport_access_stall_other_cpu_end_wrap, + ._wifi_apb80m_request = osi.wifi_apb80m_request, + ._wifi_apb80m_release = osi.wifi_apb80m_release, + ._phy_disable = osi.phy_disable, + ._phy_enable = osi.phy_enable, + ._phy_update_country_info = osi.phy_update_country_info, + ._read_mac = osi.read_mac, + ._timer_arm = osi.timer_arm, + ._timer_disarm = osi.timer_disarm, + ._timer_done = osi.timer_done, + ._timer_setfn = osi.timer_setfn, + ._timer_arm_us = osi.timer_arm_us, + ._wifi_reset_mac = osi.wifi_reset_mac, + ._wifi_clock_enable = osi.wifi_clock_enable, + ._wifi_clock_disable = osi.wifi_clock_disable, + ._wifi_rtc_enable_iso = @ptrCast(&osi.wifi_rtc_enable_iso), + ._wifi_rtc_disable_iso = @ptrCast(&osi.wifi_rtc_disable_iso), + ._esp_timer_get_time = osi.esp_timer_get_time, + ._nvs_set_i8 = @ptrCast(&osi.nvs_set_i8), + ._nvs_get_i8 = @ptrCast(&osi.nvs_get_i8), + ._nvs_set_u8 = @ptrCast(&osi.nvs_set_u8), + ._nvs_get_u8 = @ptrCast(&osi.nvs_get_u8), + ._nvs_set_u16 = @ptrCast(&osi.nvs_set_u16), + ._nvs_get_u16 = @ptrCast(&osi.nvs_get_u16), + ._nvs_open = @ptrCast(&osi.nvs_open), + ._nvs_close = @ptrCast(&osi.nvs_close), + ._nvs_commit = @ptrCast(&osi.nvs_commit), + ._nvs_set_blob = @ptrCast(&osi.nvs_set_blob), + ._nvs_get_blob = @ptrCast(&osi.nvs_get_blob), + ._nvs_erase_key = @ptrCast(&osi.nvs_erase_key), + ._get_random = osi.get_random, + ._get_time = @ptrCast(&osi.get_time), + ._random = &osi.random, + ._slowclk_cal_get = osi.slowclk_cal_get, + ._log_write = osi.log_write, + ._log_writev = osi.log_writev, + ._log_timestamp = osi.log_timestamp, + ._malloc_internal = osi.malloc_internal, + ._realloc_internal = @ptrCast(&osi.realloc_internal), + ._calloc_internal = osi.calloc_internal, + ._zalloc_internal = osi.zalloc_internal, + ._wifi_malloc = osi.wifi_malloc, + ._wifi_realloc = @ptrCast(&osi.wifi_realloc), + ._wifi_calloc = osi.wifi_calloc, + ._wifi_zalloc = osi.wifi_zalloc, + ._wifi_create_queue = osi.wifi_create_queue, + ._wifi_delete_queue = osi.wifi_delete_queue, + ._coex_init = osi.coex_init, + ._coex_deinit = osi.coex_deinit, + ._coex_enable = osi.coex_enable, + ._coex_disable = osi.coex_disable, + ._coex_status_get = osi.coex_status_get, + ._coex_condition_set = @ptrCast(&osi.coex_condition_set), + ._coex_wifi_request = osi.coex_wifi_request, + ._coex_wifi_release = osi.coex_wifi_release, + ._coex_wifi_channel_set = osi.coex_wifi_channel_set, + ._coex_event_duration_get = osi.coex_event_duration_get, + ._coex_pti_get = osi.coex_pti_get, + ._coex_schm_status_bit_clear = osi.coex_schm_status_bit_clear, + ._coex_schm_status_bit_set = osi.coex_schm_status_bit_set, + ._coex_schm_interval_set = osi.coex_schm_interval_set, + ._coex_schm_interval_get = osi.coex_schm_interval_get, + ._coex_schm_curr_period_get = osi.coex_schm_curr_period_get, + ._coex_schm_curr_phase_get = osi.coex_schm_curr_phase_get, + ._coex_schm_process_restart = osi.coex_schm_process_restart, + ._coex_schm_register_cb = osi.coex_schm_register_cb, + ._coex_register_start_cb = osi.coex_register_start_cb, + ._coex_schm_flexible_period_set = osi.coex_schm_flexible_period_set, + ._coex_schm_flexible_period_get = osi.coex_schm_flexible_period_get, + ._magic = @bitCast(c.ESP_WIFI_OS_ADAPTER_MAGIC), +}; + +pub fn c_result(err_code: i32) InternalError!void { + const InternalWifiError = enum(i32) { + /// Out of memory + esp_err_no_mem = 0x101, + + /// Invalid argument + esp_err_invalid_arg = 0x102, + + /// Wi_fi driver was not installed by esp_wifi_init + esp_err_wifi_not_init = 0x3001, + + /// Wi_fi driver was not started by esp_wifi_start + esp_err_wifi_not_started = 0x3002, + + /// Wi_fi driver was not stopped by esp_wifi_stop + esp_err_wifi_not_stopped = 0x3003, + + /// Wi_fi interface error + esp_err_wifi_if = 0x3004, + + /// Wi_fi mode error + esp_err_wifi_mode = 0x3005, + + /// Wi_fi internal state error + esp_err_wifi_state = 0x3006, + + /// Wi_fi internal control block of station or soft-AP error + esp_err_wifi_conn = 0x3007, + + /// Wi_fi internal NVS module error + esp_err_wifi_nvs = 0x3008, + + /// MAC address is invalid + esp_err_wifi_mac = 0x3009, + + /// SSID is invalid + esp_err_wifi_ssid = 0x300A, + + /// Password is invalid + esp_err_wifi_password = 0x300B, + + /// Timeout error + esp_err_wifi_timeout = 0x300C, + + /// Wi_fi is in sleep state(RF closed) and wakeup fail + esp_err_wifi_wake_fail = 0x300D, + + /// The caller would block + esp_err_wifi_would_block = 0x300E, + + /// Station still in disconnect status + esp_err_wifi_not_connect = 0x300F, + + /// Failed to post the event to Wi_fi task + esp_err_wifi_post = 0x3012, + + /// Invalid Wi_fi state when init/deinit is called + esp_err_wifi_init_state = 0x3013, + + /// Returned when Wi_fi is stopping + esp_err_wifi_stop_state = 0x3014, + + /// The Wi_fi connection is not associated + esp_err_wifi_not_assoc = 0x3015, + + /// The Wi_fi TX is disallowed + esp_err_wifi_tx_disallow = 0x3016, + }; + + if (err_code != c.ESP_OK) { + const err: InternalWifiError = @enumFromInt(err_code); + log.err("Internal wifi error occurred: {s}", .{@tagName(err)}); + return error.InternalError; + } +} + +pub const wifi_ap_config_t = extern struct { + ssid: [32]u8 = std.mem.zeroes([32]u8), + password: [64]u8 = std.mem.zeroes([64]u8), + ssid_len: u8 = std.mem.zeroes(u8), + channel: u8 = std.mem.zeroes(u8), + authmode: c.wifi_auth_mode_t = std.mem.zeroes(c.wifi_auth_mode_t), + ssid_hidden: u8 = std.mem.zeroes(u8), + max_connection: u8 = std.mem.zeroes(u8), + beacon_interval: u16 = std.mem.zeroes(u16), + csa_count: u8 = std.mem.zeroes(u8), + dtim_period: u8 = std.mem.zeroes(u8), + pairwise_cipher: c.wifi_cipher_type_t = std.mem.zeroes(c.wifi_cipher_type_t), + ftm_responder: bool = std.mem.zeroes(bool), + pmf_cfg: c.wifi_pmf_config_t = std.mem.zeroes(c.wifi_pmf_config_t), + sae_pwe_h2e: c.wifi_sae_pwe_method_t = std.mem.zeroes(c.wifi_sae_pwe_method_t), +}; + +pub const wifi_sta_config_t = extern struct { + // NOTE: maybe a little more imagination + pub const Packed1 = packed struct { + rm_enabled: bool, + btm_enabled: bool, + mbo_enabled: bool, + ft_enabled: bool, + owe_enabled: bool, + transition_disable: bool, + reserved: u26, + }; + + pub const Packed2 = packed struct { + he_dcm_set: u1, + he_dcm_max_constellation_tx: u2, + he_dcm_max_constellation_rx: u2, + he_mcs9_enabled: u1, + he_su_beamformee_disabled: u1, + he_trig_su_bmforming_feedback_disabled: u1, + he_trig_mu_bmforming_partial_feedback_disabled: u1, + he_trig_cqi_feedback_disable: u1, + he_reserved: u22, + }; + + ssid: [32]u8 = std.mem.zeroes([32]u8), + password: [64]u8 = std.mem.zeroes([64]u8), + scan_method: c.wifi_scan_method_t = std.mem.zeroes(c.wifi_scan_method_t), + bssid_set: bool = std.mem.zeroes(bool), + bssid: [6]u8 = std.mem.zeroes([6]u8), + channel: u8 = std.mem.zeroes(u8), + listen_interval: u16 = std.mem.zeroes(u16), + sort_method: c.wifi_sort_method_t = std.mem.zeroes(c.wifi_sort_method_t), + threshold: c.wifi_scan_threshold_t = std.mem.zeroes(c.wifi_scan_threshold_t), + pmf_cfg: c.wifi_pmf_config_t = std.mem.zeroes(c.wifi_pmf_config_t), + packed1: Packed1 = std.mem.zeroes(Packed1), + sae_pwe_h2e: c.wifi_sae_pwe_method_t = std.mem.zeroes(c.wifi_sae_pwe_method_t), + sae_pk_mode: c.wifi_sae_pk_mode_t = std.mem.zeroes(c.wifi_sae_pk_mode_t), + failure_retry_cnt: u8 = std.mem.zeroes(u8), + packed2: Packed2 = std.mem.zeroes(Packed2), + sae_h2e_identifier: [32]u8 = std.mem.zeroes([32]u8), +}; + +pub const wifi_nan_config_t = extern struct { + op_channel: u8 = std.mem.zeroes(u8), + master_pref: u8 = std.mem.zeroes(u8), + scan_time: u8 = std.mem.zeroes(u8), + warm_up_sec: u16 = std.mem.zeroes(u16), +}; + +pub const wifi_config_t = extern union { + ap: wifi_ap_config_t, + sta: wifi_sta_config_t, + nan: wifi_nan_config_t, +}; + +extern fn esp_wifi_set_config(interface: c.wifi_interface_t, conf: ?*wifi_config_t) c.esp_err_t; diff --git a/port/espressif/esp/src/hal/wifi.zig b/port/espressif/esp/src/hal/wifi.zig new file mode 100644 index 000000000..3c445bf8c --- /dev/null +++ b/port/espressif/esp/src/hal/wifi.zig @@ -0,0 +1,1752 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const c = @import("esp-wifi-driver"); +const microzig = @import("microzig"); +const TrapFrame = microzig.cpu.TrapFrame; +const time = microzig.drivers.time; +const peripherals = microzig.chip.peripherals; +const SYSTEM = peripherals.SYSTEM; +const RTC_CNTL = peripherals.RTC_CNTL; +const APB_CTRL = peripherals.APB_CTRL; +const hal = microzig.hal; +const systimer = hal.systimer; + +const log = std.log.scoped(.esp_wifi); // what should be the scope? + +// based on the rust crate esp-wifi: https://github.com/esp-rs/esp-hal/blob/main/esp-wifi + +pub const Error = error{ + OutOfMemory, + InternalWifiError, +}; + +pub fn init(allocator: Allocator) Error!void { + wifi_allocator = allocator; + + // TODO: check that clock frequency is higher or equal to 80mhz + + enable_wifi_power_domain_and_init_clocks(); + // phy_mem_init(); // only sets some global variable on esp32c3 + + microzig.cpu.interrupt.disable_interrupts(); + + setup_interrupts(); + setup_timer_periodic_alarm(); + try init_main_task(allocator); + + const task = try Task.create(allocator, timer_task, null, 8096); + schedule_task(task); + + microzig.cpu.interrupt.enable_interrupts(); + + // This would populate the main task's context. + // NOTE: we could probably remove this and wait for the first preemption interrupt + yield_task(); + + // NOTE: wifi driver log level should be configurable. + try c_result(c.esp_wifi_internal_set_log_level(c.WIFI_LOG_VERBOSE)); +} + +pub const wifi_controller = struct { + pub fn init() Error!void { + init_config.wpa_crypto_funcs = c.g_wifi_default_wpa_crypto_funcs; + + // coex init + + // TODO: if coex enabled + if (false) try c_result(c.coex_init()); + + try c_result(c.esp_wifi_init_internal(&init_config)); + + try c_result(c.esp_wifi_set_mode(c.WIFI_MODE_NULL)); + + try c_result(c.esp_supplicant_init()); + + // try c_result(c.esp_wifi_set_tx_done_cb(tx_done_cb)); + // + // try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_STA, recv_cb_sta)); + // try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_AP, recv_cb_ap)); + + { + // TODO: config + const country_code: [3]u8 = .{ 'C', 'N', 0 }; + const country: c.wifi_country_t = .{ + .cc = country_code, + .schan = 1, + .nchan = 13, + .max_tx_power = 20, + .policy = c.WIFI_COUNTRY_POLICY_MANUAL, + }; + try c_result(c.esp_wifi_set_country(&country)); + } + + try c_result(c.esp_wifi_start()); + } + + // I pupulated this with the defaults from rust. Some of it should be configurable. + var init_config: c.wifi_init_config_t = .{ + .osi_funcs = &osi_functions.table, + // .wpa_crypto_funcs = c.g_wifi_default_wpa_crypto_funcs, + .static_rx_buf_num = 10, + .dynamic_rx_buf_num = 32, + .tx_buf_type = c.CONFIG_ESP_WIFI_TX_BUFFER_TYPE, + .static_tx_buf_num = 0, + .dynamic_tx_buf_num = 32, + .rx_mgmt_buf_type = c.CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF, + .rx_mgmt_buf_num = c.CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF, + .cache_tx_buf_num = c.WIFI_CACHE_TX_BUFFER_NUM, + .csi_enable = 0, // TODO: WiFi channel state information enable flag. + .ampdu_rx_enable = 1, + .ampdu_tx_enable = 1, + .amsdu_tx_enable = 0, + .nvs_enable = 0, + .nano_enable = 0, + .rx_ba_win = 6, + .wifi_task_core_id = 0, + .beacon_max_len = c.WIFI_SOFTAP_BEACON_MAX_LEN, + .mgmt_sbuf_num = c.WIFI_MGMT_SBUF_NUM, + .feature_caps = wifi_feature_caps, + .sta_disconnected_pm = false, + .espnow_max_encrypt_num = c.CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM, + .tx_hetb_queue_num = 3, + .dump_hesigb_enable = false, + .magic = c.WIFI_INIT_CONFIG_MAGIC, + }; + + const wifi_enable_wpa3_sae: u64 = 1 << 0; + const wifi_enable_enterprise: u64 = 1 << 7; + // const wifi_ftm_initiator: u64 = 1 << 2; + // const wifi_ftm_responder: u64 = 1 << 3; + // const wifi_enable_gcmp: u64 = 1 << 4; + // const wifi_enable_gmac: u64 = 1 << 5; + // const wifi_enable_11r: u64 = 1 << 6; + + const wifi_feature_caps: u64 = wifi_enable_wpa3_sae | wifi_enable_enterprise; +}; + +fn enable_wifi_power_domain_and_init_clocks() void { + const system_wifibb_rst: u32 = 1 << 0; + const system_fe_rst: u32 = 1 << 1; + const system_wifimac_rst: u32 = 1 << 2; + const system_btbb_rst: u32 = 1 << 3; // bluetooth baseband + const system_btmac_rst: u32 = 1 << 4; // deprecated + const system_rw_btmac_rst: u32 = 1 << 9; // bluetooth mac + const system_rw_btmac_reg_rst: u32 = 1 << 11; // bluetooth mac registers + const system_btbb_reg_rst: u32 = 1 << 13; // bluetooth baseband registers + + const modem_reset_field_when_pu: u32 = system_wifibb_rst | + system_fe_rst | + system_wifimac_rst | + system_btbb_rst | + system_btmac_rst | + system_rw_btmac_rst | + system_rw_btmac_reg_rst | + system_btbb_reg_rst; + + RTC_CNTL.DIG_PWC.modify(.{ + .WIFI_FORCE_PD = 0, + .BT_FORCE_PD = 0, + }); + + APB_CTRL.WIFI_RST_EN.write(.{ .WIFI_RST = APB_CTRL.WIFI_RST_EN.read().WIFI_RST | modem_reset_field_when_pu }); + APB_CTRL.WIFI_RST_EN.write(.{ .WIFI_RST = APB_CTRL.WIFI_RST_EN.read().WIFI_RST & ~modem_reset_field_when_pu }); + + RTC_CNTL.DIG_ISO.modify(.{ + .WIFI_FORCE_ISO = 0, + .BT_FORCE_ISO = 0, + }); + + const system_wifi_clk_i2c_clk_en: u32 = 1 << 5; + const system_wifi_clk_unused_bit12: u32 = 1 << 12; + const wifi_bt_sdio_clk: u32 = system_wifi_clk_i2c_clk_en | system_wifi_clk_unused_bit12; + const system_wifi_clk_en: u32 = 0x00FB9FCF; + + APB_CTRL.WIFI_CLK_EN.write(.{ + .WIFI_CLK_EN = APB_CTRL.WIFI_CLK_EN.read().WIFI_CLK_EN & + ~wifi_bt_sdio_clk | + system_wifi_clk_en, + }); +} + +fn setup_interrupts() void { + // TODO: which interrupts are used should be configurable. + + microzig.cpu.interrupt.set_priority_threshold(.zero); + + microzig.cpu.interrupt.map(.wifi_mac, .interrupt1); + microzig.cpu.interrupt.map(.wifi_pwr, .interrupt1); + microzig.cpu.interrupt.map(.systimer_target0, .interrupt2); + microzig.cpu.interrupt.map(.from_cpu_intr0, .interrupt3); + + inline for (&.{ .interrupt1, .interrupt2, .interrupt3 }) |int| { + microzig.cpu.interrupt.set_type(int, .level); + microzig.cpu.interrupt.set_priority(int, .lowest); + } + + inline for (&.{ .interrupt2, .interrupt3 }) |int| { + microzig.cpu.interrupt.enable(int); + } +} + +const timer_alarm: systimer.Alarm = .alarm0; +const preemt_interval: time.Duration = .from_ms(10); + +fn setup_timer_periodic_alarm() void { + // unit0 is already enabled as it is used by `hal.time`. + timer_alarm.set_unit(.unit0); + + // sets the period to one second. + timer_alarm.set_period(@intCast(preemt_interval.to_us() * systimer.ticks_per_us())); + + // to enable period mode you have to first clear the mode bit. + timer_alarm.set_mode(.target); + timer_alarm.set_mode(.period); + + timer_alarm.set_interrupt_enabled(true); + timer_alarm.set_enabled(true); +} + +const Task = struct { + trap_frame: TrapFrame, + stack: []u8, + semaphore: u32 = 0, + next: *Task, + + pub fn create( + allocator: Allocator, + entry: *const fn (param: ?*anyopaque) callconv(.c) noreturn, + param: ?*anyopaque, + stack_size: usize, + ) !*Task { + const stack: []u8 = try allocator.alloc(u8, stack_size); + errdefer allocator.free(stack); + + const task: *Task = try allocator.create(Task); + errdefer allocator.destroy(task); + + const trap_frame: TrapFrame = blk: { + const stack_top_addr: usize = @intFromPtr(stack.ptr) + stack.len; + + var frame: TrapFrame = undefined; + @memset(std.mem.asBytes(&frame), 0); + + frame.pc = @intFromPtr(entry); + frame.a0 = @intFromPtr(param); + frame.sp = stack_top_addr - stack_top_addr % 16; + + break :blk frame; + }; + + task.* = .{ + .trap_frame = trap_frame, + .stack = stack, + .next = task, // loop back to this task + }; + + return task; + } + + pub fn destroy(task: *Task, allocator: Allocator) void { + allocator.free(task.stack); + allocator.destroy(task); + } +}; + +var main_task: *Task = undefined; +/// SAFETY: we don't need optionals because there will always be the main task +/// running. It can't be deleted. +var current_task: *Task = undefined; + +fn init_main_task(allocator: Allocator) !void { + const task: *Task = try allocator.create(Task); + task.* = .{ + .trap_frame = undefined, + .stack = &.{}, + .next = task, // loop back to this task + }; + current_task = task; + main_task = task; +} + +fn schedule_task(task: *Task) void { + task.next = current_task.next; + current_task.next = task; +} + +fn switch_task(trap_frame: *TrapFrame) void { + copy_context(¤t_task.trap_frame, trap_frame); + + // check if we need to delete any task + + current_task = current_task.next; + + copy_context(trap_frame, ¤t_task.trap_frame); +} + +fn copy_context(dst: *TrapFrame, src: *const TrapFrame) void { + // don't restore csrs + const size = @offsetOf(TrapFrame, "mstatus"); + @memcpy(std.mem.asBytes(dst)[0..size], std.mem.asBytes(src)[0..size]); +} + +fn yield_task() void { + SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ + .CPU_INTR_FROM_CPU_0 = 1, + }); +} + +const Timer = struct { + ets_timer: *c.ets_timer, + deadline: time.Deadline, + periodic: ?time.Duration, +}; + +const TimerList = std.SinglyLinkedList(Timer); +const TimerListNode = TimerList.Node; + +var timer_list: TimerList = .{}; + +fn add_timer(allocator: Allocator, ets_timer: *c.ets_timer) !void { + const timer: Timer = .{ + .ets_timer = ets_timer, + .deadline = .init_absolute(null), + .periodic = null, + }; + + const node = try allocator.create(TimerListNode); + node.* = .{ + .data = timer, + }; + timer_list.prepend(node); +} + +fn find_timer(ets_timer: *c.ets_timer) ?*Timer { + var current_node = timer_list.first; + while (current_node) |node| : (current_node = node.next) { + const timer = &node.data; + if (timer.ets_timer == ets_timer) { + return timer; + } + } + return null; +} + +fn find_next_timer_due(now: time.Absolute) ?*Timer { + var current_node = timer_list.first; + while (current_node) |node| : (current_node = node.next) { + const timer = &node.data; + if (timer.deadline.is_reached_by(now)) { + return timer; + } + } + return null; +} + +fn timer_task(_: ?*anyopaque) callconv(.c) noreturn { + while (true) { + const now = hal.time.get_time_since_boot(); + + const maybe_call, const arg = blk: { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + if (find_next_timer_due(now)) |timer| { + if (timer.periodic) |period| { + timer.deadline = .init_relative(now, period); + } else { + timer.deadline = .init_absolute(null); + } + + break :blk .{ timer.ets_timer.func, timer.ets_timer.priv }; + } else { + break :blk .{ null, null }; + } + }; + + if (maybe_call) |callback| { + callback(arg); + } else { + yield_task(); + } + } +} + +var wifi_interrupt_handler: struct { + f: *const fn (?*anyopaque) callconv(.c) void, + arg: ?*anyopaque, +} = undefined; + +pub const interrupt_handlers = struct { + pub fn wifi_xxx(_: *TrapFrame) linksection(".trap") callconv(.c) void { + log.debug("interrupt WIFI_xxx {} {?}", .{ + wifi_interrupt_handler.f, + wifi_interrupt_handler.arg, + }); + + wifi_interrupt_handler.f(wifi_interrupt_handler.arg); + } + + pub fn timer(trap_frame: *TrapFrame) linksection(".trap") callconv(.c) void { + switch_task(trap_frame); + + timer_alarm.clear_interrupt(); + } + + pub fn software(trap_frame: *TrapFrame) linksection(".trap") callconv(.c) void { + switch_task(trap_frame); + + SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ + .CPU_INTR_FROM_CPU_0 = 0, + }); + } +}; + +var wifi_allocator: Allocator = undefined; + +fn allocator_create_in_cs(comptime T: type) Error!*T { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + return wifi_allocator.create(T); +} + +fn allocator_destroy_in_cs(memory: anytype) void { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + wifi_allocator.destroy(memory); +} + +fn allocator_alloc_in_cs(comptime T: type, n: usize) Allocator.Error![]T { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + return wifi_allocator.alloc(T, n); +} + +fn allocator_free_in_cs(memory: anytype) void { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + return wifi_allocator.free(memory); +} + +extern fn vsnprintf(buffer: [*c]u8, len: usize, fmt: [*c]const u8, va_list: std.builtin.VaList) callconv(.c) void; + +const log_wifi_internal = std.log.scoped(.esp_wifi_internal); + +fn syslog(fmt: [*c]const u8, va_list: std.builtin.VaList) callconv(.c) void { + var buf: [512]u8 = undefined; + vsnprintf(&buf, 512, fmt, va_list); + log_wifi_internal.debug("{s}", .{&buf}); +} + +const c_other_exports = struct { + pub export var WIFI_EVENT: c.esp_event_base_t = "WIFI_EVENT"; +}; + +const c_functions = struct { + pub export fn strlen(ptr: ?*anyopaque) callconv(.c) usize { + return std.mem.len(@as([*:0]const u8, @ptrCast(ptr))); + } + + pub export fn strnlen(ptr: ?*anyopaque, _: usize) callconv(.c) usize { + // TODO Actually provide a complete implementation. + return std.mem.len(@as([*:0]const u8, @ptrCast(ptr))); + } + + pub export fn malloc(len: usize) callconv(.c) ?*anyopaque { + const maybe_alloc = blk: { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + break :blk wifi_allocator.rawAlloc(@sizeOf(usize) + len, .@"4", @returnAddress()); + }; + + if (maybe_alloc) |alloc| { + const alloc_len: *usize = @alignCast(@ptrCast(alloc)); + alloc_len.* = len; + + return @ptrFromInt(@intFromPtr(alloc) + @sizeOf(usize)); + } else { + return null; + } + } + + pub export fn calloc(number: usize, size: usize) callconv(.c) ?*anyopaque { + const total_size: usize = number * size; + if (malloc(total_size)) |ptr| { + @memset(@as([*]u8, @ptrCast(ptr))[0..total_size], 0); + return ptr; + } + return null; + } + + pub export fn free(ptr: ?*anyopaque) callconv(.c) void { + std.debug.assert(ptr != null); + + const alloc: [*]u8 = @ptrFromInt(@intFromPtr(ptr) - 4); + const alloc_len: *usize = @alignCast(@ptrCast(alloc)); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + wifi_allocator.rawFree(alloc[0 .. 4 + alloc_len.*], .@"4", @returnAddress()); + } + + pub export fn _putchar(byte: u8) callconv(.c) void { + // NOTE: not interrupt safe + + const static = struct { + var buf: [256]u8 = undefined; + var bytes_written: usize = 0; + }; + + static.buf[static.bytes_written] = byte; + static.bytes_written += 1; + + if (static.bytes_written >= 256) { + log_wifi_internal.debug("_putchar: {s}", .{&static.buf}); + static.bytes_written = 0; + } + } + + pub export fn puts(ptr: ?*anyopaque) callconv(.c) void { + // NOTE: not interrupt safe + + const s: []const u8 = std.mem.span(@as([*:0]const u8, @ptrCast(ptr))); + log_wifi_internal.debug("{s}", .{s}); + } + + pub export fn rtc_printf(fmt: [*c]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); + } + + pub export fn phy_printf(fmt: [*c]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); + } + + pub export fn coexist_printf(fmt: [*c]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); + } + + pub export fn net80211_printf(fmt: [*c]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); + } + + pub export fn pp_printf(fmt: [*c]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); + } +}; + +comptime { + std.testing.refAllDecls(c_functions); + std.testing.refAllDecls(c_other_exports); +} + +const osi_functions = struct { + pub var table: c.wifi_osi_funcs_t = .{ + ._version = c.ESP_WIFI_OS_ADAPTER_VERSION, + ._env_is_chip = osi_functions.env_is_chip, + ._set_intr = osi_functions.set_intr, + ._clear_intr = osi_functions.clear_intr, + ._set_isr = osi_functions.set_isr, + ._ints_on = osi_functions.ints_on, + ._ints_off = osi_functions.ints_off, + ._is_from_isr = osi_functions.is_from_isr, + ._spin_lock_create = osi_functions.spin_lock_create, + ._spin_lock_delete = osi_functions.spin_lock_delete, + ._wifi_int_disable = osi_functions.wifi_int_disable, + ._wifi_int_restore = osi_functions.wifi_int_restore, + ._task_yield_from_isr = osi_functions.task_yield_from_isr, + ._semphr_create = osi_functions.semphr_create, + ._semphr_delete = osi_functions.semphr_delete, + ._semphr_take = osi_functions.semphr_take, + ._semphr_give = osi_functions.semphr_give, + ._wifi_thread_semphr_get = osi_functions.wifi_thread_semphr_get, + ._mutex_create = osi_functions.mutex_create, + ._recursive_mutex_create = osi_functions.recursive_mutex_create, + ._mutex_delete = osi_functions.mutex_delete, + ._mutex_lock = osi_functions.mutex_lock, + ._mutex_unlock = osi_functions.mutex_unlock, + ._queue_create = osi_functions.queue_create, + ._queue_delete = osi_functions.queue_delete, + ._queue_send = osi_functions.queue_send, + ._queue_send_from_isr = osi_functions.queue_send_from_isr, + ._queue_send_to_back = @ptrCast(&osi_functions.queue_send_to_back), + ._queue_send_to_front = @ptrCast(&osi_functions.queue_send_to_front), + ._queue_recv = osi_functions.queue_recv, + ._queue_msg_waiting = osi_functions.queue_msg_waiting, + ._event_group_create = @ptrCast(&osi_functions.event_group_create), + ._event_group_delete = @ptrCast(&osi_functions.event_group_delete), + ._event_group_set_bits = @ptrCast(&osi_functions.event_group_set_bits), + ._event_group_clear_bits = @ptrCast(&osi_functions.event_group_clear_bits), + ._event_group_wait_bits = @ptrCast(&osi_functions.event_group_wait_bits), + ._task_create_pinned_to_core = osi_functions.task_create_pinned_to_core, + ._task_create = osi_functions.task_create, + ._task_delete = @ptrCast(&osi_functions.task_delete), + ._task_delay = osi_functions.task_delay, + ._task_ms_to_tick = osi_functions.task_ms_to_tick, + ._task_get_current_task = osi_functions.task_get_current_task, + ._task_get_max_priority = osi_functions.task_get_max_priority, + ._malloc = osi_functions.malloc, + ._free = &osi_functions.free, + ._event_post = @ptrCast(&osi_functions.event_post), + ._get_free_heap_size = @ptrCast(&osi_functions.get_free_heap_size), + ._rand = osi_functions.rand, + ._dport_access_stall_other_cpu_start_wrap = osi_functions.dport_access_stall_other_cpu_start_wrap, + ._dport_access_stall_other_cpu_end_wrap = osi_functions.dport_access_stall_other_cpu_end_wrap, + ._wifi_apb80m_request = osi_functions.wifi_apb80m_request, + ._wifi_apb80m_release = osi_functions.wifi_apb80m_release, + ._phy_disable = osi_functions.phy_disable, + ._phy_enable = osi_functions.phy_enable, + ._phy_update_country_info = osi_functions.phy_update_country_info, + ._read_mac = osi_functions.read_mac, + ._timer_arm = osi_functions.timer_arm, + ._timer_disarm = osi_functions.timer_disarm, + ._timer_done = @ptrCast(&osi_functions.timer_done), + ._timer_setfn = @ptrCast(&osi_functions.timer_setfn), + ._timer_arm_us = osi_functions.timer_arm_us, + ._wifi_reset_mac = osi_functions.wifi_reset_mac, + ._wifi_clock_enable = osi_functions.wifi_clock_enable, + ._wifi_clock_disable = osi_functions.wifi_clock_disable, + ._wifi_rtc_enable_iso = @ptrCast(&osi_functions.wifi_rtc_enable_iso), + ._wifi_rtc_disable_iso = @ptrCast(&osi_functions.wifi_rtc_disable_iso), + ._esp_timer_get_time = osi_functions.esp_timer_get_time, + ._nvs_set_i8 = @ptrCast(&osi_functions.nvs_set_i8), + ._nvs_get_i8 = @ptrCast(&osi_functions.nvs_get_i8), + ._nvs_set_u8 = @ptrCast(&osi_functions.nvs_set_u8), + ._nvs_get_u8 = @ptrCast(&osi_functions.nvs_get_u8), + ._nvs_set_u16 = @ptrCast(&osi_functions.nvs_set_u16), + ._nvs_get_u16 = @ptrCast(&osi_functions.nvs_get_u16), + ._nvs_open = @ptrCast(&osi_functions.nvs_open), + ._nvs_close = @ptrCast(&osi_functions.nvs_close), + ._nvs_commit = @ptrCast(&osi_functions.nvs_commit), + ._nvs_set_blob = @ptrCast(&osi_functions.nvs_set_blob), + ._nvs_get_blob = @ptrCast(&osi_functions.nvs_get_blob), + ._nvs_erase_key = @ptrCast(&osi_functions.nvs_erase_key), + ._get_random = osi_functions.get_random, + ._get_time = @ptrCast(&osi_functions.get_time), + ._random = &osi_functions.random, + ._slowclk_cal_get = osi_functions.slowclk_cal_get, + ._log_write = osi_functions.log_write, + ._log_writev = osi_functions.log_writev, + ._log_timestamp = osi_functions.log_timestamp, + ._malloc_internal = osi_functions.malloc_internal, + ._realloc_internal = @ptrCast(&osi_functions.realloc_internal), + ._calloc_internal = osi_functions.calloc_internal, + ._zalloc_internal = osi_functions.zalloc_internal, + ._wifi_malloc = osi_functions.wifi_malloc, + ._wifi_realloc = @ptrCast(&osi_functions.wifi_realloc), + ._wifi_calloc = osi_functions.wifi_calloc, + ._wifi_zalloc = osi_functions.wifi_zalloc, + ._wifi_create_queue = osi_functions.wifi_create_queue, + ._wifi_delete_queue = osi_functions.wifi_delete_queue, + ._coex_init = @ptrCast(&osi_functions.coex_init), + ._coex_deinit = @ptrCast(&osi_functions.coex_deinit), + ._coex_enable = @ptrCast(&osi_functions.coex_enable), + ._coex_disable = @ptrCast(&osi_functions.coex_disable), + ._coex_status_get = @ptrCast(&osi_functions.coex_status_get), + ._coex_condition_set = null, + ._coex_wifi_request = @ptrCast(&osi_functions.coex_wifi_request), + ._coex_wifi_release = @ptrCast(&osi_functions.coex_wifi_release), + ._coex_wifi_channel_set = @ptrCast(&osi_functions.coex_wifi_channel_set), + ._coex_event_duration_get = @ptrCast(&osi_functions.coex_event_duration_get), + ._coex_pti_get = @ptrCast(&osi_functions.coex_pti_get), + ._coex_schm_status_bit_clear = @ptrCast(&osi_functions.coex_schm_status_bit_clear), + ._coex_schm_status_bit_set = @ptrCast(&osi_functions.coex_schm_status_bit_set), + ._coex_schm_interval_set = @ptrCast(&osi_functions.coex_schm_interval_set), + ._coex_schm_interval_get = @ptrCast(&osi_functions.coex_schm_interval_get), + ._coex_schm_curr_period_get = @ptrCast(&osi_functions.coex_schm_curr_period_get), + ._coex_schm_curr_phase_get = @ptrCast(&osi_functions.coex_schm_curr_phase_get), + ._coex_schm_process_restart = @ptrCast(&osi_functions.coex_schm_process_restart), + ._coex_schm_register_cb = @ptrCast(&osi_functions.coex_schm_register_cb), + ._coex_register_start_cb = @ptrCast(&osi_functions.coex_register_start_cb), + ._coex_schm_flexible_period_set = @ptrCast(&osi_functions.coex_schm_flexible_period_set), + ._coex_schm_flexible_period_get = @ptrCast(&osi_functions.coex_schm_flexible_period_get), + ._magic = @bitCast(c.ESP_WIFI_OS_ADAPTER_MAGIC), + }; + + pub fn env_is_chip() callconv(.c) bool { + return true; + } + + pub fn set_intr(cpu_no: i32, intr_source: u32, intr_num: u32, intr_prio: i32) callconv(.c) void { + // TODO: maybe assert here argument values contain the values bellow + + log.debug("set_intr {} {} {} {}", .{ cpu_no, intr_source, intr_num, intr_prio }); + + // esp32c3 + // these are already set up + // possible calls: + // INFO - set_intr 0 2 1 1 (WIFI_PWR) + // INFO - set_intr 0 0 1 1 (WIFI_MAC) + } + + pub fn clear_intr(intr_source: u32, intr_num: u32) callconv(.c) void { + log.debug("clear_intr {} {}", .{ intr_source, intr_num }); + } + + pub fn set_isr( + n: i32, + f: ?*anyopaque, + arg: ?*anyopaque, + ) callconv(.c) void { + log.debug("set_isr {} {?} {?}", .{ n, f, arg }); + + switch (n) { + 0, 1 => { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + wifi_interrupt_handler = .{ + .f = @alignCast(@ptrCast(f)), + .arg = arg, + }; + }, + else => @panic("invalid interrupt number"), + } + + microzig.cpu.interrupt.enable(.interrupt1); + } + + // NOTE: temp until I see what bits are set. + pub fn ints_on(mask: u32) callconv(.c) void { + std.debug.panic("ints_on {}", .{mask}); + } + + pub fn ints_off(mask: u32) callconv(.c) void { + std.debug.panic("ints_off {}", .{mask}); + } + + pub fn is_from_isr() callconv(.c) bool { + return true; + } + + var FAKE_SPINLOCK: u8 = 1; + pub fn spin_lock_create() callconv(.c) ?*anyopaque { + log.debug("spin_lock_create", .{}); + return &FAKE_SPINLOCK; + } + + pub fn spin_lock_delete(_: ?*anyopaque) callconv(.c) void { + log.debug("spin_lock_delete", .{}); + } + + // NOTE: critical section for now. If we decide to run the code on multiple cpus (on other chips) we + // should change this. + pub fn wifi_int_disable(_: ?*anyopaque) callconv(.c) u32 { + log.debug("wifi_int_disable", .{}); + + const ints_enabled = microzig.cpu.interrupt.globally_enabled(); + if (ints_enabled) { + microzig.cpu.interrupt.disable_interrupts(); + } + return @intFromBool(ints_enabled); + } + + pub fn wifi_int_restore(_: ?*anyopaque, state: u32) callconv(.c) void { + log.debug("wifi_int_restore", .{}); + + // NOTE: is this required? + microzig.cpu.fence(); + + // check if interrupts were enabled before. + if (state != 0) { + microzig.cpu.interrupt.enable_interrupts(); + } + } + + pub fn task_yield_from_isr() callconv(.c) void { + log.debug("task_yield_from_isr", .{}); + + yield_task(); + } + + pub fn semphr_create(max_value: u32, init_value: u32) callconv(.c) ?*anyopaque { + log.debug("semphr_create {} {}", .{ max_value, init_value }); + + const sem = allocator_create_in_cs(u32) catch { + log.warn("failed to allocate semaphore", .{}); + return null; + }; + sem.* = init_value; + + return sem; + } + + pub fn semphr_delete(ptr: ?*anyopaque) callconv(.c) void { + log.debug("semphr_delete {?}", .{ptr}); + + allocator_destroy_in_cs(@as(*u32, @alignCast(@ptrCast(ptr)))); + } + + pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { + log.debug("semphr_take {?} {}", .{ ptr, tick }); + + const forever = tick == c.OSI_FUNCS_TIME_BLOCKING; + const timeout = tick; + const start = hal.time.get_time_since_boot(); + + const sem: *u32 = @alignCast(@ptrCast(ptr)); + + while (true) { + const res: bool = blk: { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + microzig.cpu.fence(); + + const cnt = sem.*; + if (cnt > 0) { + sem.* = cnt - 1; + break :blk true; + } else { + break :blk false; + } + }; + + if (res) { + return 1; + } + + if (!forever and hal.time.get_time_since_boot().diff(start).to_us() > timeout) { + break; + } + + yield_task(); + } + + return 0; + } + + pub fn semphr_give(ptr: ?*anyopaque) callconv(.c) i32 { + log.debug("semphr_give {?}", .{ptr}); + + const sem: *u32 = @alignCast(@ptrCast(ptr)); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + const cnt = sem.*; + sem.* = cnt + 1; + return 1; + } + + pub fn wifi_thread_semphr_get() callconv(.c) ?*anyopaque { + return @ptrCast(¤t_task.semaphore); + } + + const Mutex = struct { + locking_task: usize, + count: u32, + recursive: bool, + }; + + // TODO: idk if we're gonna need to implement this + pub fn mutex_create() callconv(.c) ?*anyopaque { + @panic("mutex_create: not implemented"); + } + + pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { + log.debug("recursive_mutex_create", .{}); + + const mutex = allocator_create_in_cs(Mutex) catch { + log.warn("failed to allocate recursive mutex", .{}); + return null; + }; + mutex.* = .{ + .locking_task = 0xffff_ffff, + .count = 0, + .recursive = true, + }; + return mutex; + } + + pub fn mutex_delete(ptr: ?*anyopaque) callconv(.c) void { + log.debug("mutex_delete {?}", .{ptr}); + + const mutex: *Mutex = @alignCast(@ptrCast(ptr)); + allocator_destroy_in_cs(mutex); + } + + pub fn mutex_lock(ptr: ?*anyopaque) callconv(.c) i32 { + log.debug("mutex lock {?}", .{ptr}); + + const mutex: *Mutex = @alignCast(@ptrCast(ptr)); + const cur_task_id: usize = @intFromPtr(current_task); + + while (true) { + const mutex_locked = blk: { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + if (mutex.count == 0) { + mutex.locking_task = cur_task_id; + mutex.count += 1; + break :blk true; + } else if (mutex.locking_task == cur_task_id) { + mutex.count += 1; + break :blk true; + } else { + break :blk false; + } + }; + + microzig.cpu.fence(); + + if (mutex_locked) { + return 1; + } + + yield_task(); + } + } + + pub fn mutex_unlock(ptr: ?*anyopaque) callconv(.c) i32 { + log.debug("mutex unlock {?}", .{ptr}); + + const mutex: *Mutex = @alignCast(@ptrCast(ptr)); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + microzig.cpu.fence(); + + if (mutex.count > 0) { + mutex.count -= 1; + return 1; + } else { + return 0; + } + } + + const Queue = struct { + capacity: usize, + item_len: usize, + read_index: usize = 0, + write_index: usize = 0, + storage: []u8, + + pub fn create(allocator: Allocator, capacity: usize, item_len: usize) error{OutOfMemory}!*Queue { + const queue = try allocator.create(Queue); + errdefer allocator.destroy(queue); + + queue.* = .{ + .capacity = capacity, + .item_len = item_len, + .read_index = 0, + .write_index = 0, + .storage = try allocator.alloc(u8, capacity * item_len), + }; + + return queue; + } + + pub fn destroy(self: *Queue, allocator: Allocator) void { + allocator.free(self.storage); + allocator.destroy(self); + } + + pub fn len(self: Queue) usize { + if (self.write_index >= self.read_index) { + return self.write_index - self.read_index; + } else { + return self.capacity - self.read_index + self.write_index; + } + } + + pub fn get(self: Queue, index: usize) []u8 { + const item_start = self.item_len * index; + return self.storage[item_start..][0..self.item_len]; + } + + pub fn enqueue(self: *Queue, item: [*]const u8) error{QueueFull}!void { + if (self.len() == self.capacity) { + return error.QueueFull; + } + + const slot = self.get(self.write_index); + @memcpy(slot, item[0..self.item_len]); + self.write_index = (self.write_index + 1) % self.capacity; + } + + pub fn dequeue(self: *Queue, item: [*]u8) error{QueueEmpty}!void { + if (self.len() == 0) { + return error.QueueEmpty; + } + + const slot = self.get(self.read_index); + @memcpy(item[0..self.item_len], slot); + self.read_index = (self.read_index + 1) % self.capacity; + } + }; + + pub fn queue_create(capacity: u32, item_len: u32) callconv(.c) ?*anyopaque { + log.debug("queue_create {} {}", .{ capacity, item_len }); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + const queue = Queue.create(wifi_allocator, capacity, item_len) catch { + log.warn("failed to allocate queue", .{}); + return null; + }; + + return queue; + } + + pub fn queue_delete(ptr: ?*anyopaque) callconv(.c) void { + log.debug("queue_delete {?}", .{ptr}); + + const queue: *Queue = @alignCast(@ptrCast(ptr)); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + queue.destroy(wifi_allocator); + } + + // NOTE: here we ignore the timeout. The rust version doesn't use it. + fn queue_send_common(ptr: ?*anyopaque, item_ptr: ?*anyopaque) callconv(.c) i32 { + const queue: *Queue = @alignCast(@ptrCast(ptr)); + const item: [*]const u8 = @alignCast(@ptrCast(item_ptr)); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + queue.enqueue(item) catch { + log.warn("failed to add item to queue", .{}); + return 0; + }; + + return 1; + } + + pub fn queue_send(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) callconv(.c) i32 { + log.debug("queue_send {?} {?} {}", .{ ptr, item_ptr, block_time_tick }); + + return queue_send_common(ptr, item_ptr); + } + + pub fn queue_send_from_isr(ptr: ?*anyopaque, item_ptr: ?*anyopaque, _hptw: ?*anyopaque) callconv(.c) i32 { + log.debug("queue_send_from_isr {?} {?} {?}", .{ ptr, item_ptr, _hptw }); + + @as(*u32, @alignCast(@ptrCast(_hptw))).* = 1; + return queue_send_common(ptr, item_ptr); + } + + pub fn queue_send_to_back() callconv(.c) void { + @panic("queue_send_to_back: not implemented"); + } + + pub fn queue_send_to_front() callconv(.c) void { + @panic("queue_send_to_front: not implemented"); + } + + pub fn queue_recv(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) callconv(.c) i32 { + log.debug("queue_recv {?} {?} {}", .{ ptr, item_ptr, block_time_tick }); + + const forever = block_time_tick == c.OSI_FUNCS_TIME_BLOCKING; + const timeout = block_time_tick; + const start = hal.time.get_time_since_boot(); + + const queue: *Queue = @alignCast(@ptrCast(ptr)); + const item: [*]u8 = @alignCast(@ptrCast(item_ptr)); + + while (true) { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + if (queue.dequeue(item)) |_| { + return 1; + } else |_| {} + + if (!forever and hal.time.get_time_since_boot().diff(start).to_us() > timeout) { + return -1; + } + + yield_task(); + } + } + + pub fn queue_msg_waiting(ptr: ?*anyopaque) callconv(.c) u32 { + log.debug("queue_msg_waiting {?}", .{ptr}); + + const queue: *Queue = @alignCast(@ptrCast(ptr)); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + return queue.len(); + } + + pub fn event_group_create() callconv(.c) void { + @panic("event_group_create: not implemented"); + } + + pub fn event_group_delete() callconv(.c) void { + @panic("event_group_delete: not implemented"); + } + + pub fn event_group_set_bits() callconv(.c) void { + @panic("event_group_set_bits: not implemented"); + } + + pub fn event_group_clear_bits() callconv(.c) void { + @panic("event_group_clear_bits: not implemented"); + } + + pub fn event_group_wait_bits() callconv(.c) void { + @panic("event_group_wait_bits: not implemented"); + } + + fn task_create_common( + task_func: ?*anyopaque, + name: [*c]const u8, + stack_depth: u32, + param: ?*anyopaque, + prio: u32, + task_handle: ?*anyopaque, + core_id: u32, + ) i32 { + _ = name; // autofix + _ = prio; // autofix + _ = core_id; // autofix + + const cs = microzig.interrupt.enter_critical_section(); + cs.leave(); + + const task: *Task = Task.create( + wifi_allocator, + @alignCast(@ptrCast(task_func)), + param, + stack_depth, + ) catch { + log.warn("failed to create task", .{}); + return 0; + }; + schedule_task(task); + + @as(*usize, @alignCast(@ptrCast(task_handle))).* = @intFromPtr(task); + + return 1; + } + + pub fn task_create_pinned_to_core( + task_func: ?*anyopaque, + name: [*c]const u8, + stack_depth: u32, + param: ?*anyopaque, + prio: u32, + task_handle: ?*anyopaque, + core_id: u32, + ) callconv(.c) i32 { + log.debug("task_create_pinned_to_core {?} {s} {} {?} {} {?} {}", .{ + task_func, + name, + stack_depth, + param, + prio, + task_handle, + core_id, + }); + + return task_create_common(task_func, name, stack_depth, param, prio, task_handle, core_id); + } + + pub fn task_create( + task_func: ?*anyopaque, + name: [*c]const u8, + stack_depth: u32, + param: ?*anyopaque, + prio: u32, + task_handle: ?*anyopaque, + ) callconv(.c) i32 { + log.debug("task_create {?} {s} {} {?} {} {?}", .{ + task_func, + name, + stack_depth, + param, + prio, + task_handle, + }); + + return task_create_common(task_func, name, stack_depth, param, prio, task_handle, 0); + } + + pub fn task_delete() callconv(.c) void { + @panic("task_delete: not implemented"); + } + + pub fn task_delay(tick: u32) callconv(.c) void { + log.debug("task_delay {}", .{tick}); + + const start = hal.time.get_time_since_boot(); + const delay: time.Duration = .from_us(tick); + + while (hal.time.get_time_since_boot().diff(start).less_than(delay)) { + yield_task(); + } + } + + // NOTE: we could probably use milliseconds directly. + pub fn task_ms_to_tick(ms: u32) callconv(.c) i32 { + // NOTE: idk about bitcast here. Seems weird that it returns i32. + return @intCast(ms * 1_000); + } + + pub fn task_get_current_task() callconv(.c) ?*anyopaque { + return current_task; + } + + pub fn task_get_max_priority() callconv(.c) i32 { + return -1; + } + + pub const malloc = c_functions.malloc; + pub const free = c_functions.free; + + pub fn event_post() callconv(.c) void { + @panic("event_post: not implemented"); + } + + pub fn get_free_heap_size() callconv(.c) void { + @panic("get_free_heap_size: not implemented"); + } + + pub fn rand() callconv(.c) u32 { + return hal.rng.random_u32(); + } + + pub fn dport_access_stall_other_cpu_start_wrap() callconv(.c) void { + log.debug("dport_access_stall_other_cpu_start_wrap", .{}); + } + + pub fn dport_access_stall_other_cpu_end_wrap() callconv(.c) void { + log.debug("dport_access_stall_other_cpu_end_wrap", .{}); + } + + pub fn wifi_apb80m_request() callconv(.c) void { + log.debug("wifi_apb80m_request", .{}); + } + + pub fn wifi_apb80m_release() callconv(.c) void { + log.debug("wifi_apb80m_release", .{}); + } + + fn phy_set_enabled(enable: bool) void { + log.debug("phy_set_enabled", .{}); + + const system_wifi_clk_wifi_bt_common_m: u32 = 0x78078F; + + var wifi_clk_en = APB_CTRL.WIFI_CLK_EN.read().WIFI_CLK_EN; + if (enable) { + wifi_clk_en |= system_wifi_clk_wifi_bt_common_m; + } else { + wifi_clk_en &= ~system_wifi_clk_wifi_bt_common_m; + } + APB_CTRL.WIFI_CLK_EN.write(.{ .WIFI_CLK_EN = wifi_clk_en }); + } + + pub fn phy_disable() callconv(.c) void { + log.debug("phy_disable", .{}); + phy_set_enabled(false); + } + + pub fn phy_enable() callconv(.c) void { + log.debug("phy_enable", .{}); + phy_set_enabled(true); + } + + pub fn phy_update_country_info(country: [*c]const u8) callconv(.c) c_int { + log.debug("phy_update_country_info {s}", .{country}); + return -1; + } + + pub fn read_mac(mac: [*c]u8, typ: c_uint) callconv(.c) c_int { + log.debug("read_mac {*} {}", .{ mac, typ }); + + const EFUSE = microzig.chip.peripherals.EFUSE; + + const low_32_bits: u32 = EFUSE.RD_MAC_SPI_SYS_0.read().MAC_0; + const high_16_bits: u16 = EFUSE.RD_MAC_SPI_SYS_1.read().MAC_1; + @memcpy(mac[0..4], std.mem.asBytes(&low_32_bits)); + @memcpy(mac[4..6], std.mem.asBytes(&high_16_bits)); + + // idk what this means + switch (typ) { + // esp_mac_wifi_softap + 1 => { + const tmp = mac[0]; + var i: u6 = 0; + while (i < 64) : (i += 1) { + mac[0] |= 0x02; + mac[0] ^= @intCast(i << 2); + + if (mac[0] != tmp) { + break; + } + } + }, + // esp_mac_bt + 2 => { + const tmp = mac[0]; + var i: u6 = 0; + while (i < 64) : (i += 1) { + mac[0] |= 0x02; + mac[0] ^= @intCast(i << 2); + + if (mac[0] != tmp) { + break; + } + } + mac[5] += 1; + }, + else => {}, + } + + return 0; + } + + pub fn timer_arm(ets_timer_ptr: ?*anyopaque, ms: u32, repeat: bool) callconv(.c) void { + timer_arm_us(ets_timer_ptr, ms * 1_000, repeat); + } + + pub fn timer_disarm(ets_timer_ptr: ?*anyopaque) callconv(.c) void { + log.debug("timer_disarm {?}", .{ets_timer_ptr}); + + const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + if (find_timer(ets_timer)) |timer| { + timer.deadline = .init_absolute(null); + } else { + log.warn("timer not found based on ets_timer", .{}); + } + } + + pub fn timer_done() callconv(.c) void { + @panic("timer_done: not implemented"); + } + + pub fn timer_setfn(ets_timer_ptr: ?*anyopaque, callback_ptr: ?*anyopaque, arg: ?*anyopaque) callconv(.c) void { + log.debug("timer_setfn {?} {?} {?}", .{ ets_timer_ptr, callback_ptr, arg }); + + const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); + + ets_timer.func = @alignCast(@ptrCast(callback_ptr)); + ets_timer.priv = arg; + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + if (find_timer(ets_timer)) |tim| { + tim.deadline = .init_absolute(null); + } else { + add_timer(wifi_allocator, ets_timer) catch { + log.warn("failed to allocate timer", .{}); + }; + } + } + + pub fn timer_arm_us(ets_timer_ptr: ?*anyopaque, us: u32, repeat: bool) callconv(.c) void { + log.debug("timer_arm_us {?} {} {}", .{ ets_timer_ptr, us, repeat }); + + const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + if (find_timer(ets_timer)) |timer| { + const period: time.Duration = .from_us(us); + timer.deadline = .init_relative(hal.time.get_time_since_boot(), period); + timer.periodic = if (repeat) period else null; + } else { + log.warn("timer not found based on ets_timer", .{}); + } + } + + pub fn wifi_reset_mac() callconv(.c) void { + log.debug("wifi_reset_mac", .{}); + + APB_CTRL.WIFI_RST_EN.write(.{ .WIFI_RST = APB_CTRL.WIFI_RST_EN.read().WIFI_RST | (1 << 2) }); + APB_CTRL.WIFI_RST_EN.write(.{ .WIFI_RST = APB_CTRL.WIFI_RST_EN.read().WIFI_RST & ~@as(u32, 1 << 2) }); + } + + pub fn wifi_clock_enable() callconv(.c) void { + log.debug("wifi_clock_enable", .{}); + + // no op on esp32c3 + } + + pub fn wifi_clock_disable() callconv(.c) void { + log.debug("wifi_clock_disable", .{}); + + // no op on esp32c3 + } + + pub fn wifi_rtc_enable_iso() callconv(.c) void { + @panic("wifi_rtc_enable_iso: not implemented"); + } + + pub fn wifi_rtc_disable_iso() callconv(.c) void { + @panic("wifi_rtc_disable_iso: not implemented"); + } + + pub fn esp_timer_get_time() callconv(.c) i64 { + log.debug("esp_timer_get_time", .{}); + + // TODO: or bit cast? + return @intCast(hal.time.get_time_since_boot().to_us()); + } + + pub fn nvs_set_i8() callconv(.c) void { + @panic("nvs_set_i8: not implemented"); + } + + pub fn nvs_get_i8() callconv(.c) void { + @panic("nvs_get_i8: not implemented"); + } + + pub fn nvs_set_u8() callconv(.c) void { + @panic("nvs_set_u8: not implemented"); + } + + pub fn nvs_get_u8() callconv(.c) void { + @panic("nvs_get_u8: not implemented"); + } + + pub fn nvs_set_u16() callconv(.c) void { + @panic("nvs_set_u16: not implemented"); + } + + pub fn nvs_get_u16() callconv(.c) void { + @panic("nvs_get_u16: not implemented"); + } + + pub fn nvs_open() callconv(.c) void { + @panic("nvs_open: not implemented"); + } + + pub fn nvs_close() callconv(.c) void { + @panic("nvs_close: not implemented"); + } + + pub fn nvs_commit() callconv(.c) void { + @panic("nvs_commit: not implemented"); + } + + pub fn nvs_set_blob() callconv(.c) void { + @panic("nvs_set_blob: not implemented"); + } + + pub fn nvs_get_blob() callconv(.c) void { + @panic("nvs_get_blob: not implemented"); + } + + pub fn nvs_erase_key() callconv(.c) void { + @panic("nvs_erase_key: not implemented"); + } + + pub fn get_random(buf: [*c]u8, len: usize) callconv(.c) c_int { + hal.rng.read(buf[0..len]); + return 0; + } + + pub fn get_time() callconv(.c) void { + @panic("get_time: not implemented"); + } + + pub fn random() callconv(.c) c_ulong { + return hal.rng.random_u32(); + } + + pub fn slowclk_cal_get() callconv(.c) u32 { + // NOTE: esp32c3 specific + return 28639; + } + + pub fn log_write(_: c_uint, _: [*c]const u8, fmt: [*c]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); + } + + pub fn log_writev(_: c_uint, _: [*c]const u8, fmt: [*c]const u8, va_list: c.va_list) callconv(.c) void { + syslog(fmt, @ptrCast(va_list)); + } + + pub fn log_timestamp() callconv(.c) u32 { + return @truncate(hal.time.get_time_since_boot().to_us() * 1_000); + } + + pub const malloc_internal = malloc; + + pub fn realloc_internal() callconv(.c) void { + @panic("realloc_internal: not implemented"); + } + + pub const calloc_internal = c_functions.calloc; + + pub fn zalloc_internal(len: usize) callconv(.c) ?*anyopaque { + if (malloc(len)) |ptr| { + @memset(@as([*]u8, @ptrCast(ptr))[0..len], 0); + return ptr; + } + + return null; + } + + pub const wifi_malloc = malloc; + + pub fn wifi_realloc() callconv(.c) void { + @panic("wifi_realloc: not implemented"); + } + + pub const wifi_calloc = c_functions.calloc; + pub const wifi_zalloc = zalloc_internal; + + var wifi_queue_handle: *Queue = undefined; + + pub fn wifi_create_queue(capacity: c_int, item_len: c_int) callconv(.c) ?*anyopaque { + log.debug("wifi_create_queue {} {}", .{ capacity, item_len }); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + wifi_queue_handle = Queue.create(wifi_allocator, @intCast(capacity), @intCast(item_len)) catch { + log.warn("failed to allocate wifi queue", .{}); + return null; + }; + + return @ptrCast(&wifi_queue_handle); + } + + pub fn wifi_delete_queue(ptr: ?*anyopaque) callconv(.c) void { + log.debug("wifi_delete_queue", .{}); + + const queue: *Queue = @alignCast(@ptrCast(ptr)); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + queue.destroy(wifi_allocator); + } + const coex_enabled = false; + + pub fn coex_init() callconv(.c) c_int { + log.debug("coex_init", .{}); + + return if (coex_enabled) c.coex_init() else 0; + } + + pub fn coex_deinit() callconv(.c) void { + log.debug("coex_deinit", .{}); + + if (coex_enabled) c.coex_deinit(); + } + + pub fn coex_enable() callconv(.c) c_int { + log.debug("coex_enable", .{}); + + return if (coex_enabled) c.coex_enable() else 0; + } + + pub fn coex_disable() callconv(.c) void { + log.debug("coex_disable", .{}); + + if (coex_enabled) c.coex_disable(); + } + + pub fn coex_status_get() callconv(.c) u32 { + log.debug("coex_status_get", .{}); + + return if (coex_enabled) c.coex_status_get() else 0; + } + + pub fn coex_wifi_request( + event: u32, + latency: u32, + duration: u32, + ) callconv(.c) c_int { + log.debug("coex_wifi_request", .{}); + + return if (coex_enabled) c.coex_wifi_request(event, latency, duration) else 0; + } + + pub fn coex_wifi_release(event: u32) callconv(.c) c_int { + log.debug("coex_wifi_release", .{}); + + return if (coex_enabled) c.coex_wifi_release(event) else 0; + } + + pub fn coex_wifi_channel_set( + primary: u8, + secondary: u8, + ) callconv(.c) c_int { + log.debug("coex_wifi_channel_set", .{}); + + return if (coex_enabled) c.coex_wifi_channel_set(primary, secondary) else 0; + } + + pub fn coex_event_duration_get(event: u32, duration: [*c]u32) callconv(.c) c_int { + log.debug("coex_event_duration_get", .{}); + + return if (coex_enabled) c.coex_event_duration_get(event, duration) else 0; + } + + pub fn coex_pti_get(event: u32, pti: [*c]u8) callconv(.c) c_int { + log.debug("coex_pti_get", .{}); + + return if (coex_enabled) c.coex_pti_get(event, pti) else 0; + } + + pub fn coex_schm_status_bit_clear(@"type": u32, status: u32) callconv(.c) void { + log.debug("coex_schm_status_bit_clear", .{}); + + if (coex_enabled) c.coex_schm_status_bit_clear(@"type", status); + } + + pub fn coex_schm_status_bit_set(@"type": u32, status: u32) callconv(.c) void { + log.debug("coex_schm_status_bit_set", .{}); + + if (coex_enabled) c.coex_schm_status_bit_set(@"type", status); + } + + pub fn coex_schm_interval_set(interval: u32) callconv(.c) c_int { + log.debug("coex_schm_interval_set", .{}); + + return if (coex_enabled) c.coex_schm_interval_set(interval) else 0; + } + + pub fn coex_schm_interval_get() callconv(.c) u32 { + log.debug("coex_schm_interval_get", .{}); + + return if (coex_enabled) c.coex_schm_interval_get() else 0; + } + + pub fn coex_schm_curr_period_get() callconv(.c) u8 { + log.debug("coex_schm_curr_period_get", .{}); + + return if (coex_enabled) c.coex_schm_curr_period_get() else 0; + } + + pub fn coex_schm_curr_phase_get() callconv(.c) ?*anyopaque { + log.debug("coex_schm_curr_phase_get", .{}); + + return if (coex_enabled) c.coex_schm_curr_phase_get() else null; + } + + pub fn coex_schm_process_restart() callconv(.c) c_int { + log.debug("coex_schm_process_restart", .{}); + + return if (coex_enabled) c.coex_schm_process_restart() else 0; + } + + pub fn coex_schm_register_cb( + @"type": c_int, + callback: ?*const fn (c_int) callconv(.c) c_int, + ) callconv(.c) c_int { + log.debug("coex_schm_register_cb", .{}); + + return if (coex_enabled) c.coex_schm_register_callback(@"type", callback) else 0; + } + + pub fn coex_register_start_cb(cb: ?*const fn () callconv(.c) c_int) callconv(.c) c_int { + log.debug("coex_register_start_cb", .{}); + + return if (coex_enabled) c.coex_register_start_cb(cb) else 0; + } + + extern fn ext_coex_schm_flexible_period_set(period: u8) i32; + pub fn coex_schm_flexible_period_set(period: u8) callconv(.c) c_int { + log.debug("coex_schm_flexible_period_set {}", .{period}); + + return if (coex_enabled) ext_coex_schm_flexible_period_set(period) else 0; + } + + extern fn ext_coex_schm_flexible_period_get() u8; + pub fn coex_schm_flexible_period_get() callconv(.c) u8 { + log.debug("coex_schm_flexible_period_get", .{}); + + return if (coex_enabled) ext_coex_schm_flexible_period_get() else 0; + } +}; + +fn c_result(err_code: i32) Error!void { + const InternalWifiError = enum(i32) { + /// Out of memory + esp_err_no_mem = 0x101, + + /// Invalid argument + esp_err_invalid_arg = 0x102, + + /// Wi_fi driver was not installed by esp_wifi_init + esp_err_wifi_not_init = 0x3001, + + /// Wi_fi driver was not started by esp_wifi_start + esp_err_wifi_not_started = 0x3002, + + /// Wi_fi driver was not stopped by esp_wifi_stop + esp_err_wifi_not_stopped = 0x3003, + + /// Wi_fi interface error + esp_err_wifi_if = 0x3004, + + /// Wi_fi mode error + esp_err_wifi_mode = 0x3005, + + /// Wi_fi internal state error + esp_err_wifi_state = 0x3006, + + /// Wi_fi internal control block of station or soft-AP error + esp_err_wifi_conn = 0x3007, + + /// Wi_fi internal NVS module error + esp_err_wifi_nvs = 0x3008, + + /// MAC address is invalid + esp_err_wifi_mac = 0x3009, + + /// SSID is invalid + esp_err_wifi_ssid = 0x300A, + + /// Password is invalid + esp_err_wifi_password = 0x300B, + + /// Timeout error + esp_err_wifi_timeout = 0x300C, + + /// Wi_fi is in sleep state(RF closed) and wakeup fail + esp_err_wifi_wake_fail = 0x300D, + + /// The caller would block + esp_err_wifi_would_block = 0x300E, + + /// Station still in disconnect status + esp_err_wifi_not_connect = 0x300F, + + /// Failed to post the event to Wi_fi task + esp_err_wifi_post = 0x3012, + + /// Invalid Wi_fi state when init/deinit is called + esp_err_wifi_init_state = 0x3013, + + /// Returned when Wi_fi is stopping + esp_err_wifi_stop_state = 0x3014, + + /// The Wi_fi connection is not associated + esp_err_wifi_not_assoc = 0x3015, + + /// The Wi_fi TX is disallowed + esp_err_wifi_tx_disallow = 0x3016, + }; + + if (err_code != c.ESP_OK) { + const err: InternalWifiError = @enumFromInt(err_code); + log.err("Internal wifi error occurred: {s}", .{@tagName(err)}); + return error.InternalWifiError; + } +} diff --git a/tools/printer/src/root.zig b/tools/printer/src/root.zig index 5781e0a00..bddb82166 100644 --- a/tools/printer/src/root.zig +++ b/tools/printer/src/root.zig @@ -134,7 +134,7 @@ fn output_source_line( out_tty_config: std.io.tty.Config, src_loc: DebugInfo.ResolvedSourceLocation, ) !void { - var dir = try std.fs.cwd().openDir(src_loc.dir_path, .{}); + var dir = std.fs.cwd().openDir(src_loc.dir_path, .{}) catch return; defer dir.close(); const file = dir.openFile(src_loc.file_path, .{}) catch return; From e7c4f7623720be7a0d2e373797522a364eb83426 Mon Sep 17 00:00:00 2001 From: tact1m4n3 Date: Mon, 14 Jul 2025 20:53:09 +0300 Subject: [PATCH 02/43] cleanup examples build script --- examples/espressif/esp/build.zig | 95 ++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 23 deletions(-) diff --git a/examples/espressif/esp/build.zig b/examples/espressif/esp/build.zig index 126e779be..8a1b64955 100644 --- a/examples/espressif/esp/build.zig +++ b/examples/espressif/esp/build.zig @@ -12,13 +12,7 @@ pub fn build(b: *std.Build) void { const mz_dep = b.dependency("microzig", .{}); const mb = MicroBuild.init(b, mz_dep) orelse return; - const targets = [_]TargetDescription{ - .{ .prefix = "esp32_c3", .target = mb.ports.esp.chips.esp32_c3 }, - .{ .prefix = "esp32_c3_direct_boot", .target = mb.ports.esp.chips.esp32_c3_direct_boot }, - .{ .prefix = "esp32_c3_flashless", .target = mb.ports.esp.chips.esp32_c3_flashless }, - }; - - const available_examples = [_]Example{ + const examples: []const Example = &.{ .{ .name = "blinky", .file = "src/blinky.zig" }, .{ .name = "custom_clock_config", .file = "src/custom_clock_config.zig" }, .{ .name = "gpio_input", .file = "src/gpio_input.zig" }, @@ -31,13 +25,25 @@ pub fn build(b: *std.Build) void { .{ .name = "ws2812_blinky", .file = "src/ws2812_blinky.zig" }, }; - for (available_examples) |example| { + const targeted_examples: []const TargetedExample = &.{ + .{ + .target = .esp32_c3, + .example = .{ + .name = "wifi", + .file = "src/wifi.zig", + }, + }, + }; + + for (examples) |example| { // If we specify example, only select the ones that match if (maybe_example) |selected_example| if (!std.mem.containsAtLeast(u8, example.name, 1, selected_example)) continue; - for (targets) |target_desc| { + for (std.enums.values(TargetEnum)) |target_enum| { + const target_desc = target_enum.get_target_desc(mb); + // `add_firmware` basically works like addExecutable, but takes a // `microzig.Target` for target instead of a `std.zig.CrossTarget`. // @@ -61,24 +67,62 @@ pub fn build(b: *std.Build) void { } } - if (maybe_example) |selected_example| { - if (std.mem.containsAtLeast(u8, "wifi", 1, selected_example)) { - // only works with image boot target for now (it should also - // support direct boot in the future). The flashless target doesn't - // have enough space to run this. - const wifi_example_firmware = mb.add_firmware(.{ - .name = "wifi", - .target = mb.ports.esp.chips.esp32_c3, - .optimize = optimize, - .root_source_file = b.path("src/wifi.zig"), - }); + for (targeted_examples) |targeted_example| { + const target_desc = targeted_example.target.get_target_desc(mb); + const example = targeted_example.example; - mb.install_firmware(wifi_example_firmware, .{}); - mb.install_firmware(wifi_example_firmware, .{ .format = .elf }); - } + // `add_firmware` basically works like addExecutable, but takes a + // `microzig.Target` for target instead of a `std.zig.CrossTarget`. + // + // The target will convey all necessary information on the chip, + // cpu and potentially the board as well. + const firmware = mb.add_firmware(.{ + .name = b.fmt("{s}_{s}", .{ target_desc.prefix, example.name }), + .target = target_desc.target, + .optimize = optimize, + .root_source_file = b.path(example.file), + }); + + // `installFirmware()` is the MicroZig pendant to `Build.installArtifact()` + // and allows installing the firmware as a typical firmware file. + // + // This will also install into `$prefix/firmware` instead of `$prefix/bin`. + mb.install_firmware(firmware, .{}); + + // For debugging, we also always install the firmware as an ELF file + mb.install_firmware(firmware, .{ .format = .elf }); } } +const TargetEnum = enum { + const all: []TargetEnum = &.{ + .esp32_c3, + .esp32_c3_direct_boot, + .esp32_c3_flashless, + }; + + esp32_c3, + esp32_c3_direct_boot, + esp32_c3_flashless, + + fn get_target_desc(target_enum: TargetEnum, mb: *MicroBuild) TargetDescription { + return switch (target_enum) { + .esp32_c3 => .{ + .prefix = "esp32_c3", + .target = mb.ports.esp.chips.esp32_c3, + }, + .esp32_c3_direct_boot => .{ + .prefix = "esp32_c3_direct_boot", + .target = mb.ports.esp.chips.esp32_c3_direct_boot, + }, + .esp32_c3_flashless => .{ + .prefix = "esp32_c3_flashless", + .target = mb.ports.esp.chips.esp32_c3_flashless, + }, + }; + } +}; + const TargetDescription = struct { prefix: []const u8, target: *const microzig.Target, @@ -88,3 +132,8 @@ const Example = struct { name: []const u8, file: []const u8, }; + +const TargetedExample = struct { + target: TargetEnum, + example: Example, +}; From 2e6979ed99c6db62602f05569acdf072d84b1fd0 Mon Sep 17 00:00:00 2001 From: tact1m4n3 Date: Mon, 14 Jul 2025 23:01:02 +0300 Subject: [PATCH 03/43] cleanup --- examples/espressif/esp/src/wifi.zig | 29 ++--------------------- port/espressif/esp/src/hal/radio/wifi.zig | 23 ++++++++++++++++++ 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index 352437eb2..25ca48030 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -21,7 +21,7 @@ pub const microzig_options: microzig.Options = .{ .level = .err, }, }, - .logFn = log, + .logFn = usb_serial_jtag.logger.log, .interrupts = .{ .interrupt1 = radio.interrupt_handlers.wifi_xxx, .interrupt2 = radio.interrupt_handlers.timer, @@ -29,33 +29,9 @@ pub const microzig_options: microzig.Options = .{ }, }; -pub fn log( - comptime level: std.log.Level, - comptime scope: @TypeOf(.EnumLiteral), - comptime format: []const u8, - args: anytype, -) void { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - usb_serial_jtag.logger.log(level, scope, format, args); -} - var buffer: [50 * 1024]u8 = undefined; pub fn main() !void { - // microzig.cpu.interrupt.map(.assist_debug, .interrupt1); - // microzig.cpu.interrupt.set_priority(.interrupt1, .lowest); - // microzig.cpu.interrupt.enable(.interrupt1); - // microzig.cpu.interrupt.enable_interrupts(); - // - // microzig.chip.peripherals.ASSIST_DEBUG.CORE_0_SP_MAX.write_raw(0xffffffff); - // microzig.chip.peripherals.ASSIST_DEBUG.CORE_0_SP_MIN.write_raw(0x3FC7C000 + 400 * 1024 - 10); - // microzig.chip.peripherals.ASSIST_DEBUG.CORE_0_INTR_ENA.modify(.{ - // .CORE_0_SP_SPILL_MIN_INTR_ENA = 1, - // .CORE_0_SP_SPILL_MAX_INTR_ENA = 1, - // }); - var fba: std.heap.FixedBufferAllocator = .init(&buffer); const allocator = fba.threadSafeAllocator(); @@ -70,8 +46,7 @@ pub fn main() !void { }); try radio.wifi.start(); - - try radio.wifi.c_result(radio.wifi.c.esp_wifi_connect()); + try radio.wifi.connect(); // var ssid: [1:0]u8 = @splat(0); // var bssid: [1:0]u8 = @splat(0); diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index db177b7bb..aa3e9507a 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -298,6 +298,10 @@ pub fn start() InternalError!void { try c_result(c.esp_wifi_start()); } +pub fn connect() InternalError!void { + try c_result(c.esp_wifi_connect()); +} + pub const Event = enum(i32) { /// Wi-Fi is ready for operation. WifiReady = 0, @@ -401,6 +405,25 @@ pub const Event = enum(i32) { StaNeighborRep, }; +// const StaState = enum { +// none, +// sta_started, +// sta_connected, +// sta_disconnected, +// sta_stopped, +// }; +// +// var sta_state: StaState = .none; +// +// /// Internal method called on each event post. +// pub fn update_state(event: Event) void { +// switch (event) { +// +// } +// } + +// TODO: ApState + var wifi_tx_in_flight: usize = 0; fn tx_done_cb( From 3de76a151326ee872e84b715d62f98c274054c93 Mon Sep 17 00:00:00 2001 From: tact1m4n3 Date: Thu, 17 Jul 2025 16:44:28 +0300 Subject: [PATCH 04/43] got ip via dhcp --- core/src/allocator.zig | 643 ++++++++++++++++++ core/src/microzig.zig | 1 + examples/espressif/esp/.gdb_history | 15 - examples/espressif/esp/build.zig | 154 +++++ examples/espressif/esp/build.zig.zon | 5 + examples/espressif/esp/src/include/arch/cc.h | 31 + examples/espressif/esp/src/include/lwip.h | 9 + examples/espressif/esp/src/include/lwipopts.h | 67 ++ examples/espressif/esp/src/wifi.zig | 193 +++++- modules/foundation-libc/build.zig | 1 + modules/foundation-libc/include/stdio.h | 6 + modules/foundation-libc/include/stdlib.h | 3 + modules/foundation-libc/test/build.zig | 2 +- port/espressif/esp/build.zig | 11 +- port/espressif/esp/build.zig.zon | 1 + .../esp/ld/esp32_c3/image_boot_sections.ld | 8 +- port/espressif/esp/src/cpus/esp_riscv.zig | 3 +- port/espressif/esp/src/hal/radio/osi.zig | 33 +- port/espressif/esp/src/hal/radio/wifi.zig | 131 +++- 19 files changed, 1239 insertions(+), 78 deletions(-) create mode 100644 core/src/allocator.zig delete mode 100644 examples/espressif/esp/.gdb_history create mode 100644 examples/espressif/esp/src/include/arch/cc.h create mode 100644 examples/espressif/esp/src/include/lwip.h create mode 100644 examples/espressif/esp/src/include/lwipopts.h create mode 100644 modules/foundation-libc/include/stdio.h diff --git a/core/src/allocator.zig b/core/src/allocator.zig new file mode 100644 index 000000000..5a2109ba3 --- /dev/null +++ b/core/src/allocator.zig @@ -0,0 +1,643 @@ +const std = @import("std"); +const microzig = @import("microzig.zig"); + +const Alignment = std.mem.Alignment; + +extern var microzig_heap_start: u8; +extern var microzig_heap_end: u8; + +/// The number of free lists we maintain. +const free_list_count: usize = 6; + +const Alloc = @This(); + +/// The lists of free chunks. +free_lists: [free_list_count]?*Chunk = @splat(null), + +/// The beginning address of the memory region that can be allocated from. +low_boundary: usize, + +/// The next address beyond the highest address of the memory region can allocated from. +high_boundary: usize, + +/// An optional fallback allocator that is used when the allocator runs out of memory. +/// This allows allocation from multiple disjoint memory regions. +fallback: ?*Alloc = null, + +/// Return a []u8 slice that contains the memory located between the +/// microzig_heap_start and microzig_heap_end. This is the RAM that +/// is not used by any static allocations. It relies on the linker script +/// to define the microzig_heap_start and microzig_heap_end symbols +/// +/// In the default configurations, the stack will occupy the end of this +/// memory area. The `reserve` parameter is used to reduce the size of the +/// returned slice to reserve some memory for the stack's exclusive use. +/// +/// Parameters: +/// - `reserve`: The number of bytes to omit at the end of the heap. +/// +pub fn heap(reserve: usize) []u8 { + const heapPtr: [*]u8 = @ptrCast(µzig_heap_start); + const heap_len: usize = @intFromPtr(µzig_heap_end) - @intFromPtr(µzig_heap_start) - reserve; + return heapPtr[0..heap_len]; +} + +/// Set up an allocator by adding all the memory that is not otherwise used by the program. +/// +/// In normal configurations, the heap allocations will grow down from the start of the heap to +/// the end of memory, while the stack will grow from up from the end of memory. To help prevent +/// the heap and stack from overlapping, the allocator can reserve a small amount of memory at the +/// end of the heap for the stack's exclusive use. +/// +/// Example of use: +/// ``` +/// // Get a heap allocator instance reserving 1024 bytes for the stack. +/// var heap_allocator = microzig.Allocator.init_with_heap(1024); +/// +/// // Get the std.mem.Allocator from the heap allocator. +/// const allocator : std.mem.Allocator = heap_allocator.allocator(); +/// ``` +/// +/// Parameters: +/// - `reserve`: The number of bytes to omit at the end of the heap. +/// +pub fn init_with_heap(reserve: usize) Alloc { + return init_with_buffer(heap(reserve)); +} + +/// Set up an allocator using the supplied buffer. +/// +/// Example of use: +/// ``` +/// const buffer: [4096]u8 = undefined; +/// +/// // Get a buffer allocator instance reserving 1024 bytes for the stack. +/// var buffer_allocator = microzig.Allocator.init_with_buffer(buffer); +/// +/// // Get the std.mem.Allocator from the buffer allocator. +/// const allocator : std.mem.Allocator = buffer_allocator.allocator(); +/// ``` +/// +/// Parameters: +/// - `buffer`: The buffer to use for allocation. +/// +pub fn init_with_buffer(buffer: []u8) Alloc { + var self = Alloc{ + .low_boundary = Chunk.alignment.forward(@intFromPtr(buffer.ptr)), + .high_boundary = Chunk.alignment.backward(@intFromPtr(buffer.ptr + buffer.len) - Chunk.header_size), + }; + + // Create the initial chunk with all the space as free memory. + + const init_chunk: *Chunk = @ptrFromInt(self.low_boundary); + + init_chunk.previous_size = 0; + init_chunk._size = self.high_boundary - self.low_boundary; + init_chunk.next_free = null; + init_chunk.prior_free = null; + + self.free_lists[free_index_for_size(init_chunk.size())] = init_chunk; + + return self; +} + +/// Returns a std.mem.Allocator for the allocator +pub fn allocator(self: *Alloc) std.mem.Allocator { + return .{ + .ptr = self, + .vtable = &vtable, + }; +} + +/// Returns the total amount of free memory in the heap. +pub fn free_heap(self: *Alloc) usize { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + var total: usize = 0; + for (0..free_list_count) |i| { + var c = self.free_lists[i]; + while (c) |chunk| { + total += chunk.size() - Chunk.header_size; + c = chunk.next_free; + } + } + return total; +} + +/// Returns the largest length that can be currently allocated. +pub fn max_alloc_size(self: *Alloc) usize { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + var maximum: usize = 0; + for (0..free_list_count) |i| { + var c = self.free_lists[i]; + while (c) |chunk| { + maximum = @max(maximum, chunk.size() - Chunk.header_size); + c = chunk.next_free; + } + } + return maximum; +} + +/// The allocator's virtual table +const vtable: std.mem.Allocator.VTable = + .{ + .alloc = do_alloc, + .resize = do_resize, + .remap = std.mem.Allocator.noRemap, + .free = do_free, + }; + +/// Allocate memory +/// +/// Parameters: +/// - `len` : The length of the memory to allocate +/// - `alignment`: The alignment of the memory to allocate (log2 of byte alignment) +/// +/// Returns: +/// - `?[*]u8`: A pointer to the allocated memory, or null if insufficient memory is available +fn do_alloc(ptr: *anyopaque, len: usize, alignment: Alignment, pc: usize) ?[*]u8 { + const self: *Alloc = @ptrCast(@alignCast(ptr)); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + const needed = @max(len, @sizeOf(Chunk) - Chunk.header_size); + + var free_index = free_index_for_size(needed); + + while (free_index < free_list_count) { + var maybe_chunk = self.free_lists[free_index]; + + while (maybe_chunk) |c| { + var data_addr: usize = @intFromPtr(c.data()); + var available = c.size() - Chunk.header_size; + var trim_leading = false; + + // Force data_addr alignment by trimming off leading bytes + // of at least Chunk.min_size bytes. + + if (!alignment.check(data_addr)) { + data_addr = alignment.forward(data_addr); + var offset = data_addr - @intFromPtr(c.data()); + + while (offset < Chunk.min_size) { + data_addr += alignment.toByteUnits(); + offset += alignment.toByteUnits(); + } + + available = if (offset > available) 0 else available - offset; + trim_leading = true; + } + + if (available >= needed) { + // OK, We have enough space to fit the requested memory, unlink the chunk. + + c.unlink(self); + var our_chunk = c; + + // Trim off leading bytes + if (trim_leading) { + const next_chunk: *Chunk = c.get_next(); + + our_chunk = @ptrCast(@alignCast(@as(*u8, @ptrFromInt(data_addr - Chunk.header_size)))); + our_chunk.previous_size = data_addr - @intFromPtr(c.data()); + our_chunk._size = (available + Chunk.header_size); + + c._size = our_chunk.previous_size; + + if (@intFromPtr(next_chunk) < self.high_boundary) { + next_chunk.previous_size = our_chunk._size; + } + + our_chunk._size |= 0x01; // so we don't recombine it + + c.combine_and_free(self); + } + + // See if there is trailing space that we can trim off. + + const trim_addr = Chunk.alignment.forward(data_addr + needed); + const next_addr = @intFromPtr(our_chunk.get_next()); + + if (trim_addr + Chunk.min_size < next_addr) { + const our_address = @intFromPtr(our_chunk); + + const our_new_size = trim_addr - our_address; + + our_chunk._size = our_new_size | 0x01; + + const trim_chunk: *Chunk = @ptrFromInt(trim_addr); + trim_chunk._size = next_addr - trim_addr; + trim_chunk.previous_size = our_new_size; + + trim_chunk.combine_and_free(self); + } + + our_chunk._size |= 0x01; + + return our_chunk.data(); + } + + maybe_chunk = c.next_free; + } + + free_index += 1; + } + + if (self.fallback) |f| { + return do_alloc(f, len, alignment, pc); + } + + return null; +} + +/// Resize memory. This function attempts to resize the memory in place. +/// +/// Parameters: +/// - `memory` : The memory to resize +/// - `new_len` : The new length of the memory to resize +/// +/// Returns: +/// - `bool`: True if the resize was successful, false otherwise +fn do_resize(ptr: *anyopaque, memory: []u8, _: Alignment, new_len: usize, _: usize) bool { + const self: *Alloc = @ptrCast(@alignCast(ptr)); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + var chunk = Chunk.from_data(memory, self); + + if (new_len > chunk.size() - Chunk.header_size) { + // We need to grow the chunk, which means the next chunk must be free. + + if (!chunk.get_next().is_free(self)) return false; + + const target_size = new_len + Chunk.header_size; + const combined_size = chunk.size() + chunk.get_next().size(); + + // We can't grow the chunk if there isn't enough space. + + if (combined_size < target_size) return false; + + // OK, we can grow the chunk. + + chunk.get_next().unlink(self); + + chunk._size = combined_size; + chunk.get_next().previous_size = combined_size; + } + + // See if there is trailing space that we can trim off. + + const data_addr = @intFromPtr(chunk.data()); + const trim_addr = Chunk.alignment.forward(data_addr + new_len); + const next_addr = @intFromPtr(chunk.get_next()); + + if (trim_addr + Chunk.min_size < next_addr) { + const our_address = @intFromPtr(chunk); + + const our_new_size = trim_addr - our_address; + + chunk._size = our_new_size | 0x01; + + const trim_chunk: *Chunk = @ptrFromInt(trim_addr); + trim_chunk._size = next_addr - trim_addr; + trim_chunk.previous_size = our_new_size; + + trim_chunk.combine_and_free(self); + } + + return true; +} + +/// Free memory +/// +/// Parameters: +/// - `memory` : The memory to free +fn do_free(ptr: *anyopaque, memory: []u8, alignment: Alignment, pc: usize) void { + const self: *Alloc = @ptrCast(@alignCast(ptr)); + + const addr = @intFromPtr(memory.ptr); + if (addr < self.low_boundary or addr >= self.high_boundary) { + if (self.fallback) |f| { + do_free(f, memory, alignment, pc); + return; + } + + @panic("free - address is not in range"); + } + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + Chunk.from_data(memory, self).combine_and_free(self); +} + +/// Return the index of the first free list that can hold a chunk of the +/// given size. +/// +/// Parameters: +/// - `size`: The size of the chunk +/// +/// Returns: +/// - `usize`: The index of the free list +fn free_index_for_size(size: usize) usize { + var test_size: usize = Chunk.min_size; + for (0..free_list_count - 1) |i| { + if (test_size >= size) return i; + test_size *= 2; + } + + return free_list_count - 1; +} + +/// The heap consists of a number of contiguous, variable length chunks. +/// Each chunk has a header that contains metadata about the chunk. +/// +/// A chunk is either allocated or free. +/// +/// An allocated chunk has the following layout: +/// +/// [previous_size][size+1][data] +/// +/// A free chunk has the following layout: +/// +/// [previous_size][size][next_free][prior_free][unused] +/// +/// Since the actual size of a chunk is always even, we can use the least +/// significant bit of the size field to mark the chunk as in use. +pub const Chunk = extern struct { + previous_size: usize = 0, + _size: usize = 0, // chunk size and in-use flag + prior_free: ?*Chunk = null, + next_free: ?*Chunk = null, + + const header_size = 2 * @sizeOf(usize); + const min_size = header_size + 2 * @sizeOf(?*Chunk); + const alignment = Alignment.fromByteUnits(@alignOf(Alloc)); + + /// Returns a pointer to the chunk that contains the given data. + pub fn from_data(data_slice: []u8, alloc: *Alloc) *Chunk { + // If we are built with .Debug or .ReleaseSafe, we need to do a slow + // search to make sure the slice is valid. + std.debug.assert(blk: { + var chunk: *Chunk = @ptrFromInt(alloc.low_boundary); + while (@intFromPtr(chunk) < alloc.high_boundary) { + if (data_slice.ptr == chunk.data()) { + break :blk true; + } + chunk = chunk.get_next(); + } + break :blk false; + }); + + // Otherwise, just go for it. + return @as(*Chunk, @ptrCast(@alignCast(data_slice.ptr - header_size))); + } + + /// Return a data slice that maps the chunk's memory the user can use. + pub fn data(self: *Chunk) [*]u8 { + return @as([*]u8, @ptrCast(self)) + header_size; + } + + /// Returns the size of the chunk in bytes. + pub fn size(self: *Chunk) usize { + return self._size & ~@as(usize, 0x01); + } + + /// Returns true of the chunk is marked as free. That is the low order bit + /// of the size value is clear. + /// + /// An out-of-range Chunk pointer will return `false` (not free). + pub fn is_free(self: *Chunk, alloc: *Alloc) bool { + const addr: usize = @intFromPtr(self); + if (addr < alloc.low_boundary or addr >= alloc.high_boundary) return false; + + return (self._size & 0x01) == 0; + } + + /// Returns a pointer to the previous chunk in the heap. + /// Note: this can return an out of range Chunk pointer. + pub fn get_prior(self: *Chunk) *Chunk { + return @ptrFromInt(@intFromPtr(self) - self.previous_size); + } + + /// Returns a pointer to the next chunk in the heap. + /// Note: this can return an out of range Chunk pointer. + pub fn get_next(self: *Chunk) *Chunk { + return @ptrFromInt(@intFromPtr(self) + self.size()); + } + + /// Combine this chunk with any neighboring free chunks and + /// add the result to the appropriate free list. + pub fn combine_and_free(self: *Chunk, alloc: *Alloc) void { + var chunk = self; + + chunk._size &= ~@as(usize, 0x01); // Make sure the chunk is marked free. + + var next = chunk.get_next(); + if (next.is_free(alloc)) { + + // combine the chunks + next.unlink(alloc); + chunk._size += next._size; + + // update the next pointer + next = next.get_next(); + } + + if (chunk.previous_size != 0) { + const prior = chunk.get_prior(); + if (prior.is_free(alloc)) { + + // combine the chunks + const our_size = chunk._size; + chunk = prior; + chunk.unlink(alloc); + chunk._size += our_size; + } + } + + // set next chunk's previous size + if (@intFromPtr(next) < alloc.high_boundary) { + next.previous_size = chunk._size; + } + + const free_index = free_index_for_size(chunk.size()); + const list = alloc.free_lists[free_index]; + + chunk.prior_free = null; + chunk.next_free = list; + + if (list) |head| head.prior_free = chunk; + alloc.free_lists[free_index] = chunk; + } + + /// Unlink the chunk from the free list. + pub fn unlink(self: *Chunk, alloc: *Alloc) void { + if (!self.is_free(alloc)) @panic("unlink - Chunk is not free"); + + if (self.prior_free) |p| { + p.next_free = self.next_free; + } else { + const free_list = free_index_for_size(self.size()); + alloc.free_lists[free_list] = self.next_free; + } + + if (self.next_free) |n| n.prior_free = self.prior_free; + } +}; + +//------------------------------------------------------------------------------ +// Debugging Functions + +/// Displays free chunk chains. +/// +/// All chunks show address, size, and prior and next free chunks addresses. +/// +/// This function is intended for use in a debug build. +/// It writes to the debug log. +pub fn dbg_log_free_chains(self: *Alloc) void { + std.log.debug("", .{}); + + for (0..free_list_count) |i| { + var chunks = self.free_lists[i]; + + if (chunks == null) continue; + + std.log.debug("Free list {d}: ", .{i}); + + while (chunks) |chunk| { + if (chunk.is_free(self)) { + std.log.debug(" 0x{x:08} {d:6} {x:08} {x:08} ", .{ @intFromPtr(chunk), chunk.size(), @intFromPtr(chunk.prior_free), @intFromPtr(chunk.next_free) }); + } else { + std.log.debug(" 0x{x:08} {d:6} {x:08} {x:08} ", .{ @intFromPtr(chunk), chunk.size(), @intFromPtr(chunk.prior_free), @intFromPtr(chunk.next_free) }); + } + + chunks = chunk.next_free; + } + } +} + +/// Displays the chunk list. +/// All chunks show address, size and previous chunk size. +/// Free chunks show prior and next free chunks addresses. +/// +/// This function is intended for use in a debug build. +/// It writes to the debug log. +pub fn dbg_log_chunk_list(self: *Alloc) void { + var address: usize = self.low_boundary; + var idx: usize = 0; + + std.log.debug("", .{}); + std.log.debug(" idx addr size prev prior next", .{}); + std.log.debug(" --- ---- ---- ---- ----- ----", .{}); + + while (address < self.high_boundary) { + const chunk: *Chunk = @ptrFromInt(address); + + if (chunk.is_free(self)) { + std.log.debug("{d:6} {x:08} {d:6} {d:6} {x:08} {x:08}; ", .{ idx, @intFromPtr(chunk), chunk.size(), chunk.previous_size, @intFromPtr(chunk.prior_free), @intFromPtr(chunk.next_free) }); + } else { + std.log.debug("{d:6} {x:08} {d:6} {d:6}; ", .{ idx, @intFromPtr(chunk), chunk.size(), chunk.previous_size }); + } + + address += chunk.size(); + idx += 1; + } +} + +/// Check the integrity of the allocator memory pool. +/// This function is intended for use in a debug build. +/// It will log any errors to the debug log. +pub fn dbg_integrity_check(self: *Alloc) bool { + var valid: bool = true; + + var previous_size: usize = 0; + + // If we skip over memory based on chunk size we should end up exactly at the end of the heap. + // Also, each previous_size should match the size of prior chunk. + + // We mark each chunk here by setting the low order bit of the previous size to 1. + + var address: usize = self.low_boundary; + while (address < self.high_boundary) { + const chunk: *Chunk = @ptrFromInt(address); + + if (chunk.previous_size != previous_size) { + valid = false; + std.log.debug("Chunk list integrity check failed: chunk 0x{x:08} previous_size {d} != {d}\n", .{ @intFromPtr(chunk), chunk.previous_size, previous_size }); + } + + previous_size = chunk.size(); + address += chunk.size(); + + chunk.previous_size |= 0x01; + } + + if (address > self.high_boundary) { + valid = false; + std.log.debug("Chunk list integrity check failed: address 0x{x:08} > high_boundary 0x{x:08}\n", .{ address, self.high_boundary }); + } + + // Every chunk on one of the fre lists should be marked free and should be a valid chunk + // reachable by skipping over memory based on chunk size. + + // We clear the low order bit of the previous size to 0 for any free chunks. + + for (0..free_list_count) |i| { + var chunks = self.free_lists[i]; + + if (chunks == null) continue; + + while (chunks) |chunk| { + if (!chunk.is_free(self)) { + valid = false; + std.log.debug("Chunk free list integrity check failed: chunk on free list 0x{x:08} is not free\n", .{@intFromPtr(chunk)}); + } + + var found: bool = false; + + var test_address: usize = self.low_boundary; + while (test_address < self.high_boundary) { + const test_chunk: *Chunk = @ptrFromInt(test_address); + test_address += test_chunk.size(); + + if (chunk == test_chunk) { + found = true; + break; + } + } + + if (!found) { + valid = false; + std.log.debug("Chunk free list integrity check failed: chunk on free list 0x{x:08} is not in the chunk list\n", .{@intFromPtr(chunk)}); + } + + chunk.previous_size &= ~@as(usize, 0x01); + + chunks = chunk.next_free; + } + } + + // Make sure any chunk with the low order bit of the previous size set also has the + // low order bit of the size set. + + // Unmark each chunk here by clearing the low order bit of the previous size to 0. + + address = self.low_boundary; + while (address < self.high_boundary) { + const chunk: *Chunk = @ptrFromInt(address); + if (chunk.previous_size & 0x01 != 0 and chunk._size & 0x01 == 0) { + valid = false; + std.log.debug("Chunk integrity check failed: Chunk 0x{x:08} in-use chunk marked as free\n", .{@intFromPtr(chunk)}); + } + chunk.previous_size &= ~@as(usize, 0x01); + address += chunk.size(); + } + + return valid; +} diff --git a/core/src/microzig.zig b/core/src/microzig.zig index e29c3a997..9b07e2053 100644 --- a/core/src/microzig.zig +++ b/core/src/microzig.zig @@ -30,6 +30,7 @@ pub const board = if (config.has_board) @import("board") else void; /// Contains device-independent drivers for peripherial devices. pub const drivers = @import("drivers"); +pub const allocator = @import("allocator.zig"); pub const core = @import("core.zig"); pub const concurrency = @import("concurrency.zig"); pub const interrupt = @import("interrupt.zig"); diff --git a/examples/espressif/esp/.gdb_history b/examples/espressif/esp/.gdb_history deleted file mode 100644 index 1c81a854f..000000000 --- a/examples/espressif/esp/.gdb_history +++ /dev/null @@ -1,15 +0,0 @@ -target remote :3333 -l -r -continue -target remote :3333 -c -target remote :3333 -b hal.radio.osi.event_post -c -stack 20 -target remote :3333 -c -target remote :3333 -b ccmp_encrypt -c diff --git a/examples/espressif/esp/build.zig b/examples/espressif/esp/build.zig index 8a1b64955..bcdaec7c7 100644 --- a/examples/espressif/esp/build.zig +++ b/examples/espressif/esp/build.zig @@ -31,6 +31,7 @@ pub fn build(b: *std.Build) void { .example = .{ .name = "wifi", .file = "src/wifi.zig", + .features = .{ .lwip = true }, }, }, }; @@ -83,6 +84,49 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path(example.file), }); + if (example.features.lwip) { + const resolved_zig_target = b.resolveTargetQuery(firmware.target.zig_target); + + const foundation_libc_dep = b.dependency("foundation_libc", .{ + .optimize = optimize, + .target = resolved_zig_target, + }); + const lwip_dep = b.dependency("lwip", .{}); + + const lwip_lib = b.addLibrary(.{ + .name = "lwip", + .root_module = b.createModule(.{ + .optimize = optimize, + .target = resolved_zig_target, + }), + .linkage = .static, + }); + + lwip_lib.addCSourceFiles(.{ + .root = lwip_dep.path("src"), + .files = &lwip_files, + .flags = &lwip_flags, + }); + + lwip_lib.linkLibrary(foundation_libc_dep.artifact("foundation")); + + lwip_lib.addIncludePath(b.path("src/include")); + lwip_lib.addIncludePath(lwip_dep.path("src/include")); + + firmware.app_mod.linkLibrary(lwip_lib); + + const lwip_translate_c = b.addTranslateC(.{ + .root_source_file = b.path("src/include/lwip.h"), + .target = resolved_zig_target, + .optimize = optimize, + .link_libc = false, + }); + lwip_translate_c.addIncludePath(b.path("src/include")); + lwip_translate_c.addIncludePath(lwip_dep.path("src/include")); + + firmware.app_mod.addImport("lwip", lwip_translate_c.createModule()); + } + // `installFirmware()` is the MicroZig pendant to `Build.installArtifact()` // and allows installing the firmware as a typical firmware file. // @@ -129,11 +173,121 @@ const TargetDescription = struct { }; const Example = struct { + const Features = packed struct { + lwip: bool = false, + }; + name: []const u8, file: []const u8, + features: Features = .{}, }; const TargetedExample = struct { target: TargetEnum, example: Example, }; + +const lwip_flags = [_][]const u8{ "-std=c99", "-fno-sanitize=undefined" }; +const lwip_files = [_][]const u8{ + // Core files + "core/init.c", + "core/def.c", + "core/dns.c", + "core/inet_chksum.c", + "core/ip.c", + "core/mem.c", + "core/memp.c", + "core/netif.c", + "core/pbuf.c", + "core/raw.c", + "core/stats.c", + "core/sys.c", + "core/altcp.c", + "core/altcp_alloc.c", + "core/altcp_tcp.c", + "core/tcp.c", + "core/tcp_in.c", + "core/tcp_out.c", + "core/timeouts.c", + "core/udp.c", + + // IPv4 implementation: + "core/ipv4/acd.c", + "core/ipv4/autoip.c", + "core/ipv4/dhcp.c", + "core/ipv4/etharp.c", + "core/ipv4/icmp.c", + "core/ipv4/igmp.c", + "core/ipv4/ip4_frag.c", + "core/ipv4/ip4.c", + "core/ipv4/ip4_addr.c", + + // IPv6 implementation: + "core/ipv6/dhcp6.c", + "core/ipv6/ethip6.c", + "core/ipv6/icmp6.c", + "core/ipv6/inet6.c", + "core/ipv6/ip6.c", + "core/ipv6/ip6_addr.c", + "core/ipv6/ip6_frag.c", + "core/ipv6/mld6.c", + "core/ipv6/nd6.c", + + // Interfaces + "netif/ethernet.c", + + // Interfaces: + // "netif/bridgeif.c", + // "netif/ethernet.c", + // "netif/slipif.c", + // "netif/bridgeif_fdb.c", + + // sequential APIs + // "api/err.c", + // "api/api_msg.c", + // "api/netifapi.c", + // "api/sockets.c", + // "api/netbuf.c", + // "api/api_lib.c", + // "api/tcpip.c", + // "api/netdb.c", + // "api/if_api.c", + + // 6LoWPAN + // "netif/lowpan6.c", + // "netif/lowpan6_ble.c", + // "netif/lowpan6_common.c", + // "netif/zepif.c", + + // PPP + // "netif/ppp/polarssl/arc4.c", + // "netif/ppp/polarssl/des.c", + // "netif/ppp/polarssl/md4.c", + // "netif/ppp/polarssl/sha1.c", + // "netif/ppp/polarssl/md5.c", + // "netif/ppp/ipcp.c", + // "netif/ppp/magic.c", + // "netif/ppp/pppoe.c", + // "netif/ppp/mppe.c", + // "netif/ppp/multilink.c", + // "netif/ppp/chap-new.c", + // "netif/ppp/auth.c", + // "netif/ppp/chap_ms.c", + // "netif/ppp/ipv6cp.c", + // "netif/ppp/chap-md5.c", + // "netif/ppp/upap.c", + // "netif/ppp/pppapi.c", + // "netif/ppp/pppos.c", + // "netif/ppp/eap.c", + // "netif/ppp/pppol2tp.c", + // "netif/ppp/demand.c", + // "netif/ppp/fsm.c", + // "netif/ppp/eui64.c", + // "netif/ppp/ccp.c", + // "netif/ppp/pppcrypt.c", + // "netif/ppp/utils.c", + // "netif/ppp/vj.c", + // "netif/ppp/lcp.c", + // "netif/ppp/ppp.c", + // "netif/ppp/ecp.c", +}; diff --git a/examples/espressif/esp/build.zig.zon b/examples/espressif/esp/build.zig.zon index 83c09a474..1be72bfc3 100644 --- a/examples/espressif/esp/build.zig.zon +++ b/examples/espressif/esp/build.zig.zon @@ -4,6 +4,11 @@ .version = "0.0.0", .dependencies = .{ .microzig = .{ .path = "../../.." }, + .lwip = .{ + .url = "https://download.savannah.nongnu.org/releases/lwip/lwip-2.2.1.zip", + .hash = "N-V-__8AAHHNdQCulP7hfd2g0o1RPg4_KWqqUjQ42Wda47eN", + }, + .foundation_libc = .{ .path = "../../../modules/foundation-libc" }, }, .paths = .{ "README.md", diff --git a/examples/espressif/esp/src/include/arch/cc.h b/examples/espressif/esp/src/include/arch/cc.h new file mode 100644 index 000000000..1387dfd67 --- /dev/null +++ b/examples/espressif/esp/src/include/arch/cc.h @@ -0,0 +1,31 @@ +#ifndef LWIP_ARCH_CC_H +#define LWIP_ARCH_CC_H + +#include +#include + +extern uint32_t rand(void); + +#define LWIP_PLATFORM_DIAG(x) // TODO: Implement these +#define LWIP_PLATFORM_ASSERT(x) // TODO: Implement these + +#define BYTE_ORDER LITTLE_ENDIAN + +#define LWIP_RAND() (rand()) + +#define LWIP_NO_STDDEF_H 0 +#define LWIP_NO_STDINT_H 0 +#define LWIP_NO_INTTYPES_H 1 +#define LWIP_NO_LIMITS_H 0 +#define LWIP_NO_CTYPE_H 1 + +#define LWIP_UNUSED_ARG(x) (void)x +#define LWIP_PROVIDE_ERRNO 1 + +/* ---------- No Protection Needed ---------- */ + +#define SYS_ARCH_DECL_PROTECT(lev) +#define SYS_ARCH_PROTECT(lev) +#define SYS_ARCH_UNPROTECT(lev) + +#endif /* LWIP_ARCH_CC_H */ diff --git a/examples/espressif/esp/src/include/lwip.h b/examples/espressif/esp/src/include/lwip.h new file mode 100644 index 000000000..61ac15f47 --- /dev/null +++ b/examples/espressif/esp/src/include/lwip.h @@ -0,0 +1,9 @@ +#include "lwip/init.h" +#include "lwip/tcpip.h" +#include "lwip/netif.h" +#include "lwip/dhcp.h" +#include "lwip/tcp.h" +#include "lwip/udp.h" +#include "lwip/etharp.h" +#include "lwip/ethip6.h" +#include "lwip/timeouts.h" diff --git a/examples/espressif/esp/src/include/lwipopts.h b/examples/espressif/esp/src/include/lwipopts.h new file mode 100644 index 000000000..78b3de828 --- /dev/null +++ b/examples/espressif/esp/src/include/lwipopts.h @@ -0,0 +1,67 @@ +#ifndef LWIPOPTS_H +#define LWIPOPTS_H + +/* ---------- System options ---------- */ +#define NO_SYS 1 +#define SYS_LIGHTWEIGHT_PROT 0 + +/* ---------- Memory options ---------- */ +#define MEM_LIBC_MALLOC 1 // Don't use malloc() +#define MEM_USE_POOLS 0 // No custom pools +#define MEM_USE_POOLS_TRY_BIGGER_POOL 0 +#define MEMP_MEM_MALLOC 0 // Don't malloc pools +#define MEM_ALIGNMENT 4 // 32-bit CPU alignment +#define MEM_SIZE 0 // No dynamic heap + +/* ---------- PBUF options ---------- */ +#define PBUF_POOL_SIZE 8 // Number of static pbufs +#define PBUF_POOL_BUFSIZE 512 // Size of each pbuf +#define PBUF_POOL_ALLOC_SRC PBUF_ALLOC_SRC_INTERNAL + +/* ---------- Raw API ---------- */ +#define LWIP_RAW 1 +#define LWIP_NETCONN 0 +#define LWIP_SOCKET 0 + +#define LWIP_IPV4 1 +#define LWIP_IPV6 1 + +#define LINK_STATS 0 + +#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_NETIF_STATUS_CALLBACK 1 +#define LWIP_NETIF_EXT_STATUS_CALLBACK 1 + +/* ---------- Protocol Support ---------- */ +#define LWIP_ARP 1 +#define LWIP_ICMP 1 +#define LWIP_DHCP 1 // Optional +#define LWIP_DNS 1 // Optional +#define LWIP_TCP 1 +#define LWIP_UDP 1 + +/* ---------- TCP options ---------- */ +#define MEMP_NUM_TCP_PCB 4 +#define MEMP_NUM_TCP_SEG 16 +#define TCP_MSS 1460 +#define TCP_SND_BUF (2 * TCP_MSS) +#define TCP_WND (2 * TCP_MSS) + +/* ---------- UDP options ---------- */ +#define MEMP_NUM_UDP_PCB 2 + +/* ---------- Other MEMP pool sizes ---------- */ +#define MEMP_NUM_PBUF 16 +#define MEMP_NUM_NETCONN 0 +#define MEMP_NUM_SYS_TIMEOUT 12 // Required for timers like DHCP + +/* ---------- DNS ---------- */ +#define MEMP_NUM_NETBUF 0 +#define MEMP_NUM_API_MSG 0 +#define MEMP_NUM_TCPIP_MSG_API 0 +#define MEMP_NUM_TCPIP_MSG_INPKT 0 + +/* ---------- Debug options ---------- */ +#define LWIP_DEBUG 0 + +#endif /* LWIPOPTS_H */ diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index 25ca48030..a110609e2 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -5,6 +5,8 @@ const hal = microzig.hal; const radio = hal.radio; const usb_serial_jtag = hal.usb_serial_jtag; +const c = @import("lwip"); + pub const microzig_options: microzig.Options = .{ .log_level = .debug, .log_scope_levels = &.{ @@ -12,6 +14,10 @@ pub const microzig_options: microzig.Options = .{ .scope = .esp_radio, .level = .info, }, + .{ + .scope = .esp_radio_wifi, + .level = .info, + }, .{ .scope = .esp_radio_osi, .level = .info, @@ -32,22 +38,77 @@ pub const microzig_options: microzig.Options = .{ var buffer: [50 * 1024]u8 = undefined; pub fn main() !void { - var fba: std.heap.FixedBufferAllocator = .init(&buffer); - const allocator = fba.threadSafeAllocator(); + // var fba: std.heap.FixedBufferAllocator = .init(&buffer); + // const allocator = fba.threadSafeAllocator(); + var alloc = microzig.allocator.init_with_buffer(&buffer); + const allocator = alloc.allocator(); microzig.cpu.interrupt.enable_interrupts(); try radio.init(allocator); try radio.wifi.init(); + c.lwip_init(); + + var netif: c.netif = undefined; + _ = c.netif_add(&netif, @ptrCast(c.IP4_ADDR_ANY), @ptrCast(c.IP4_ADDR_ANY), @ptrCast(c.IP4_ADDR_ANY), null, netif_init, c.netif_input); + @memcpy(&netif.name, "e0"); + c.netif_create_ip6_linklocal_address(&netif, 1); + netif.ip6_autoconfig_enabled = 1; + c.netif_set_status_callback(&netif, netif_status_callback); + c.netif_set_default(&netif); + c.netif_set_up(&netif); + _ = c.dhcp_start(&netif); + try radio.wifi.set_mode(.sta); try radio.wifi.set_client_config(.{ .ssid = "Internet", }); + radio.wifi.set_packet_rx_sta_callback(rx_callback); + try radio.wifi.start(); try radio.wifi.connect(); + var connected: bool = false; + var last_mem_show = hal.time.get_time_since_boot(); + + while (true) { + const sta_state = radio.wifi.get_sta_state(); + if (!connected and sta_state == .sta_connected) { + std.log.info("link up", .{}); + c.netif_set_link_up(&netif); + connected = true; + } else if (connected and sta_state == .sta_disconnected) { + std.log.info("link down", .{}); + c.netif_set_link_down(&netif); + connected = false; + } + + const maybe_pbuf = blk: { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + break :blk rx_queue.dequeue(); + }; + + if (maybe_pbuf) |pbuf| { + if (netif.input.?(pbuf, &netif) != c.ERR_OK) { + _ = c.pbuf_free(pbuf); + } + } + + c.sys_check_timeouts(); + + const now = hal.time.get_time_since_boot(); + if (!now.diff(last_mem_show).less_than(.from_ms(1000))) { + const used_mem = 50 * 1024 - alloc.free_heap(); + std.log.info("used memory: {}K ({})", .{used_mem / 1024, used_mem}); + last_mem_show = now; + } + hal.time.sleep_ms(100); + } + // var ssid: [1:0]u8 = @splat(0); // var bssid: [1:0]u8 = @splat(0); // @@ -73,9 +134,131 @@ pub fn main() !void { // try radio.wifi.c_result(c.esp_wifi_scan_get_ap_num(&no)); // std.log.info("found {} aps", .{no}); +} - while (true) { - std.log.info("tick!", .{}); - hal.time.sleep_ms(1000); +var rx_queue: Queue(*c.struct_pbuf, 10) = .empty; + +fn rx_callback(packet: radio.wifi.PacketBuffer) void { + defer packet.deinit(); + // std.log.info("receiving packet", .{}); + + const maybe_pbuf: ?*c.struct_pbuf = c.pbuf_alloc(c.PBUF_RAW, @intCast(packet.data.len), c.PBUF_POOL); + if (maybe_pbuf) |pbuf| { + _ = c.pbuf_take(pbuf, packet.data.ptr, @intCast(packet.data.len)); + + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + rx_queue.enqueue(pbuf) catch { + std.log.warn("packet dropped", .{}); + _ = c.pbuf_free(pbuf); + }; } } + +fn netif_init(netif_c: [*c]c.struct_netif) callconv(.c) c.err_t { + const netif: *c.struct_netif = netif_c; + + netif.linkoutput = netif_output; + netif.output = c.etharp_output; + netif.output_ip6 = c.ethip6_output; + netif.mtu = 1500; + netif.flags = c.NETIF_FLAG_BROADCAST | c.NETIF_FLAG_ETHARP | c.NETIF_FLAG_ETHERNET | c.NETIF_FLAG_IGMP | c.NETIF_FLAG_MLD6; + @memcpy(&netif.hwaddr, &radio.read_mac()); + netif.hwaddr_len = 6; + + return c.ERR_OK; +} + +var packet_buf: [1500]u8 = undefined; + +fn netif_output(netif: [*c]c.struct_netif, pbuf_c: [*c]c.struct_pbuf) callconv(.c) c.err_t { + _ = netif; // autofix + const pbuf: *c.struct_pbuf = pbuf_c; + + // std.log.info("sending packet", .{}); + + // const cs = microzig.interrupt.enter_critical_section(); + // defer cs.leave(); + + var off: usize = 0; + while (off < pbuf.tot_len) { + const cnt = c.pbuf_copy_partial(pbuf, packet_buf[off..].ptr, @as(u15, @intCast(pbuf.tot_len - off)), @as(u15, @intCast(off))); + if (cnt == 0) { + std.log.err("failed to copy network packet", .{}); + return c.ERR_BUF; + } + off += cnt; + } + + radio.wifi.send_packet(.sta, packet_buf[0..pbuf.tot_len]) catch |err| { + std.log.err("failed to send packet: {}", .{err}); + }; + + return c.ERR_OK; +} + +const IPFormatter = struct { + addr: c.ip_addr_t, + + pub fn init(addr: c.ip_addr_t) IPFormatter { + return .{ .addr = addr }; + } + + pub fn format(addr: IPFormatter, comptime fmt: []const u8, opt: std.fmt.FormatOptions, writer: anytype) !void { + _ = fmt; + _ = opt; + try writer.writeAll(std.mem.sliceTo(c.ip4addr_ntoa(@as(*const c.ip4_addr_t, @ptrCast(&addr.addr))), 0)); + } +}; + +fn netif_status_callback(netif_c: [*c]c.netif) callconv(.C) void { + const netif: *c.netif = netif_c; + + std.log.info("netif status changed ip to {}", .{IPFormatter.init(netif.ip_addr)}); +} + +comptime { + _ = sys_now; +} + +export fn sys_now() callconv(.c) u32 { + return @truncate(hal.time.get_time_since_boot().to_us() * 1_000); +} + +fn Queue(Item: type, capacity: usize) type { + return struct { + const Self = @This(); + + pub const empty: Self = .{ + .buf = undefined, + .read_index = 0, + .write_index = 0, + .len = 0, + }; + + buf: [capacity]Item, + read_index: usize, + write_index: usize, + len: usize, + + pub fn enqueue(self: *Self, item: Item) error{NoSpace}!void { + if (self.len < capacity) { + self.buf[self.write_index] = item; + self.write_index = (self.write_index + 1) % capacity; + self.len += 1; + } else { + return error.NoSpace; + } + } + + pub fn dequeue(self: *Self) ?Item { + if (self.len == 0) return null; + + const item = self.buf[self.read_index]; + self.read_index = (self.read_index + 1) % capacity; + self.len -= 1; + return item; + } + }; +} diff --git a/modules/foundation-libc/build.zig b/modules/foundation-libc/build.zig index aabe36ff5..e5ad73de7 100644 --- a/modules/foundation-libc/build.zig +++ b/modules/foundation-libc/build.zig @@ -40,6 +40,7 @@ const header_files = [_][]const u8{ "inttypes.h", "math.h", "setjmp.h", + "stdio.h", "stdlib.h", "string.h", "tgmath.h", diff --git a/modules/foundation-libc/include/stdio.h b/modules/foundation-libc/include/stdio.h new file mode 100644 index 000000000..a0b7e90cf --- /dev/null +++ b/modules/foundation-libc/include/stdio.h @@ -0,0 +1,6 @@ +#ifndef _FOUNDATION_LIBC_STDIO_H_ +#define _FOUNDATION_LIBC_STDIO_H_ + +// empty for now + +#endif diff --git a/modules/foundation-libc/include/stdlib.h b/modules/foundation-libc/include/stdlib.h index a3fb8ee33..3d1ceeb6c 100644 --- a/modules/foundation-libc/include/stdlib.h +++ b/modules/foundation-libc/include/stdlib.h @@ -2,5 +2,8 @@ #define _FOUNDATION_LIBC_STDLIB_H_ int atoi(char const * str); +void* malloc(size_t size); +void* calloc(size_t count, size_t size); +void free(void* ptr); #endif diff --git a/modules/foundation-libc/test/build.zig b/modules/foundation-libc/test/build.zig index a0266ba88..ef5c6759f 100644 --- a/modules/foundation-libc/test/build.zig +++ b/modules/foundation-libc/test/build.zig @@ -172,7 +172,7 @@ const validation_target_list = [_]std.Target.Query{ .{ .cpu_arch = .wasm64, .os_tag = .freestanding }, // nice to have, but broken: - //.{ .cpu_arch = .avr, .os_tag = .freestanding }, + // .{ .cpu_arch = .avr, .os_tag = .freestanding }, // .{ .cpu_arch = .msp430, .os_tag = .freestanding }, // error: unknown target CPU 'generic' // .{ .cpu_arch = .m68k, .os_tag = .freestanding }, // .{ .cpu_arch = .xtensa, .os_tag = .freestanding }, diff --git a/port/espressif/esp/build.zig b/port/espressif/esp/build.zig index 43cc568a2..b6ec5837e 100644 --- a/port/espressif/esp/build.zig +++ b/port/espressif/esp/build.zig @@ -159,12 +159,10 @@ pub fn build(b: *std.Build) void { const translate_c = b.addTranslateC(.{ .root_source_file = esp_wifi_sys_dep.path("esp-wifi-sys/include/include.h"), .target = esp32_c3_resolved_zig_target, - .optimize = .ReleaseFast, + .optimize = .Debug, .link_libc = false, }); - const mod = translate_c.addModule("esp-wifi-driver"); - translate_c.addIncludePath(b.path("src/hal/radio/libc_dummy_include")); translate_c.addIncludePath(esp_wifi_sys_dep.path("esp-wifi-sys/include")); translate_c.addIncludePath(esp_wifi_sys_dep.path("esp-wifi-sys/headers")); @@ -172,6 +170,13 @@ pub fn build(b: *std.Build) void { // esp32_c3 specific translate_c.addIncludePath(esp_wifi_sys_dep.path("esp-wifi-sys/headers/esp32c3")); + const mod = translate_c.addModule("esp-wifi-driver"); + + const foundation_libc_dep = b.dependency("microzig/modules/foundation-libc", .{ + .target = esp32_c3_resolved_zig_target, + }); + mod.linkLibrary(foundation_libc_dep.artifact("foundation")); + mod.addLibraryPath(esp_wifi_sys_dep.path("esp-wifi-sys/libs/esp32c3")); inline for (&.{ "btbb", diff --git a/port/espressif/esp/build.zig.zon b/port/espressif/esp/build.zig.zon index 765d844e3..5263b6e34 100644 --- a/port/espressif/esp/build.zig.zon +++ b/port/espressif/esp/build.zig.zon @@ -4,6 +4,7 @@ .version = "0.0.0", .dependencies = .{ .@"microzig/build-internals" = .{ .path = "../../../build-internals" }, + .@"microzig/modules/foundation-libc" = .{ .path = "../../../modules/foundation-libc" }, .@"microzig/modules/riscv32-common" = .{ .path = "../../../modules/riscv32-common" }, .@"esp-wifi-sys" = .{ .url = "git+https://github.com/esp-rs/esp-wifi-sys#99dd43f9992b276efff3c75467839801e7ea55c7", diff --git a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld index e73d848f9..409bf6f45 100644 --- a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld @@ -48,6 +48,12 @@ SECTIONS . += 16; } > IROM + .stack (NOLOAD) : ALIGN(4) + { + . += 0x8000; + end_of_stack = .; + } > DRAM + .ram_text : ALIGN(4) { . = ALIGN(4); @@ -74,7 +80,7 @@ SECTIONS .data.dummy (NOLOAD) : ALIGN(4) { - . = . + SIZEOF(.ram_text) + SIZEOF(.ram_text.wifi); + . = . + SIZEOF(.stack) + SIZEOF(.ram_text) + SIZEOF(.ram_text.wifi); } > DRAM .data : ALIGN(4) diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index f71ef6625..0eb15c729 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -289,6 +289,7 @@ pub const startup_logic = switch (cpu_config.boot_mode) { const sections = struct { extern var microzig_bss_start: u8; extern var microzig_bss_end: u8; + extern var end_of_stack: u8; }; fn _start() callconv(.naked) noreturn { @@ -296,7 +297,7 @@ pub const startup_logic = switch (cpu_config.boot_mode) { \\mv sp, %[eos] \\jal _start_c : - : [eos] "r" (@as(u32, microzig.config.end_of_stack)), + : [eos] "r" (@as(u32, @intFromPtr(§ions.end_of_stack))), ); } diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index a1cb78b1d..32cda6a75 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -38,11 +38,11 @@ fn syslog(fmt: ?[*:0]const u8, va_list: std.builtin.VaList) callconv(.c) void { pub export var WIFI_EVENT: c.esp_event_base_t = "WIFI_EVENT"; -pub export fn strlen(str: ?[*:0]const u8) callconv(.c) usize { - const s = str orelse return 0; - - return std.mem.len(s); -} +// pub export fn strlen(str: ?[*:0]const u8) callconv(.c) usize { +// const s = str orelse return 0; +// +// return std.mem.len(s); +// } pub export fn strnlen(str: ?[*:0]const u8, _: usize) callconv(.c) usize { const s = str orelse return 0; @@ -59,9 +59,9 @@ pub export fn strdup(_: ?[*:0]const u8) callconv(.c) ?[*:0]const u8 { @panic("strdup"); } -pub export fn atoi(_: ?[*:0]const u8) callconv(.c) i32 { - @panic("atoi"); -} +// pub export fn atoi(_: ?[*:0]const u8) callconv(.c) i32 { +// @panic("atoi"); +// } pub export fn strcasecmp(_: ?[*:0]const u8, _: ?[*:0]const u8) callconv(.c) i32 { @panic("atoi"); @@ -89,14 +89,14 @@ pub export fn __assert_func( pub export fn malloc(len: usize) callconv(.c) ?*anyopaque { log.debug("malloc {}", .{len}); - const buf = allocator.alloc(u8, @sizeOf(usize) + len) catch { + const buf = allocator.rawAlloc(@sizeOf(usize) + len, .@"4", @returnAddress()) orelse { log.warn("failed to allocate memory: {}", .{len}); return null; }; - const alloc_len: *usize = @alignCast(@ptrCast(buf.ptr)); + const alloc_len: *usize = @alignCast(@ptrCast(buf)); alloc_len.* = len; - return @ptrFromInt(@intFromPtr(buf.ptr) + @sizeOf(usize)); + return @ptrFromInt(@intFromPtr(buf) + @sizeOf(usize)); } pub export fn calloc(number: usize, size: usize) callconv(.c) ?*anyopaque { @@ -115,7 +115,7 @@ pub export fn free(ptr: ?*anyopaque) callconv(.c) void { const buf_ptr: [*]u8 = @ptrFromInt(@intFromPtr(ptr) - @sizeOf(usize)); const buf_len: *usize = @alignCast(@ptrCast(buf_ptr)); - allocator.free(buf_ptr[0 .. @sizeOf(usize) + buf_len.*]); + allocator.rawFree(buf_ptr[0 .. @sizeOf(usize) + buf_len.*], .@"4", @returnAddress()); } pub export fn esp_wifi_free_internal_heap(_: ?*anyopaque) callconv(.c) void { @@ -203,11 +203,11 @@ pub export fn esp_fill_random(buf: [*c]u8, len: usize) callconv(.c) void { comptime { _ = WIFI_EVENT; - _ = strlen; + // _ = strlen; _ = strnlen; _ = strrchr; _ = strdup; - _ = atoi; + // _ = atoi; _ = strcasecmp; _ = mktime; _ = __assert_func; @@ -228,6 +228,7 @@ comptime { _ = sleep; _ = usleep; _ = esp_fill_random; + _ = rand; } // ----- end of exports ----- @@ -794,6 +795,8 @@ pub fn event_post( const event: wifi.Event = @enumFromInt(id); log.info("received event: {s}", .{@tagName(event)}); + wifi.update_sta_state(event); + return 0; } @@ -801,7 +804,7 @@ pub fn get_free_heap_size() callconv(.c) void { @panic("get_free_heap_size: not implemented"); } -pub fn rand() callconv(.c) u32 { +pub export fn rand() callconv(.c) u32 { return hal.rng.random_u32(); } diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index aa3e9507a..cf956c3be 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -51,10 +51,6 @@ pub fn init() InternalError!void { inited = true; } -pub fn callback(_: ?*anyopaque) callconv(.c) void { - log.info("hello world", .{}); -} - pub fn deinit() void { if (!inited) { @panic("trying to deinit the wifi controller but it isn't initialized"); @@ -302,6 +298,20 @@ pub fn connect() InternalError!void { try c_result(c.esp_wifi_connect()); } +pub const Interface = enum(u32) { + sta = c.WIFI_IF_STA, + ap = c.WIFI_IF_AP, + + pub fn is_active(self: Interface) InternalError!bool { + const mode = try get_mode(); + if (mode == .ap_sta) return true; + return switch (self) { + .sta => mode == .sta, + .ap => mode == .ap, + }; + } +}; + pub const Event = enum(i32) { /// Wi-Fi is ready for operation. WifiReady = 0, @@ -405,26 +415,49 @@ pub const Event = enum(i32) { StaNeighborRep, }; -// const StaState = enum { -// none, -// sta_started, -// sta_connected, -// sta_disconnected, -// sta_stopped, -// }; -// -// var sta_state: StaState = .none; -// -// /// Internal method called on each event post. -// pub fn update_state(event: Event) void { -// switch (event) { -// -// } -// } +const StaState = enum { + none, + sta_started, + sta_connected, + sta_disconnected, + sta_stopped, +}; + +var sta_state: StaState = .none; + +/// Internal method called on each event post. +pub fn update_sta_state(event: Event) void { + const new_sta_state: StaState = switch (event) { + .StaStart => .sta_started, + .StaConnected => .sta_connected, + .StaDisconnected => .sta_disconnected, + .StaStop => .sta_stopped, + else => return, + }; + @atomicStore(StaState, &sta_state, new_sta_state, .unordered); +} + +pub fn get_sta_state() StaState { + return @atomicLoad(StaState, &sta_state, .unordered); +} // TODO: ApState -var wifi_tx_in_flight: usize = 0; +var packets_in_flight: usize = 0; + +pub fn send_packet(iface: Interface, data: []const u8) (error{TooManyPacketsInFlight} || InternalError)!void { + std.debug.assert(try iface.is_active()); // tried to send packet on invalid interface + + const pkts_in_flight = @atomicLoad(usize, &packets_in_flight, .acquire); + if (pkts_in_flight >= 15) { + log.warn("too many packets in flight", .{}); + return error.TooManyPacketsInFlight; + } + _ = @atomicRmw(usize, &packets_in_flight, .Add, 1, .acq_rel); + errdefer _ = @atomicRmw(usize, &packets_in_flight, .Sub, 1, .acq_rel); + + try c_result(c.esp_wifi_internal_tx(@intFromEnum(iface), @constCast(@ptrCast(data.ptr)), @intCast(data.len))); +} fn tx_done_cb( _: u8, @@ -434,41 +467,65 @@ fn tx_done_cb( ) callconv(.c) void { log.debug("tx_done_cb", .{}); - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); + _ = @atomicRmw(usize, &packets_in_flight, .Sub, 1, .acq_rel); +} + +/// Every packet buffer must be deinited by the user in the callback. +pub const PacketBuffer = struct { + data: []const u8, + eb: ?*anyopaque, - if (wifi_tx_in_flight > 0) { - wifi_tx_in_flight -= 1; + pub fn deinit(self: PacketBuffer) void { + c.esp_wifi_internal_free_rx_buffer(self.eb); } +}; + +pub const RxCallback = *const fn (pb: PacketBuffer) void; + +var packet_rx_sta_callback: ?RxCallback = null; +var packet_rx_ap_callback: ?RxCallback = null; + +pub fn set_packet_rx_sta_callback(cb: RxCallback) void { + packet_rx_sta_callback = cb; } fn recv_cb_sta(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_err_t { - log.debug("recv_cb_sta {?} {} {?}", .{buf, len, eb}); + log.debug("recv_cb_sta {?} {} {?}", .{ buf, len, eb }); + + const pb: PacketBuffer = .{ + .data = @as([*]const u8, @ptrCast(buf))[0..len], + .eb = eb, + }; + + if (packet_rx_sta_callback) |cb| { + cb(pb); + } return c.ESP_OK; } fn recv_cb_ap(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_err_t { - _ = buf; // autofix - _ = len; // autofix - _ = eb; // autofix - - log.debug("recv_cb_ap", .{}); + const pb: PacketBuffer = .{ + .data = @as([*]const u8, @ptrCast(buf))[0..len], + .eb = eb, + }; - @panic("recv_cb_ap"); + if (packet_rx_ap_callback) |cb| { + cb(pb); + } - // return c.ESP_OK; + return c.ESP_OK; } // I pupulated this with the defaults from rust. Some of it should be configurable. var init_config: c.wifi_init_config_t = .{ .osi_funcs = &g_wifi_osi_funcs, // .wpa_crypto_funcs = c.g_wifi_default_wpa_crypto_funcs, - .static_rx_buf_num = 3, //10, - .dynamic_rx_buf_num = 3, //32, + .static_rx_buf_num = 10, + .dynamic_rx_buf_num = 32, .tx_buf_type = c.CONFIG_ESP_WIFI_TX_BUFFER_TYPE, .static_tx_buf_num = 0, - .dynamic_tx_buf_num = 3, //32, + .dynamic_tx_buf_num = 32, .rx_mgmt_buf_type = c.CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER, .rx_mgmt_buf_num = c.CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF, .cache_tx_buf_num = c.WIFI_CACHE_TX_BUFFER_NUM, @@ -693,7 +750,7 @@ pub fn c_result(err_code: i32) InternalError!void { if (err_code != c.ESP_OK) { const err: InternalWifiError = @enumFromInt(err_code); - log.err("Internal wifi error occurred: {s}", .{@tagName(err)}); + log.err("internal wifi error occurred: {s}", .{@tagName(err)}); return error.InternalError; } } From ee5003857ed947040abdfe56c3e7fc6ec5dda965 Mon Sep 17 00:00:00 2001 From: tact1m4n3 Date: Fri, 18 Jul 2025 17:45:51 +0300 Subject: [PATCH 05/43] cleanup and more --- core/src/concurrency.zig | 38 + examples/espressif/esp/build.zig | 1 + examples/espressif/esp/src/wifi.zig | 98 +- port/espressif/esp/build.zig | 11 +- port/espressif/esp/build.zig.zon | 1 - port/espressif/esp/esp32_c3.ld | 2591 ----------------- port/espressif/esp/src/hal.zig | 4 + port/espressif/esp/src/hal/radio.zig | 83 +- .../esp/src/hal/radio/global_state.zig | 0 .../esp/src/hal/radio/multitasking.zig | 6 + port/espressif/esp/src/hal/radio/osi.zig | 261 +- port/espressif/esp/src/hal/radio/timer.zig | 45 +- port/espressif/esp/src/hal/radio/wifi.zig | 489 ++-- 13 files changed, 540 insertions(+), 3088 deletions(-) delete mode 100644 port/espressif/esp/esp32_c3.ld delete mode 100644 port/espressif/esp/src/hal/radio/global_state.zig diff --git a/core/src/concurrency.zig b/core/src/concurrency.zig index 0e7369acd..24b4fc403 100644 --- a/core/src/concurrency.zig +++ b/core/src/concurrency.zig @@ -159,3 +159,41 @@ test "AtomicStaticBitSet set_first_available" { const err = bs.set_first_available(); try std.testing.expectError(AtomicStaticBitSetError.NoAvailableBit, err); } + +// TODO: docs + tests + someone please verify it +pub fn SPSC_Queue(Item: type, capacity: usize) type { + return struct { + const Self = @This(); + + pub const empty: Self = .{ + .buf = undefined, + .read_index = 0, + .write_index = 0, + .len = .init(0), + }; + + buf: [capacity]Item, + read_index: usize, + write_index: usize, + len: std.atomic.Value(usize), + + pub fn enqueue(self: *Self, item: Item) error{NoSpace}!void { + if (self.len.load(.acquire) < capacity) { + self.buf[self.write_index] = item; + self.write_index = (self.write_index + 1) % capacity; + _ = self.len.fetchAdd(1, .acq_rel); + } else { + return error.NoSpace; + } + } + + pub fn dequeue(self: *Self) ?Item { + if (self.len.load(.acquire) == 0) return null; + + const item = self.buf[self.read_index]; + self.read_index = (self.read_index + 1) % capacity; + _ = self.len.fetchSub(1, .acq_rel); + return item; + } + }; +} diff --git a/examples/espressif/esp/build.zig b/examples/espressif/esp/build.zig index bcdaec7c7..88347a62d 100644 --- a/examples/espressif/esp/build.zig +++ b/examples/espressif/esp/build.zig @@ -98,6 +98,7 @@ pub fn build(b: *std.Build) void { .root_module = b.createModule(.{ .optimize = optimize, .target = resolved_zig_target, + .link_libc = false, }), .linkage = .static, }); diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index a110609e2..a60727db7 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -1,5 +1,6 @@ const std = @import("std"); const microzig = @import("microzig"); +const SPSC_Queue = microzig.concurrency.SPSC_Queue; const interrupt = microzig.cpu.interrupt; const hal = microzig.hal; const radio = hal.radio; @@ -33,6 +34,17 @@ pub const microzig_options: microzig.Options = .{ .interrupt2 = radio.interrupt_handlers.timer, .interrupt3 = radio.interrupt_handlers.software, }, + .hal = .{ + .radio = .{ + .wifi_interrupt = .interrupt1, + .timer_interrupt = .interrupt2, + .yield_interrupt = .interrupt3, + // .timer = .{ .systimer = .{ + // .unit = .unit0, + // .alarm = .alarm0, + // } }, + }, + }, }; var buffer: [50 * 1024]u8 = undefined; @@ -60,20 +72,24 @@ pub fn main() !void { c.netif_set_up(&netif); _ = c.dhcp_start(&netif); - try radio.wifi.set_mode(.sta); - try radio.wifi.set_client_config(.{ - .ssid = "Internet", + try radio.wifi.apply(.{ + .sta = .{ + .ssid = "Internet", + }, }); radio.wifi.set_packet_rx_sta_callback(rx_callback); try radio.wifi.start(); + // non blocking try radio.wifi.connect(); var connected: bool = false; var last_mem_show = hal.time.get_time_since_boot(); while (true) { + radio.tick(); + const sta_state = radio.wifi.get_sta_state(); if (!connected and sta_state == .sta_connected) { std.log.info("link up", .{}); @@ -103,10 +119,31 @@ pub fn main() !void { const now = hal.time.get_time_since_boot(); if (!now.diff(last_mem_show).less_than(.from_ms(1000))) { const used_mem = 50 * 1024 - alloc.free_heap(); - std.log.info("used memory: {}K ({})", .{used_mem / 1024, used_mem}); + std.log.info("used memory: {}K ({})", .{ used_mem / 1024, used_mem }); last_mem_show = now; } hal.time.sleep_ms(100); + + // if (events.wifi.contains(.Connected)) { + // c.netif_set_link_up(&netif); + // } + // if (events.contains(.Disconnected)) { + // c.netif_set_link_down(&netif); + // } + // + // while (radio.wifi.recv_packet()) |packet| { + // netif.input.?(packet); + // } + // + // c.sys_check_timeouts(); + // + // const now = hal.time.get_time_since_boot(); + // if (!now.diff(last_mem_show).less_than(.from_ms(1000))) { + // const used_mem = 50 * 1024 - alloc.free_heap(); + // std.log.info("used memory: {}K ({})", .{ used_mem / 1024, used_mem }); + // last_mem_show = now; + // } + // hal.time.sleep_ms(100); } // var ssid: [1:0]u8 = @splat(0); @@ -136,10 +173,11 @@ pub fn main() !void { // std.log.info("found {} aps", .{no}); } -var rx_queue: Queue(*c.struct_pbuf, 10) = .empty; +var rx_queue: SPSC_Queue(*c.struct_pbuf, 10) = .empty; -fn rx_callback(packet: radio.wifi.PacketBuffer) void { +fn rx_callback(packet: radio.wifi.ReceivedPacket) void { defer packet.deinit(); + // std.log.info("receiving packet", .{}); const maybe_pbuf: ?*c.struct_pbuf = c.pbuf_alloc(c.PBUF_RAW, @intCast(packet.data.len), c.PBUF_POOL); @@ -164,7 +202,7 @@ fn netif_init(netif_c: [*c]c.struct_netif) callconv(.c) c.err_t { netif.output_ip6 = c.ethip6_output; netif.mtu = 1500; netif.flags = c.NETIF_FLAG_BROADCAST | c.NETIF_FLAG_ETHARP | c.NETIF_FLAG_ETHERNET | c.NETIF_FLAG_IGMP | c.NETIF_FLAG_MLD6; - @memcpy(&netif.hwaddr, &radio.read_mac()); + @memcpy(&netif.hwaddr, &radio.read_mac(.sta)); netif.hwaddr_len = 6; return c.ERR_OK; @@ -173,14 +211,11 @@ fn netif_init(netif_c: [*c]c.struct_netif) callconv(.c) c.err_t { var packet_buf: [1500]u8 = undefined; fn netif_output(netif: [*c]c.struct_netif, pbuf_c: [*c]c.struct_pbuf) callconv(.c) c.err_t { - _ = netif; // autofix + _ = netif; const pbuf: *c.struct_pbuf = pbuf_c; // std.log.info("sending packet", .{}); - // const cs = microzig.interrupt.enter_critical_section(); - // defer cs.leave(); - var off: usize = 0; while (off < pbuf.tot_len) { const cnt = c.pbuf_copy_partial(pbuf, packet_buf[off..].ptr, @as(u15, @intCast(pbuf.tot_len - off)), @as(u15, @intCast(off))); @@ -218,47 +253,10 @@ fn netif_status_callback(netif_c: [*c]c.netif) callconv(.C) void { std.log.info("netif status changed ip to {}", .{IPFormatter.init(netif.ip_addr)}); } -comptime { - _ = sys_now; -} - export fn sys_now() callconv(.c) u32 { return @truncate(hal.time.get_time_since_boot().to_us() * 1_000); } -fn Queue(Item: type, capacity: usize) type { - return struct { - const Self = @This(); - - pub const empty: Self = .{ - .buf = undefined, - .read_index = 0, - .write_index = 0, - .len = 0, - }; - - buf: [capacity]Item, - read_index: usize, - write_index: usize, - len: usize, - - pub fn enqueue(self: *Self, item: Item) error{NoSpace}!void { - if (self.len < capacity) { - self.buf[self.write_index] = item; - self.write_index = (self.write_index + 1) % capacity; - self.len += 1; - } else { - return error.NoSpace; - } - } - - pub fn dequeue(self: *Self) ?Item { - if (self.len == 0) return null; - - const item = self.buf[self.read_index]; - self.read_index = (self.read_index + 1) % capacity; - self.len -= 1; - return item; - } - }; +export fn rand() callconv(.c) i32 { + return @bitCast(hal.rng.random_u32()); } diff --git a/port/espressif/esp/build.zig b/port/espressif/esp/build.zig index b6ec5837e..04d9bd441 100644 --- a/port/espressif/esp/build.zig +++ b/port/espressif/esp/build.zig @@ -171,12 +171,6 @@ pub fn build(b: *std.Build) void { translate_c.addIncludePath(esp_wifi_sys_dep.path("esp-wifi-sys/headers/esp32c3")); const mod = translate_c.addModule("esp-wifi-driver"); - - const foundation_libc_dep = b.dependency("microzig/modules/foundation-libc", .{ - .target = esp32_c3_resolved_zig_target, - }); - mod.linkLibrary(foundation_libc_dep.artifact("foundation")); - mod.addLibraryPath(esp_wifi_sys_dep.path("esp-wifi-sys/libs/esp32c3")); inline for (&.{ "btbb", @@ -191,10 +185,13 @@ pub fn build(b: *std.Build) void { "smartconfig", "wapi", "wpa_supplicant", - "printf", }) |library| { mod.linkSystemLibrary(library, .{}); } + + mod.linkSystemLibrary("printf", .{ + .weak = true, + }); } const BootMode = enum { diff --git a/port/espressif/esp/build.zig.zon b/port/espressif/esp/build.zig.zon index 5263b6e34..765d844e3 100644 --- a/port/espressif/esp/build.zig.zon +++ b/port/espressif/esp/build.zig.zon @@ -4,7 +4,6 @@ .version = "0.0.0", .dependencies = .{ .@"microzig/build-internals" = .{ .path = "../../../build-internals" }, - .@"microzig/modules/foundation-libc" = .{ .path = "../../../modules/foundation-libc" }, .@"microzig/modules/riscv32-common" = .{ .path = "../../../modules/riscv32-common" }, .@"esp-wifi-sys" = .{ .url = "git+https://github.com/esp-rs/esp-wifi-sys#99dd43f9992b276efff3c75467839801e7ea55c7", diff --git a/port/espressif/esp/esp32_c3.ld b/port/espressif/esp/esp32_c3.ld deleted file mode 100644 index ae9ad7af1..000000000 --- a/port/espressif/esp/esp32_c3.ld +++ /dev/null @@ -1,2591 +0,0 @@ -/* - * This file was auto-generated by microzig - * - * Target CPU: generic_rv32 - * Target Chip: ESP32-C3 - */ - -ENTRY(_start); - -MEMORY -{ - irom (rx) : ORIGIN = 0x42000020, LENGTH = 0x800000 - 0x20 - drom (r) : ORIGIN = 0x3C000020, LENGTH = 0x800000 - 0x20 - iram (rwx) : ORIGIN = 0x40380000, LENGTH = 313K - dram (rw) : ORIGIN = 0x3FC80000, LENGTH = 313K -} - -SECTIONS -{ - .irom.text : - { - KEEP(*(microzig_flash_start)) - *(.text*) - . += 16; - } > irom - - .drom.dummy (NOLOAD) : - { - . = ALIGN(ALIGNOF(.irom.text)) + SIZEOF(.irom.text); - . = ALIGN(0x10000) + 0x20; - } > drom - - .drom.rodata : ALIGN(0x10) - { - KEEP(*(.app_desc)) - *(.rodata*) - *(.srodata*) - - /* wifi rodata */ - *(.rodata_wlog_*.*) - - . = ALIGN(0x10); - } > drom - - .iram.rwtext : ALIGN(4) - { - . = ALIGN(4); - - KEEP(*(.trap)) - KEEP(*(.ram_text)) - *(.rwtext*) - - /* wifi rwtext */ - *(.wifi0iram .wifi0iram.*) - *(.wifirxiram .wifirxiram.*) - *(.wifislprxiram .wifislprxiram.*) - *(.wifislpiram .wifislpiram.*) - *(.phyiram .phyiram.*) - *(.iram1 .iram1.*) - *(.wifiextrairam.* ) - *(.coexiram.* ) - } > iram - - .dram.dummy (NOLOAD) : - { - . = ALIGN(ALIGNOF(.iram.rwtext)) + SIZEOF(.iram.rwtext); - } > dram - - .dram.data : - { - microzig_data_start = .; - *(.sdata*) - *(.data*) - - /* wifi data */ - *(.dram1 .dram1.*) - } > dram - - .dram.bss (NOLOAD) : - { - microzig_bss_start = .; - *(.bss*) - *(.sbss*) - microzig_bss_end = .; - } > dram - - PROVIDE(__global_pointer$ = microzig_data_start + 0x800); -} - -/* ROM function interface esp32c3.rom.ld for esp32c3 - * - * - * Generated from ./interface-esp32c3.yml md5sum 93b28a9e1fe42d212018eb4336849208 - * - * Compatible with ROM where ECO version equal or greater to 0. - * - * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT. - */ - -/*************************************** - Group common - ***************************************/ - -/* Functions */ -rtc_get_reset_reason = 0x40000018; -analog_super_wdt_reset_happened = 0x4000001c; -jtag_cpu_reset_happened = 0x40000020; -rtc_get_wakeup_cause = 0x40000024; -rtc_boot_control = 0x40000028; -rtc_select_apb_bridge = 0x4000002c; -rtc_unhold_all_pads = 0x40000030; -set_rtc_memory_crc = 0x40000034; -cacl_rtc_memory_crc = 0x40000038; -ets_is_print_boot = 0x4000003c; -ets_printf = 0x40000040; -ets_install_putc1 = 0x40000044; -ets_install_uart_printf = 0x40000048; -ets_install_putc2 = 0x4000004c; -PROVIDE( ets_delay_us = 0x40000050 ); -ets_get_stack_info = 0x40000054; -ets_install_lock = 0x40000058; -ets_backup_dma_copy = 0x4000005c; -ets_apb_backup_init_lock_func = 0x40000060; -UartRxString = 0x40000064; -uart_tx_one_char = 0x40000068; -uart_tx_one_char2 = 0x4000006c; -uart_rx_one_char = 0x40000070; -uart_rx_one_char_block = 0x40000074; -uart_rx_readbuff = 0x40000078; -uartAttach = 0x4000007c; -uart_tx_flush = 0x40000080; -uart_tx_wait_idle = 0x40000084; -uart_div_modify = 0x40000088; -multofup = 0x4000008c; -software_reset = 0x40000090; -software_reset_cpu = 0x40000094; -assist_debug_clock_enable = 0x40000098; -assist_debug_record_enable = 0x4000009c; -clear_super_wdt_reset_flag = 0x400000a0; -disable_default_watchdog = 0x400000a4; -send_packet = 0x400000a8; -recv_packet = 0x400000ac; -GetUartDevice = 0x400000b0; -UartDwnLdProc = 0x400000b4; -Uart_Init = 0x400000b8; -ets_set_user_start = 0x400000bc; -/* Data (.data, .bss, .rodata) */ -ets_rom_layout_p = 0x3ff1fffc; -ets_ops_table_ptr = 0x3fcdfffc; - - -/*************************************** - Group miniz - ***************************************/ - -/* Functions */ -mz_adler32 = 0x400000c0; -mz_crc32 = 0x400000c4; -mz_free = 0x400000c8; -tdefl_compress = 0x400000cc; -tdefl_compress_buffer = 0x400000d0; -tdefl_compress_mem_to_heap = 0x400000d4; -tdefl_compress_mem_to_mem = 0x400000d8; -tdefl_compress_mem_to_output = 0x400000dc; -tdefl_get_adler32 = 0x400000e0; -tdefl_get_prev_return_status = 0x400000e4; -tdefl_init = 0x400000e8; -tdefl_write_image_to_png_file_in_memory = 0x400000ec; -tdefl_write_image_to_png_file_in_memory_ex = 0x400000f0; -tinfl_decompress = 0x400000f4; -tinfl_decompress_mem_to_callback = 0x400000f8; -tinfl_decompress_mem_to_heap = 0x400000fc; -tinfl_decompress_mem_to_mem = 0x40000100; - - -/*************************************** - Group tjpgd - ***************************************/ - -/* Functions */ -PROVIDE( jd_prepare = 0x40000104 ); -PROVIDE( jd_decomp = 0x40000108 ); - - -/*************************************** - Group spiflash_legacy - ***************************************/ - -/* Functions */ -PROVIDE( esp_rom_spiflash_wait_idle = 0x4000010c ); -PROVIDE( esp_rom_spiflash_write_encrypted = 0x40000110 ); -PROVIDE( esp_rom_spiflash_write_encrypted_dest = 0x40000114 ); -PROVIDE( esp_rom_spiflash_write_encrypted_enable = 0x40000118 ); -PROVIDE( esp_rom_spiflash_write_encrypted_disable = 0x4000011c ); -PROVIDE( esp_rom_spiflash_erase_chip = 0x40000120 ); -PROVIDE( esp_rom_spiflash_erase_block = 0x40000124 ); -PROVIDE( esp_rom_spiflash_erase_sector = 0x40000128 ); -PROVIDE( esp_rom_spiflash_write = 0x4000012c ); -PROVIDE( esp_rom_spiflash_read = 0x40000130 ); -PROVIDE( esp_rom_spiflash_config_param = 0x40000134 ); -PROVIDE( esp_rom_spiflash_read_user_cmd = 0x40000138 ); -PROVIDE( esp_rom_spiflash_select_qio_pins = 0x4000013c ); -PROVIDE( esp_rom_spiflash_unlock = 0x40000140 ); -PROVIDE( esp_rom_spi_flash_auto_sus_res = 0x40000144 ); -PROVIDE( esp_rom_spi_flash_send_resume = 0x40000148 ); -PROVIDE( esp_rom_spi_flash_update_id = 0x4000014c ); -PROVIDE( esp_rom_spiflash_config_clk = 0x40000150 ); -PROVIDE( esp_rom_spiflash_config_readmode = 0x40000154 ); -PROVIDE( esp_rom_spiflash_read_status = 0x40000158 ); -PROVIDE( esp_rom_spiflash_read_statushigh = 0x4000015c ); -PROVIDE( esp_rom_spiflash_write_status = 0x40000160 ); -PROVIDE( esp_rom_spiflash_attach = 0x40000164 ); -PROVIDE( spi_flash_get_chip_size = 0x40000168 ); -PROVIDE( spi_flash_guard_set = 0x4000016c ); -PROVIDE( spi_flash_guard_get = 0x40000170 ); -PROVIDE( spi_flash_write_config_set = 0x40000174 ); -PROVIDE( spi_flash_write_config_get = 0x40000178 ); -PROVIDE( spi_flash_safe_write_address_func_set = 0x4000017c ); -PROVIDE( spi_flash_unlock = 0x40000180 ); -PROVIDE( spi_flash_erase_range = 0x40000184 ); -PROVIDE( spi_flash_erase_sector = 0x40000188 ); -PROVIDE( spi_flash_write = 0x4000018c ); -PROVIDE( spi_flash_read = 0x40000190 ); -PROVIDE( spi_flash_write_encrypted = 0x40000194 ); -PROVIDE( spi_flash_read_encrypted = 0x40000198 ); -PROVIDE( spi_flash_mmap_os_func_set = 0x4000019c ); -PROVIDE( spi_flash_mmap_page_num_init = 0x400001a0 ); -PROVIDE( spi_flash_mmap = 0x400001a4 ); -PROVIDE( spi_flash_mmap_pages = 0x400001a8 ); -PROVIDE( spi_flash_munmap = 0x400001ac ); -PROVIDE( spi_flash_mmap_dump = 0x400001b0 ); -PROVIDE( spi_flash_check_and_flush_cache = 0x400001b4 ); -PROVIDE( spi_flash_mmap_get_free_pages = 0x400001b8 ); -PROVIDE( spi_flash_cache2phys = 0x400001bc ); -PROVIDE( spi_flash_phys2cache = 0x400001c0 ); -PROVIDE( spi_flash_disable_cache = 0x400001c4 ); -PROVIDE( spi_flash_restore_cache = 0x400001c8 ); -PROVIDE( spi_flash_cache_enabled = 0x400001cc ); -PROVIDE( spi_flash_enable_cache = 0x400001d0 ); -PROVIDE( spi_cache_mode_switch = 0x400001d4 ); -PROVIDE( spi_common_set_dummy_output = 0x400001d8 ); -PROVIDE( spi_common_set_flash_cs_timing = 0x400001dc ); -PROVIDE( esp_enable_cache_flash_wrap = 0x400001e0 ); -PROVIDE( SPIEraseArea = 0x400001e4 ); -PROVIDE( SPILock = 0x400001e8 ); -PROVIDE( SPIMasterReadModeCnfig = 0x400001ec ); -PROVIDE( SPI_Common_Command = 0x400001f0 ); -PROVIDE( SPI_WakeUp = 0x400001f4 ); -PROVIDE( SPI_block_erase = 0x400001f8 ); -PROVIDE( SPI_chip_erase = 0x400001fc ); -PROVIDE( SPI_init = 0x40000200 ); -PROVIDE( SPI_page_program = 0x40000204 ); -PROVIDE( SPI_read_data = 0x40000208 ); -PROVIDE( SPI_sector_erase = 0x4000020c ); -PROVIDE( SPI_write_enable = 0x40000210 ); -PROVIDE( SelectSpiFunction = 0x40000214 ); -PROVIDE( SetSpiDrvs = 0x40000218 ); -PROVIDE( Wait_SPI_Idle = 0x4000021c ); -PROVIDE( spi_dummy_len_fix = 0x40000220 ); -PROVIDE( Disable_QMode = 0x40000224 ); -PROVIDE( Enable_QMode = 0x40000228 ); -/* Data (.data, .bss, .rodata) */ -PROVIDE( rom_spiflash_legacy_funcs = 0x3fcdfff4 ); -PROVIDE( rom_spiflash_legacy_data = 0x3fcdfff0 ); -PROVIDE( g_flash_guard_ops = 0x3fcdfff8 ); - - -/*************************************** - Group spi_flash_hal - ***************************************/ - -/* Functions */ -PROVIDE( spi_flash_hal_poll_cmd_done = 0x4000022c ); -PROVIDE( spi_flash_hal_device_config = 0x40000230 ); -PROVIDE( spi_flash_hal_configure_host_io_mode = 0x40000234 ); -PROVIDE( spi_flash_hal_common_command = 0x40000238 ); -PROVIDE( spi_flash_hal_read = 0x4000023c ); -PROVIDE( spi_flash_hal_erase_chip = 0x40000240 ); -PROVIDE( spi_flash_hal_erase_sector = 0x40000244 ); -PROVIDE( spi_flash_hal_erase_block = 0x40000248 ); -PROVIDE( spi_flash_hal_program_page = 0x4000024c ); -PROVIDE( spi_flash_hal_set_write_protect = 0x40000250 ); -PROVIDE( spi_flash_hal_host_idle = 0x40000254 ); - - -/*************************************** - Group spi_flash_chips - ***************************************/ - -/* Functions */ -PROVIDE( spi_flash_chip_generic_probe = 0x40000258 ); -PROVIDE( spi_flash_chip_generic_detect_size = 0x4000025c ); -PROVIDE( spi_flash_chip_generic_write = 0x40000260 ); -PROVIDE( spi_flash_chip_generic_write_encrypted = 0x40000264 ); -PROVIDE( spi_flash_chip_generic_set_write_protect = 0x40000268 ); -PROVIDE( spi_flash_common_write_status_16b_wrsr = 0x4000026c ); -PROVIDE( spi_flash_chip_generic_reset = 0x40000270 ); -PROVIDE( spi_flash_chip_generic_erase_chip = 0x40000274 ); -PROVIDE( spi_flash_chip_generic_erase_sector = 0x40000278 ); -PROVIDE( spi_flash_chip_generic_erase_block = 0x4000027c ); -PROVIDE( spi_flash_chip_generic_page_program = 0x40000280 ); -PROVIDE( spi_flash_chip_generic_get_write_protect = 0x40000284 ); -PROVIDE( spi_flash_common_read_status_16b_rdsr_rdsr2 = 0x40000288 ); -PROVIDE( spi_flash_chip_generic_read_reg = 0x4000028c ); -PROVIDE( spi_flash_chip_generic_yield = 0x40000290 ); -PROVIDE( spi_flash_generic_wait_host_idle = 0x40000294 ); -PROVIDE( spi_flash_chip_generic_wait_idle = 0x40000298 ); -PROVIDE( spi_flash_chip_generic_config_host_io_mode = 0x4000029c ); -PROVIDE( spi_flash_chip_generic_read = 0x400002a0 ); -PROVIDE( spi_flash_common_read_status_8b_rdsr2 = 0x400002a4 ); -PROVIDE( spi_flash_chip_generic_get_io_mode = 0x400002a8 ); -PROVIDE( spi_flash_common_read_status_8b_rdsr = 0x400002ac ); -PROVIDE( spi_flash_common_write_status_8b_wrsr = 0x400002b0 ); -PROVIDE( spi_flash_common_write_status_8b_wrsr2 = 0x400002b4 ); -PROVIDE( spi_flash_common_set_io_mode = 0x400002b8 ); -PROVIDE( spi_flash_chip_generic_set_io_mode = 0x400002bc ); -PROVIDE( spi_flash_chip_gd_get_io_mode = 0x400002c0 ); -PROVIDE( spi_flash_chip_gd_probe = 0x400002c4 ); -PROVIDE( spi_flash_chip_gd_set_io_mode = 0x400002c8 ); -/* Data (.data, .bss, .rodata) */ -PROVIDE( spi_flash_chip_generic_config_data = 0x3fcdffec ); - - -/*************************************** - Group memspi_host - ***************************************/ - -/* Functions */ -PROVIDE( memspi_host_read_id_hs = 0x400002cc ); -PROVIDE( memspi_host_read_status_hs = 0x400002d0 ); -PROVIDE( memspi_host_flush_cache = 0x400002d4 ); -PROVIDE( memspi_host_erase_chip = 0x400002d8 ); -PROVIDE( memspi_host_erase_sector = 0x400002dc ); -PROVIDE( memspi_host_erase_block = 0x400002e0 ); -PROVIDE( memspi_host_program_page = 0x400002e4 ); -PROVIDE( memspi_host_read = 0x400002e8 ); -PROVIDE( memspi_host_set_write_protect = 0x400002ec ); -PROVIDE( memspi_host_set_max_read_len = 0x400002f0 ); -PROVIDE( memspi_host_read_data_slicer = 0x400002f4 ); -PROVIDE( memspi_host_write_data_slicer = 0x400002f8 ); - - -/*************************************** - Group esp_flash - ***************************************/ - -/* Functions */ -PROVIDE( esp_flash_chip_driver_initialized = 0x400002fc ); -PROVIDE( esp_flash_read_id = 0x40000300 ); -PROVIDE( esp_flash_get_size = 0x40000304 ); -PROVIDE( esp_flash_erase_chip = 0x40000308 ); -PROVIDE( rom_esp_flash_erase_region = 0x4000030c ); -PROVIDE( esp_flash_get_chip_write_protect = 0x40000310 ); -PROVIDE( esp_flash_set_chip_write_protect = 0x40000314 ); -PROVIDE( esp_flash_get_protectable_regions = 0x40000318 ); -PROVIDE( esp_flash_get_protected_region = 0x4000031c ); -PROVIDE( esp_flash_set_protected_region = 0x40000320 ); -PROVIDE( esp_flash_read = 0x40000324 ); -PROVIDE( esp_flash_write = 0x40000328 ); -PROVIDE( esp_flash_write_encrypted = 0x4000032c ); -PROVIDE( esp_flash_read_encrypted = 0x40000330 ); -PROVIDE( esp_flash_get_io_mode = 0x40000334 ); -PROVIDE( esp_flash_set_io_mode = 0x40000338 ); -PROVIDE( spi_flash_boot_attach = 0x4000033c ); -PROVIDE( spi_flash_dump_counters = 0x40000340 ); -PROVIDE( spi_flash_get_counters = 0x40000344 ); -PROVIDE( spi_flash_op_counters_config = 0x40000348 ); -PROVIDE( spi_flash_reset_counters = 0x4000034c ); -/* Data (.data, .bss, .rodata) */ -PROVIDE( esp_flash_default_chip = 0x3fcdffe8 ); -PROVIDE( esp_flash_api_funcs = 0x3fcdffe4 ); - - -/*************************************** - Group cache - ***************************************/ - -/* Functions */ -PROVIDE( Cache_Get_ICache_Line_Size = 0x400004b0 ); -PROVIDE( Cache_Get_Mode = 0x400004b4 ); -PROVIDE( Cache_Address_Through_IBus = 0x400004b8 ); -PROVIDE( Cache_Address_Through_DBus = 0x400004bc ); -PROVIDE( Cache_Set_Default_Mode = 0x400004c0 ); -PROVIDE( Cache_Enable_Defalut_ICache_Mode = 0x400004c4 ); -PROVIDE( ROM_Boot_Cache_Init = 0x400004c8 ); -PROVIDE( Cache_Invalidate_ICache_Items = 0x400004cc ); -PROVIDE( Cache_Op_Addr = 0x400004d0 ); -PROVIDE( Cache_Invalidate_Addr = 0x400004d4 ); -PROVIDE( Cache_Invalidate_ICache_All = 0x400004d8 ); -PROVIDE( Cache_Mask_All = 0x400004dc ); -PROVIDE( Cache_UnMask_Dram0 = 0x400004e0 ); -PROVIDE( Cache_Suspend_ICache_Autoload = 0x400004e4 ); -PROVIDE( Cache_Resume_ICache_Autoload = 0x400004e8 ); -PROVIDE( Cache_Start_ICache_Preload = 0x400004ec ); -PROVIDE( Cache_ICache_Preload_Done = 0x400004f0 ); -PROVIDE( Cache_End_ICache_Preload = 0x400004f4 ); -PROVIDE( Cache_Config_ICache_Autoload = 0x400004f8 ); -PROVIDE( Cache_Enable_ICache_Autoload = 0x400004fc ); -PROVIDE( Cache_Disable_ICache_Autoload = 0x40000500 ); -PROVIDE( Cache_Enable_ICache_PreLock = 0x40000504 ); -PROVIDE( Cache_Disable_ICache_PreLock = 0x40000508 ); -PROVIDE( Cache_Lock_ICache_Items = 0x4000050c ); -PROVIDE( Cache_Unlock_ICache_Items = 0x40000510 ); -PROVIDE( Cache_Lock_Addr = 0x40000514 ); -PROVIDE( Cache_Unlock_Addr = 0x40000518 ); -PROVIDE( Cache_Disable_ICache = 0x4000051c ); -PROVIDE( Cache_Enable_ICache = 0x40000520 ); -PROVIDE( Cache_Suspend_ICache = 0x40000524 ); -PROVIDE( Cache_Resume_ICache = 0x40000528 ); -PROVIDE( Cache_Freeze_ICache_Enable = 0x4000052c ); -PROVIDE( Cache_Freeze_ICache_Disable = 0x40000530 ); -PROVIDE( Cache_Pms_Lock = 0x40000534 ); -PROVIDE( Cache_Ibus_Pms_Set_Addr = 0x40000538 ); -PROVIDE( Cache_Ibus_Pms_Set_Attr = 0x4000053c ); -PROVIDE( Cache_Dbus_Pms_Set_Addr = 0x40000540 ); -PROVIDE( Cache_Dbus_Pms_Set_Attr = 0x40000544 ); -PROVIDE( Cache_Set_IDROM_MMU_Size = 0x40000548 ); -PROVIDE( Cache_Get_IROM_MMU_End = 0x4000054c ); -PROVIDE( Cache_Get_DROM_MMU_End = 0x40000550 ); -PROVIDE( Cache_Owner_Init = 0x40000554 ); -PROVIDE( Cache_Occupy_ICache_MEMORY = 0x40000558 ); -PROVIDE( Cache_MMU_Init = 0x4000055c ); -PROVIDE( Cache_Ibus_MMU_Set = 0x40000560 ); -PROVIDE( Cache_Dbus_MMU_Set = 0x40000564 ); -PROVIDE( Cache_Count_Flash_Pages = 0x40000568 ); -PROVIDE( Cache_Travel_Tag_Memory = 0x4000056c ); -PROVIDE( Cache_Get_Virtual_Addr = 0x40000570 ); -PROVIDE( Cache_Get_Memory_BaseAddr = 0x40000574 ); -PROVIDE( Cache_Get_Memory_Addr = 0x40000578 ); -PROVIDE( Cache_Get_Memory_value = 0x4000057c ); -/* Data (.data, .bss, .rodata) */ -PROVIDE( rom_cache_op_cb = 0x3fcdffd8 ); -PROVIDE( rom_cache_internal_table_ptr = 0x3fcdffd4 ); - - -/*************************************** - Group clock - ***************************************/ - -/* Functions */ -ets_get_apb_freq = 0x40000580; -ets_get_cpu_frequency = 0x40000584; -ets_update_cpu_frequency = 0x40000588; -ets_get_printf_channel = 0x4000058c; -ets_get_xtal_div = 0x40000590; -ets_set_xtal_div = 0x40000594; -ets_get_xtal_freq = 0x40000598; - - -/*************************************** - Group gpio - ***************************************/ - -/* Functions */ -gpio_input_get = 0x4000059c; -gpio_matrix_in = 0x400005a0; -gpio_matrix_out = 0x400005a4; -gpio_output_disable = 0x400005a8; -gpio_output_enable = 0x400005ac; -gpio_output_set = 0x400005b0; -gpio_pad_hold = 0x400005b4; -gpio_pad_input_disable = 0x400005b8; -gpio_pad_input_enable = 0x400005bc; -gpio_pad_pulldown = 0x400005c0; -gpio_pad_pullup = 0x400005c4; -gpio_pad_select_gpio = 0x400005c8; -gpio_pad_set_drv = 0x400005cc; -gpio_pad_unhold = 0x400005d0; -gpio_pin_wakeup_disable = 0x400005d4; -gpio_pin_wakeup_enable = 0x400005d8; -gpio_bypass_matrix_in = 0x400005dc; - - -/*************************************** - Group interrupts - ***************************************/ - -/* Functions */ -esprv_intc_int_set_priority = 0x400005e0; -esprv_intc_int_set_threshold = 0x400005e4; -esprv_intc_int_enable = 0x400005e8; -esprv_intc_int_disable = 0x400005ec; -esprv_intc_int_set_type = 0x400005f0; -intr_matrix_set = 0x400005f4; -ets_intr_lock = 0x400005f8; -ets_intr_unlock = 0x400005fc; -PROVIDE( intr_handler_set = 0x40000600 ); -ets_isr_attach = 0x40000604; -ets_isr_mask = 0x40000608; -ets_isr_unmask = 0x4000060c; - - -/*************************************** - Group crypto - ***************************************/ - -/* Functions */ -md5_vector = 0x40000610; -MD5Init = 0x40000614; -MD5Update = 0x40000618; -MD5Final = 0x4000061c; -hmac_md5_vector = 0x40000620; -hmac_md5 = 0x40000624; -crc32_le = 0x40000628; -crc32_be = 0x4000062c; -crc16_le = 0x40000630; -crc16_be = 0x40000634; -crc8_le = 0x40000638; -crc8_be = 0x4000063c; -esp_crc8 = 0x40000640; -ets_sha_enable = 0x40000644; -ets_sha_disable = 0x40000648; -ets_sha_get_state = 0x4000064c; -ets_sha_init = 0x40000650; -ets_sha_process = 0x40000654; -ets_sha_starts = 0x40000658; -ets_sha_update = 0x4000065c; -ets_sha_finish = 0x40000660; -ets_sha_clone = 0x40000664; -ets_hmac_enable = 0x40000668; -ets_hmac_disable = 0x4000066c; -ets_hmac_calculate_message = 0x40000670; -ets_hmac_calculate_downstream = 0x40000674; -ets_hmac_invalidate_downstream = 0x40000678; -ets_jtag_enable_temporarily = 0x4000067c; -ets_aes_enable = 0x40000680; -ets_aes_disable = 0x40000684; -ets_aes_setkey = 0x40000688; -ets_aes_block = 0x4000068c; -ets_bigint_enable = 0x40000690; -ets_bigint_disable = 0x40000694; -ets_bigint_multiply = 0x40000698; -ets_bigint_modmult = 0x4000069c; -ets_bigint_modexp = 0x400006a0; -ets_bigint_wait_finish = 0x400006a4; -ets_bigint_getz = 0x400006a8; -ets_ds_enable = 0x400006ac; -ets_ds_disable = 0x400006b0; -ets_ds_start_sign = 0x400006b4; -ets_ds_is_busy = 0x400006b8; -ets_ds_finish_sign = 0x400006bc; -ets_ds_encrypt_params = 0x400006c0; -ets_aes_setkey_dec = 0x400006c4; -ets_aes_setkey_enc = 0x400006c8; -ets_mgf1_sha256 = 0x400006cc; - - -/*************************************** - Group efuse - ***************************************/ - -/* Functions */ -ets_efuse_read = 0x400006d0; -ets_efuse_program = 0x400006d4; -ets_efuse_clear_program_registers = 0x400006d8; -ets_efuse_write_key = 0x400006dc; -ets_efuse_get_read_register_address = 0x400006e0; -ets_efuse_get_key_purpose = 0x400006e4; -ets_efuse_key_block_unused = 0x400006e8; -ets_efuse_find_unused_key_block = 0x400006ec; -ets_efuse_rs_calculate = 0x400006f0; -ets_efuse_count_unused_key_blocks = 0x400006f4; -ets_efuse_secure_boot_enabled = 0x400006f8; -ets_efuse_secure_boot_aggressive_revoke_enabled = 0x400006fc; -ets_efuse_cache_encryption_enabled = 0x40000700; -ets_efuse_download_modes_disabled = 0x40000704; -ets_efuse_find_purpose = 0x40000708; -ets_efuse_flash_opi_5pads_power_sel_vddspi = 0x4000070c; -ets_efuse_force_send_resume = 0x40000710; -ets_efuse_get_flash_delay_us = 0x40000714; -ets_efuse_get_mac = 0x40000718; -ets_efuse_get_spiconfig = 0x4000071c; -ets_efuse_usb_print_is_disabled = 0x40000720; -/*ets_efuse_get_uart_print_channel = 0x40000724;*/ -ets_efuse_usb_serial_jtag_print_is_disabled = 0x40000724; -ets_efuse_get_uart_print_control = 0x40000728; -ets_efuse_get_wp_pad = 0x4000072c; -ets_efuse_legacy_spi_boot_mode_disabled = 0x40000730; -ets_efuse_security_download_modes_enabled = 0x40000734; -ets_efuse_set_timing = 0x40000738; -ets_efuse_jtag_disabled = 0x4000073c; -ets_efuse_usb_download_mode_disabled = 0x40000740; -ets_efuse_usb_module_disabled = 0x40000744; -ets_efuse_usb_device_disabled = 0x40000748; - - -/*************************************** - Group secureboot - ***************************************/ - -/* Functions */ -ets_emsa_pss_verify = 0x4000074c; -ets_rsa_pss_verify = 0x40000750; -ets_secure_boot_verify_bootloader_with_keys = 0x40000754; -ets_secure_boot_verify_signature = 0x40000758; -ets_secure_boot_read_key_digests = 0x4000075c; -ets_secure_boot_revoke_public_key_digest = 0x40000760; - - -/*************************************** - Group usb_uart - ***************************************/ - -/* Functions */ -PROVIDE( usb_uart_rx_one_char = 0x400008cc ); -PROVIDE( usb_uart_rx_one_char_block = 0x400008d0 ); -PROVIDE( usb_uart_tx_flush = 0x400008d4 ); -PROVIDE( usb_uart_tx_one_char = 0x400008d8 ); -/* Data (.data, .bss, .rodata) */ -PROVIDE( g_uart_print = 0x3fcdffd1 ); -PROVIDE( g_usb_print = 0x3fcdffd0 ); - - -/*************************************** - Group bluetooth - ***************************************/ - -/* Functions */ -bt_rf_coex_get_dft_cfg = 0x400008dc; -bt_rf_coex_hooks_p_set = 0x400008e0; -btdm_con_maxevtime_cal_impl = 0x400008e4; -btdm_controller_get_compile_version_impl = 0x400008e8; -btdm_controller_rom_data_init = 0x400008ec; -btdm_dis_privacy_err_report_impl = 0x400008f0; -btdm_disable_adv_delay_impl = 0x400008f4; -btdm_enable_scan_continue_impl = 0x400008f8; -btdm_enable_scan_forever_impl = 0x400008fc; -btdm_get_power_state_impl = 0x40000900; -btdm_get_prevent_sleep_flag_impl = 0x40000904; -btdm_power_state_active_impl = 0x40000908; -btdm_switch_phy_coded_impl = 0x4000090c; -hci_acl_data_handler = 0x40000910; -hci_disconnect_cmd_handler = 0x40000914; -hci_le_con_upd_cmd_handler = 0x40000918; -hci_le_ltk_req_neg_reply_cmd_handler = 0x4000091c; -hci_le_ltk_req_reply_cmd_handler = 0x40000920; -hci_le_rd_chnl_map_cmd_handler = 0x40000924; -hci_le_rd_phy_cmd_handler = 0x40000928; -hci_le_rd_rem_feats_cmd_handler = 0x4000092c; -hci_le_rem_con_param_req_neg_reply_cmd_handler = 0x40000930; -hci_le_rem_con_param_req_reply_cmd_handler = 0x40000934; -hci_le_set_data_len_cmd_handler = 0x40000938; -hci_le_set_phy_cmd_handler = 0x4000093c; -hci_le_start_enc_cmd_handler = 0x40000940; -hci_rd_auth_payl_to_cmd_handler = 0x40000944; -hci_rd_rem_ver_info_cmd_handler = 0x40000948; -hci_rd_rssi_cmd_handler = 0x4000094c; -hci_rd_tx_pwr_lvl_cmd_handler = 0x40000950; -hci_vs_set_pref_slave_evt_dur_cmd_handler = 0x40000954; -hci_vs_set_pref_slave_latency_cmd_handler = 0x40000958; -hci_wr_auth_payl_to_cmd_handler = 0x4000095c; -ll_channel_map_ind_handler = 0x40000960; -ll_connection_param_req_handler = 0x40000964; -ll_connection_param_rsp_handler = 0x40000968; -ll_connection_update_ind_handler = 0x4000096c; -ll_enc_req_handler = 0x40000970; -ll_enc_rsp_handler = 0x40000974; -ll_feature_req_handler = 0x40000978; -ll_feature_rsp_handler = 0x4000097c; -ll_length_req_handler = 0x40000980; -ll_length_rsp_handler = 0x40000984; -ll_min_used_channels_ind_handler = 0x40000988; -ll_pause_enc_req_handler = 0x4000098c; -ll_pause_enc_rsp_handler = 0x40000990; -ll_phy_req_handler = 0x40000994; -ll_phy_rsp_handler = 0x40000998; -ll_phy_update_ind_handler = 0x4000099c; -ll_ping_req_handler = 0x400009a0; -ll_ping_rsp_handler = 0x400009a4; -ll_slave_feature_req_handler = 0x400009a8; -ll_start_enc_req_handler = 0x400009ac; -ll_start_enc_rsp_handler = 0x400009b0; -ll_terminate_ind_handler = 0x400009b4; -ll_version_ind_handler = 0x400009b8; -llc_auth_payl_nearly_to_handler = 0x400009bc; -llc_auth_payl_real_to_handler = 0x400009c0; -llc_encrypt_ind_handler = 0x400009c4; -llc_hci_command_handler_wrapper = 0x400009c8; -llc_ll_connection_param_req_pdu_send = 0x400009cc; -llc_ll_connection_param_rsp_pdu_send = 0x400009d0; -llc_ll_connection_update_ind_pdu_send = 0x400009d4; -llc_ll_enc_req_pdu_send = 0x400009d8; -llc_ll_enc_rsp_pdu_send = 0x400009dc; -llc_ll_feature_req_pdu_send = 0x400009e0; -llc_ll_feature_rsp_pdu_send = 0x400009e4; -llc_ll_length_req_pdu_send = 0x400009e8; -llc_ll_length_rsp_pdu_send = 0x400009ec; -llc_ll_pause_enc_req_pdu_send = 0x400009f0; -llc_ll_pause_enc_rsp_pdu_send = 0x400009f4; -llc_ll_phy_req_pdu_send = 0x400009f8; -llc_ll_phy_rsp_pdu_send = 0x400009fc; -llc_ll_ping_req_pdu_send = 0x40000a00; -llc_ll_ping_rsp_pdu_send = 0x40000a04; -llc_ll_start_enc_req_pdu_send = 0x40000a08; -llc_ll_start_enc_rsp_pdu_send = 0x40000a0c; -llc_ll_terminate_ind_pdu_send = 0x40000a10; -llc_ll_unknown_rsp_pdu_send = 0x40000a14; -llc_llcp_ch_map_update_ind_pdu_send = 0x40000a18; -llc_llcp_phy_upd_ind_pdu_send = 0x40000a1c; -llc_llcp_version_ind_pdu_send = 0x40000a20; -llc_op_ch_map_upd_ind_handler = 0x40000a24; -llc_op_con_upd_ind_handler = 0x40000a28; -llc_op_disconnect_ind_handler = 0x40000a2c; -llc_op_dl_upd_ind_handler = 0x40000a30; -llc_op_encrypt_ind_handler = 0x40000a34; -llc_op_feats_exch_ind_handler = 0x40000a38; -llc_op_le_ping_ind_handler = 0x40000a3c; -llc_op_phy_upd_ind_handler = 0x40000a40; -llc_op_ver_exch_ind_handler = 0x40000a44; -llc_stopped_ind_handler = 0x40000a48; -lld_acl_rx_ind_handler = 0x40000a4c; -lld_acl_tx_cfm_handler = 0x40000a50; -lld_adv_end_ind_handler = 0x40000a54; -lld_adv_rep_ind_handler = 0x40000a58; -lld_ch_map_upd_cfm_handler = 0x40000a5c; -lld_con_estab_ind_handler = 0x40000a60; -lld_con_evt_sd_evt_time_set = 0x40000a64; -lld_con_offset_upd_ind_handler = 0x40000a68; -lld_con_param_upd_cfm_handler = 0x40000a6c; -lld_disc_ind_handler = 0x40000a70; -lld_init_end_ind_handler = 0x40000a74; -lld_llcp_rx_ind_handler_wrapper = 0x40000a78; -lld_llcp_tx_cfm_handler = 0x40000a7c; -lld_per_adv_end_ind_handler = 0x40000a80; -lld_per_adv_rep_ind_handler = 0x40000a84; -lld_per_adv_rx_end_ind_handler = 0x40000a88; -lld_phy_coded_500k_get = 0x40000a8c; -lld_phy_upd_cfm_handler = 0x40000a90; -lld_scan_end_ind_handler = 0x40000a94; -lld_scan_req_ind_handler = 0x40000a98; -lld_sync_start_req_handler = 0x40000a9c; -lld_test_end_ind_handler = 0x40000aa0; -lld_update_rxbuf_handler = 0x40000aa4; -llm_ch_map_update_ind_handler = 0x40000aa8; -llm_hci_command_handler_wrapper = 0x40000aac; -llm_scan_period_to_handler = 0x40000ab0; -r_Add2SelfBigHex256 = 0x40000ab4; -r_AddBigHex256 = 0x40000ab8; -r_AddBigHexModP256 = 0x40000abc; -r_AddP256 = 0x40000ac0; -r_AddPdiv2_256 = 0x40000ac4; -r_GF_Jacobian_Point_Addition256 = 0x40000ac8; -r_GF_Jacobian_Point_Double256 = 0x40000acc; -r_GF_Point_Jacobian_To_Affine256 = 0x40000ad0; -r_MultiplyBigHexByUint32_256 = 0x40000ad4; -r_MultiplyBigHexModP256 = 0x40000ad8; -r_MultiplyByU16ModP256 = 0x40000adc; -r_SubtractBigHex256 = 0x40000ae0; -r_SubtractBigHexMod256 = 0x40000ae4; -r_SubtractBigHexUint32_256 = 0x40000ae8; -r_SubtractFromSelfBigHex256 = 0x40000aec; -r_SubtractFromSelfBigHexSign256 = 0x40000af0; -r_aes_alloc = 0x40000af4; -r_aes_ccm_continue = 0x40000af8; -r_aes_ccm_process_e = 0x40000afc; -r_aes_ccm_xor_128_lsb = 0x40000b00; -r_aes_ccm_xor_128_msb = 0x40000b04; -r_aes_cmac_continue = 0x40000b08; -r_aes_cmac_start = 0x40000b0c; -r_aes_k1_continue = 0x40000b10; -r_aes_k2_continue = 0x40000b14; -r_aes_k3_continue = 0x40000b18; -r_aes_k4_continue = 0x40000b1c; -r_aes_shift_left_128 = 0x40000b20; -r_aes_start = 0x40000b24; -r_aes_xor_128 = 0x40000b28; -r_assert_err = 0x40000b2c; -r_assert_param = 0x40000b30; -r_assert_warn = 0x40000b34; -r_bigHexInversion256 = 0x40000b38; -r_ble_sw_cca_check_isr = 0x40000b3c; -r_ble_util_buf_acl_tx_alloc = 0x40000b40; -r_ble_util_buf_acl_tx_elt_get = 0x40000b44; -r_ble_util_buf_acl_tx_free = 0x40000b48; -r_ble_util_buf_acl_tx_free_in_isr = 0x40000b4c; -r_ble_util_buf_adv_tx_alloc = 0x40000b50; -r_ble_util_buf_adv_tx_free = 0x40000b54; -r_ble_util_buf_adv_tx_free_in_isr = 0x40000b58; -r_ble_util_buf_env_deinit = 0x40000b5c; -r_ble_util_buf_env_init = 0x40000b60; -r_ble_util_buf_get_rx_buf_nb = 0x40000b64; -r_ble_util_buf_get_rx_buf_size = 0x40000b68; -r_ble_util_buf_llcp_tx_alloc = 0x40000b6c; -r_ble_util_buf_llcp_tx_free = 0x40000b70; -r_ble_util_buf_rx_alloc = 0x40000b74; -r_ble_util_buf_rx_alloc_in_isr = 0x40000b78; -r_ble_util_buf_rx_free = 0x40000b7c; -r_ble_util_buf_rx_free_in_isr = 0x40000b80; -r_ble_util_buf_set_rx_buf_nb = 0x40000b84; -r_ble_util_buf_set_rx_buf_size = 0x40000b88; -r_ble_util_data_rx_buf_reset = 0x40000b8c; -r_bt_bb_get_intr_mask = 0x40000b90; -r_bt_bb_intr_clear = 0x40000b94; -r_bt_bb_intr_mask_set = 0x40000b98; -r_bt_rf_coex_cfg_set = 0x40000ba0; -r_bt_rf_coex_conn_dynamic_pti_en_get = 0x40000ba4; -r_bt_rf_coex_ext_adv_dynamic_pti_en_get = 0x40000bac; -r_bt_rf_coex_ext_scan_dynamic_pti_en_get = 0x40000bb0; -r_bt_rf_coex_legacy_adv_dynamic_pti_en_get = 0x40000bb4; -r_bt_rf_coex_per_adv_dynamic_pti_en_get = 0x40000bb8; -r_bt_rf_coex_pti_table_get = 0x40000bbc; -r_bt_rf_coex_st_param_get = 0x40000bc0; -r_bt_rf_coex_st_param_set = 0x40000bc4; -r_bt_rf_coex_sync_scan_dynamic_pti_en_get = 0x40000bc8; -r_bt_rma_apply_rule_cs_fmt = 0x40000bcc; -r_bt_rma_apply_rule_cs_idx = 0x40000bd0; -r_bt_rma_configure = 0x40000bd4; -r_bt_rma_deregister_rule_cs_fmt = 0x40000bd8; -r_bt_rma_deregister_rule_cs_idx = 0x40000bdc; -r_bt_rma_get_ant_by_act = 0x40000be0; -r_bt_rma_init = 0x40000be4; -r_bt_rma_register_rule_cs_fmt = 0x40000be8; -r_bt_rma_register_rule_cs_idx = 0x40000bec; -r_bt_rtp_apply_rule_cs_fmt = 0x40000bf0; -r_bt_rtp_apply_rule_cs_idx = 0x40000bf4; -r_bt_rtp_deregister_rule_cs_fmt = 0x40000bf8; -r_bt_rtp_deregister_rule_cs_idx = 0x40000bfc; -r_bt_rtp_init = 0x40000c04; -r_bt_rtp_register_rule_cs_fmt = 0x40000c08; -r_bt_rtp_register_rule_cs_idx = 0x40000c0c; -r_btdm_isr = 0x40000c10; -r_cali_phase_match_p = 0x40000c20; -r_cmp_abs_time = 0x40000c24; -r_cmp_dest_id = 0x40000c28; -r_cmp_timer_id = 0x40000c2c; -r_co_bdaddr_compare = 0x40000c30; -r_co_ble_pkt_dur_in_us = 0x40000c34; -r_co_list_extract = 0x40000c38; -r_co_list_extract_after = 0x40000c3c; -r_co_list_extract_sublist = 0x40000c40; -r_co_list_find = 0x40000c44; -r_co_list_init = 0x40000c48; -r_co_list_insert_after = 0x40000c4c; -r_co_list_insert_before = 0x40000c50; -r_co_list_merge = 0x40000c54; -r_co_list_pool_init = 0x40000c58; -r_co_list_pop_front = 0x40000c5c; -r_co_list_push_back = 0x40000c60; -r_co_list_push_back_sublist = 0x40000c64; -r_co_list_push_front = 0x40000c68; -r_co_list_size = 0x40000c6c; -r_co_nb_good_le_channels = 0x40000c70; -r_co_util_pack = 0x40000c74; -r_co_util_read_array_size = 0x40000c78; -r_co_util_unpack = 0x40000c7c; -r_dbg_env_deinit = 0x40000c80; -r_dbg_env_init = 0x40000c84; -r_dbg_platform_reset_complete = 0x40000c88; -r_dl_upd_proc_start = 0x40000c8c; -r_dump_data = 0x40000c90; -r_ecc_abort_key256_generation = 0x40000c94; -r_ecc_gen_new_public_key = 0x40000c98; -r_ecc_gen_new_secret_key = 0x40000c9c; -r_ecc_generate_key256 = 0x40000ca0; -r_ecc_get_debug_Keys = 0x40000ca4; -r_ecc_init = 0x40000ca8; -r_ecc_is_valid_point = 0x40000cac; -r_ecc_multiplication_event_handler = 0x40000cb0; -r_ecc_point_multiplication_win_256 = 0x40000cb4; -r_emi_alloc_em_mapping_by_offset = 0x40000cb8; -r_emi_base_reg_lut_show = 0x40000cbc; -r_emi_em_base_reg_show = 0x40000cc0; -r_emi_free_em_mapping_by_offset = 0x40000cc4; -r_emi_get_em_mapping_idx_by_offset = 0x40000cc8; -r_emi_get_mem_addr_by_offset = 0x40000ccc; -r_emi_overwrite_em_mapping_by_offset = 0x40000cd0; -r_esp_vendor_hci_command_handler = 0x40000cd4; -r_get_stack_usage = 0x40000cd8; -r_h4tl_acl_hdr_rx_evt_handler = 0x40000cdc; -r_h4tl_cmd_hdr_rx_evt_handler = 0x40000ce0; -r_h4tl_cmd_pld_rx_evt_handler = 0x40000ce4; -r_h4tl_eif_io_event_post = 0x40000ce8; -r_h4tl_eif_register = 0x40000cec; -r_h4tl_init = 0x40000cf0; -r_h4tl_out_of_sync = 0x40000cf4; -r_h4tl_out_of_sync_check = 0x40000cf8; -r_h4tl_read_hdr = 0x40000cfc; -r_h4tl_read_next_out_of_sync = 0x40000d00; -r_h4tl_read_payl = 0x40000d04; -r_h4tl_read_start = 0x40000d08; -r_h4tl_rx_acl_hdr_extract = 0x40000d0c; -r_h4tl_rx_cmd_hdr_extract = 0x40000d10; -r_h4tl_rx_done = 0x40000d14; -r_h4tl_start = 0x40000d18; -r_h4tl_stop = 0x40000d1c; -r_h4tl_tx_done = 0x40000d20; -r_h4tl_tx_evt_handler = 0x40000d24; -r_h4tl_write = 0x40000d28; -r_hci_acl_tx_data_alloc = 0x40000d2c; -r_hci_acl_tx_data_received = 0x40000d30; -r_hci_basic_cmd_send_2_controller = 0x40000d34; -r_hci_ble_adv_report_filter_check = 0x40000d38; -r_hci_ble_adv_report_tx_check = 0x40000d3c; -r_hci_ble_conhdl_register = 0x40000d40; -r_hci_ble_conhdl_unregister = 0x40000d44; -r_hci_build_acl_data = 0x40000d48; -r_hci_build_cc_evt = 0x40000d4c; -r_hci_build_cs_evt = 0x40000d50; -r_hci_build_evt = 0x40000d54; -r_hci_build_le_evt = 0x40000d58; -r_hci_cmd_get_max_param_size = 0x40000d5c; -r_hci_cmd_received = 0x40000d60; -r_hci_cmd_reject = 0x40000d64; -r_hci_evt_mask_check = 0x40000d68; -r_hci_evt_mask_set = 0x40000d6c; -r_hci_fc_acl_buf_size_set = 0x40000d70; -r_hci_fc_acl_en = 0x40000d74; -r_hci_fc_acl_packet_sent = 0x40000d78; -r_hci_fc_check_host_available_nb_acl_packets = 0x40000d7c; -r_hci_fc_host_nb_acl_pkts_complete = 0x40000d80; -r_hci_fc_init = 0x40000d84; -r_hci_look_for_cmd_desc = 0x40000d88; -r_hci_look_for_evt_desc = 0x40000d8c; -r_hci_look_for_le_evt_desc = 0x40000d90; -r_hci_look_for_le_evt_desc_esp = 0x40000d94; -r_hci_pack_bytes = 0x40000d98; -r_hci_send_2_controller = 0x40000da0; -r_hci_send_2_host = 0x40000da4; -r_hci_tl_c2h_data_flow_on = 0x40000da8; -r_hci_tl_cmd_hdr_rx_evt_handler = 0x40000dac; -r_hci_tl_cmd_pld_rx_evt_handler = 0x40000db0; -r_hci_tl_get_pkt = 0x40000db4; -r_hci_tl_hci_pkt_handler = 0x40000db8; -r_hci_tl_hci_tx_done_evt_handler = 0x40000dbc; -r_hci_tl_inc_nb_h2c_cmd_pkts = 0x40000dc0; -r_hci_tl_save_pkt = 0x40000dc4; -r_hci_tl_send = 0x40000dc8; -r_hci_tx_done = 0x40000dcc; -r_hci_tx_start = 0x40000dd0; -r_hci_tx_trigger = 0x40000dd4; -r_isValidSecretKey_256 = 0x40000dd8; -r_ke_check_malloc = 0x40000ddc; -r_ke_event_callback_set = 0x40000de0; -r_ke_event_clear = 0x40000de4; -r_ke_event_flush = 0x40000de8; -r_ke_event_get = 0x40000dec; -r_ke_event_get_all = 0x40000df0; -r_ke_event_init = 0x40000df4; -r_ke_event_schedule = 0x40000df8; -r_ke_event_set = 0x40000dfc; -r_ke_flush = 0x40000e00; -r_ke_free = 0x40000e04; -r_ke_handler_search = 0x40000e08; -r_ke_init = 0x40000e0c; -r_ke_is_free = 0x40000e10; -r_ke_malloc = 0x40000e14; -r_ke_mem_init = 0x40000e18; -r_ke_mem_is_empty = 0x40000e1c; -r_ke_mem_is_in_heap = 0x40000e20; -r_ke_msg_alloc = 0x40000e24; -r_ke_msg_dest_id_get = 0x40000e28; -r_ke_msg_discard = 0x40000e2c; -r_ke_msg_forward = 0x40000e30; -r_ke_msg_forward_new_id = 0x40000e34; -r_ke_msg_free = 0x40000e38; -r_ke_msg_in_queue = 0x40000e3c; -r_ke_msg_save = 0x40000e40; -r_ke_msg_send = 0x40000e44; -r_ke_msg_send_basic = 0x40000e48; -r_ke_msg_src_id_get = 0x40000e4c; -r_ke_queue_extract = 0x40000e50; -r_ke_queue_insert = 0x40000e54; -r_ke_sleep_check = 0x40000e58; -r_ke_state_get = 0x40000e5c; -r_ke_state_set = 0x40000e60; -r_ke_task_check = 0x40000e64; -r_ke_task_create = 0x40000e68; -r_ke_task_delete = 0x40000e6c; -r_ke_task_handler_get = 0x40000e70; -r_ke_task_init = 0x40000e74; -r_ke_task_msg_flush = 0x40000e78; -r_ke_task_saved_update = 0x40000e7c; -r_ke_time = 0x40000e84; -r_ke_time_cmp = 0x40000e88; -r_ke_time_past = 0x40000e8c; -r_ke_timer_active = 0x40000e90; -r_ke_timer_adjust_all = 0x40000e94; -r_ke_timer_clear = 0x40000e98; -r_ke_timer_init = 0x40000e9c; -r_ke_timer_schedule = 0x40000ea0; -r_ke_timer_set = 0x40000ea4; -r_led_init = 0x40000ea8; -r_led_set_all = 0x40000eac; -r_llc_aes_res_cb = 0x40000eb0; -r_llc_ch_map_up_proc_err_cb = 0x40000eb4; -r_llc_cleanup = 0x40000eb8; -r_llc_cmd_cmp_send = 0x40000ebc; -r_llc_cmd_stat_send = 0x40000ec0; -r_llc_con_move_cbk = 0x40000ec4; -r_llc_con_plan_set_update = 0x40000ec8; -r_llc_con_upd_param_in_range = 0x40000ecc; -r_llc_disconnect = 0x40000ed0; -r_llc_disconnect_end = 0x40000ed4; -r_llc_disconnect_proc_continue = 0x40000ed8; -r_llc_disconnect_proc_err_cb = 0x40000edc; -r_llc_dl_chg_check = 0x40000ee0; -r_llc_dle_proc_err_cb = 0x40000ee4; -r_llc_feats_exch_proc_err_cb = 0x40000ee8; -r_llc_hci_cmd_handler_tab_p_get = 0x40000eec; -r_llc_hci_con_param_req_evt_send = 0x40000ef4; -r_llc_hci_con_upd_info_send = 0x40000ef8; -r_llc_hci_disconnected_dis = 0x40000efc; -r_llc_hci_dl_upd_info_send = 0x40000f00; -r_llc_hci_enc_evt_send = 0x40000f04; -r_llc_hci_feats_info_send = 0x40000f08; -r_llc_hci_le_phy_upd_cmp_evt_send = 0x40000f0c; -r_llc_hci_ltk_request_evt_send = 0x40000f10; -r_llc_hci_nb_cmp_pkts_evt_send = 0x40000f14; -r_llc_hci_version_info_send = 0x40000f18; -r_llc_init_term_proc = 0x40000f1c; -r_llc_iv_skd_rand_gen = 0x40000f20; -r_llc_le_ping_proc_continue = 0x40000f24; -r_llc_le_ping_proc_err_cb = 0x40000f28; -/* r_llc_le_ping_restart = 0x40000f2c; */ -r_llc_le_ping_set = 0x40000f30; -r_llc_ll_pause_enc_rsp_ack_handler = 0x40000f34; -r_llc_ll_reject_ind_ack_handler = 0x40000f38; -r_llc_ll_reject_ind_pdu_send = 0x40000f3c; -r_llc_ll_start_enc_rsp_ack_handler = 0x40000f40; -r_llc_ll_terminate_ind_ack = 0x40000f44; -r_llc_ll_unknown_ind_handler = 0x40000f48; -r_llc_llcp_send = 0x40000f4c; -r_llc_llcp_state_set = 0x40000f50; -r_llc_llcp_trans_timer_set = 0x40000f54; -r_llc_llcp_tx_check = 0x40000f58; -r_llc_loc_ch_map_proc_continue = 0x40000f5c; -r_llc_loc_con_upd_proc_err_cb = 0x40000f64; -r_llc_loc_dl_upd_proc_continue = 0x40000f68; -r_llc_loc_encrypt_proc_continue = 0x40000f6c; -r_llc_loc_encrypt_proc_err_cb = 0x40000f70; -r_llc_loc_feats_exch_proc_continue = 0x40000f74; -r_llc_loc_phy_upd_proc_err_cb = 0x40000f7c; -r_llc_msg_handler_tab_p_get = 0x40000f80; -r_llc_pref_param_compute = 0x40000f84; -r_llc_proc_collision_check = 0x40000f88; -r_llc_proc_err_ind = 0x40000f8c; -r_llc_proc_get = 0x40000f90; -r_llc_proc_id_get = 0x40000f94; -r_llc_proc_reg = 0x40000f98; -r_llc_proc_state_get = 0x40000f9c; -r_llc_proc_state_set = 0x40000fa0; -r_llc_proc_timer_pause_set = 0x40000fa4; -r_llc_proc_timer_set = 0x40000fa8; -r_llc_proc_unreg = 0x40000fac; -r_llc_rem_ch_map_proc_continue = 0x40000fb0; -r_llc_rem_con_upd_proc_err_cb = 0x40000fb8; -r_llc_rem_dl_upd_proc = 0x40000fbc; -r_llc_rem_encrypt_proc_continue = 0x40000fc0; -r_llc_rem_encrypt_proc_err_cb = 0x40000fc4; -r_llc_rem_phy_upd_proc_continue = 0x40000fc8; -r_llc_rem_phy_upd_proc_err_cb = 0x40000fcc; -r_llc_role_get = 0x40000fd0; -r_llc_sk_gen = 0x40000fd4; -r_llc_start = 0x40000fd8; -r_llc_stop = 0x40000fdc; -r_llc_ver_exch_loc_proc_continue = 0x40000fe0; -r_llc_ver_proc_err_cb = 0x40000fe4; -r_llcp_pdu_handler_tab_p_get = 0x40000fe8; -r_lld_aa_gen = 0x40000fec; -r_lld_adv_adv_data_set = 0x40000ff0; -r_lld_adv_adv_data_update = 0x40000ff4; -r_lld_adv_aux_ch_idx_set = 0x40000ff8; -r_lld_adv_aux_evt_canceled_cbk = 0x40000ffc; -r_lld_adv_aux_evt_start_cbk = 0x40001000; -r_lld_adv_coex_check_ext_adv_synced = 0x40001004; -r_lld_adv_coex_env_reset = 0x40001008; -r_lld_adv_duration_update = 0x4000100c; -r_lld_adv_dynamic_pti_process = 0x40001010; -r_lld_adv_end = 0x40001014; -r_lld_adv_evt_canceled_cbk = 0x40001018; -r_lld_adv_evt_start_cbk = 0x4000101c; -r_lld_adv_ext_chain_construct = 0x40001020; -r_lld_adv_ext_pkt_prepare = 0x40001024; -r_lld_adv_frm_cbk = 0x40001028; -r_lld_adv_frm_isr = 0x4000102c; -r_lld_adv_frm_skip_isr = 0x40001030; -r_lld_adv_init = 0x40001034; -r_lld_adv_pkt_rx = 0x40001038; -r_lld_adv_pkt_rx_connect_ind = 0x4000103c; -r_lld_adv_pkt_rx_send_scan_req_evt = 0x40001040; -r_lld_adv_rand_addr_update = 0x40001044; -r_lld_adv_restart = 0x40001048; -r_lld_adv_scan_rsp_data_set = 0x4000104c; -r_lld_adv_scan_rsp_data_update = 0x40001050; -r_lld_adv_set_tx_power = 0x40001054; -r_lld_adv_start = 0x40001058; -r_lld_adv_stop = 0x4000105c; -r_lld_adv_sync_info_set = 0x40001060; -r_lld_adv_sync_info_update = 0x40001064; -r_lld_calc_aux_rx = 0x40001068; -r_lld_cca_alloc = 0x4000106c; -r_lld_cca_data_reset = 0x40001070; -r_lld_cca_free = 0x40001074; -r_lld_ch_assess_data_get = 0x40001078; -r_lld_ch_idx_get = 0x4000107c; -r_lld_ch_map_set = 0x40001080; -r_lld_channel_assess = 0x40001084; -r_lld_con_activity_act_offset_compute = 0x40001088; -r_lld_con_activity_offset_compute = 0x4000108c; -r_lld_con_ch_map_update = 0x40001090; -r_lld_con_cleanup = 0x40001094; -r_lld_con_current_tx_power_get = 0x40001098; -r_lld_con_data_flow_set = 0x4000109c; -r_lld_con_data_len_update = 0x400010a0; -r_lld_con_data_tx = 0x400010a4; -r_lld_con_enc_key_load = 0x400010a8; -r_lld_con_event_counter_get = 0x400010ac; -r_lld_con_evt_canceled_cbk = 0x400010b0; -r_lld_con_evt_duration_min_get = 0x400010b4; -r_lld_con_evt_max_eff_time_cal = 0x400010b8; -r_lld_con_evt_sd_evt_time_get = 0x400010bc; -r_lld_con_evt_start_cbk = 0x400010c0; -r_lld_con_evt_time_update = 0x400010c4; -r_lld_con_free_all_tx_buf = 0x400010c8; -r_lld_con_frm_cbk = 0x400010cc; -r_lld_con_frm_isr = 0x400010d0; -r_lld_con_frm_skip_isr = 0x400010d4; -r_lld_con_init = 0x400010d8; -r_lld_con_llcp_tx = 0x400010dc; -r_lld_con_max_lat_calc = 0x400010e0; -r_lld_con_offset_get = 0x400010e4; -r_lld_con_param_update = 0x400010e8; -r_lld_con_phys_update = 0x400010ec; -r_lld_con_pref_slave_evt_dur_set = 0x400010f0; -r_lld_con_pref_slave_latency_set = 0x400010f4; -r_lld_con_rssi_get = 0x400010f8; -r_lld_con_rx = 0x400010fc; -/* r_lld_con_rx_channel_assess = 0x40001100; */ -r_lld_con_rx_enc = 0x40001104; -r_lld_con_rx_isr = 0x40001108; -r_lld_con_rx_link_info_check = 0x4000110c; -r_lld_con_rx_llcp_check = 0x40001110; -r_lld_con_rx_sync_time_update = 0x40001114; -r_lld_con_set_tx_power = 0x4000111c; -r_lld_con_start = 0x40001120; -r_lld_con_tx = 0x40001128; -r_lld_con_tx_enc = 0x4000112c; -r_lld_con_tx_isr = 0x40001130; -r_lld_con_tx_len_update = 0x40001134; -r_lld_con_tx_len_update_for_intv = 0x40001138; -r_lld_con_tx_len_update_for_rate = 0x4000113c; -r_lld_con_tx_prog = 0x40001140; -r_lld_conn_dynamic_pti_process = 0x40001144; -r_lld_continue_scan_rx_isr_end_process = 0x40001148; -r_lld_ext_scan_dynamic_pti_process = 0x4000114c; -r_lld_hw_cca_end_isr = 0x40001150; -r_lld_hw_cca_evt_handler = 0x40001154; -r_lld_hw_cca_isr = 0x40001158; -r_lld_init_cal_anchor_point = 0x4000115c; -r_lld_init_compute_winoffset = 0x40001160; -r_lld_init_connect_req_pack = 0x40001164; -r_lld_init_end = 0x40001168; -r_lld_init_evt_canceled_cbk = 0x4000116c; -r_lld_init_evt_start_cbk = 0x40001170; -r_lld_init_frm_cbk = 0x40001174; -r_lld_init_frm_eof_isr = 0x40001178; -r_lld_init_frm_skip_isr = 0x4000117c; -r_lld_init_init = 0x40001180; -r_lld_init_process_pkt_rx = 0x40001184; -r_lld_init_process_pkt_rx_adv_ext_ind = 0x40001188; -r_lld_init_process_pkt_rx_adv_ind_or_direct_ind = 0x4000118c; -r_lld_init_process_pkt_rx_aux_connect_rsp = 0x40001190; -r_lld_init_process_pkt_tx = 0x40001194; -r_lld_init_process_pkt_tx_cal_con_timestamp = 0x40001198; -r_lld_init_sched = 0x4000119c; -r_lld_init_set_tx_power = 0x400011a0; -r_lld_init_start = 0x400011a4; -r_lld_init_stop = 0x400011a8; -r_lld_instant_proc_end = 0x400011ac; -r_lld_per_adv_ch_map_update = 0x400011b4; -r_lld_per_adv_chain_construct = 0x400011b8; -r_lld_per_adv_cleanup = 0x400011bc; -r_lld_per_adv_coex_env_reset = 0x400011c0; -r_lld_per_adv_data_set = 0x400011c4; -r_lld_per_adv_data_update = 0x400011c8; -r_lld_per_adv_dynamic_pti_process = 0x400011cc; -r_lld_per_adv_evt_canceled_cbk = 0x400011d0; -r_lld_per_adv_evt_start_cbk = 0x400011d4; -r_lld_per_adv_ext_pkt_prepare = 0x400011d8; -r_lld_per_adv_frm_cbk = 0x400011dc; -r_lld_per_adv_frm_isr = 0x400011e0; -r_lld_per_adv_frm_skip_isr = 0x400011e4; -r_lld_per_adv_init = 0x400011e8; -r_lld_per_adv_init_info_get = 0x400011ec; -r_lld_per_adv_list_add = 0x400011f0; -r_lld_per_adv_list_rem = 0x400011f4; -r_lld_per_adv_set_tx_power = 0x400011fc; -r_lld_per_adv_start = 0x40001200; -r_lld_per_adv_stop = 0x40001204; -r_lld_per_adv_sync_info_get = 0x40001208; -r_lld_process_cca_data = 0x4000120c; -r_lld_ral_search = 0x40001210; -r_lld_read_clock = 0x40001214; -r_lld_res_list_add = 0x40001218; -r_lld_res_list_is_empty = 0x40001220; -r_lld_res_list_local_rpa_get = 0x40001224; -r_lld_res_list_peer_rpa_get = 0x40001228; -r_lld_res_list_peer_update = 0x4000122c; -/* r_lld_res_list_priv_mode_update = 0x40001230; */ -r_lld_reset_reg = 0x40001238; -r_lld_rpa_renew = 0x4000123c; -r_lld_rpa_renew_evt_canceled_cbk = 0x40001240; -r_lld_rpa_renew_evt_start_cbk = 0x40001244; -r_lld_rpa_renew_instant_cbk = 0x40001248; -r_lld_rxdesc_check = 0x4000124c; -r_lld_rxdesc_free = 0x40001250; -r_lld_scan_create_sync = 0x40001254; -r_lld_scan_create_sync_cancel = 0x40001258; -r_lld_scan_end = 0x4000125c; -r_lld_scan_evt_canceled_cbk = 0x40001260; -r_lld_scan_evt_start_cbk = 0x40001264; -r_lld_scan_frm_cbk = 0x40001268; -r_lld_scan_frm_eof_isr = 0x4000126c; -r_lld_scan_frm_rx_isr = 0x40001270; -r_lld_scan_frm_skip_isr = 0x40001274; -r_lld_scan_init = 0x40001278; -r_lld_scan_params_update = 0x4000127c; -r_lld_scan_process_pkt_rx_aux_adv_ind = 0x40001288; -r_lld_scan_process_pkt_rx_aux_chain_ind = 0x4000128c; -r_lld_scan_process_pkt_rx_aux_scan_rsp = 0x40001290; -r_lld_scan_process_pkt_rx_ext_adv = 0x40001294; -r_lld_scan_process_pkt_rx_ext_adv_ind = 0x40001298; -r_lld_scan_process_pkt_rx_legacy_adv = 0x4000129c; -r_lld_scan_restart = 0x400012a0; -r_lld_scan_sched = 0x400012a4; -r_lld_scan_set_tx_power = 0x400012a8; -r_lld_scan_start = 0x400012ac; -r_lld_scan_stop = 0x400012b0; -r_lld_scan_sync_accept = 0x400012b4; -r_lld_scan_sync_info_unpack = 0x400012b8; -r_lld_scan_trunc_ind = 0x400012bc; -r_lld_sw_cca_evt_handler = 0x400012c0; -r_lld_sw_cca_isr = 0x400012c4; -r_lld_sync_ch_map_update = 0x400012c8; -r_lld_sync_cleanup = 0x400012cc; -r_lld_sync_evt_canceled_cbk = 0x400012d0; -r_lld_sync_evt_start_cbk = 0x400012d4; -r_lld_sync_frm_cbk = 0x400012d8; -r_lld_sync_frm_eof_isr = 0x400012dc; -r_lld_sync_frm_rx_isr = 0x400012e0; -r_lld_sync_frm_skip_isr = 0x400012e4; -r_lld_sync_init = 0x400012e8; -r_lld_sync_process_pkt_rx = 0x400012ec; -r_lld_sync_process_pkt_rx_aux_sync_ind = 0x400012f0; -r_lld_sync_process_pkt_rx_pkt_check = 0x400012f4; -r_lld_sync_scan_dynamic_pti_process = 0x400012f8; -r_lld_sync_sched = 0x400012fc; -r_lld_sync_start = 0x40001300; -r_lld_sync_stop = 0x40001304; -r_lld_sync_trunc_ind = 0x40001308; -r_lld_test_cleanup = 0x4000130c; -r_lld_test_evt_canceled_cbk = 0x40001310; -r_lld_test_evt_start_cbk = 0x40001314; -r_lld_test_freq2chnl = 0x40001318; -r_lld_test_frm_cbk = 0x4000131c; -r_lld_test_frm_isr = 0x40001320; -r_lld_test_init = 0x40001324; -r_lld_test_rx_isr = 0x40001328; -r_lld_test_set_tx_power = 0x4000132c; -r_lld_test_start = 0x40001330; -/* r_lld_test_stop = 0x40001334; */ -r_lld_update_rxbuf = 0x40001338; -r_lld_update_rxbuf_isr = 0x4000133c; -r_lld_white_list_add = 0x40001340; -r_lld_white_list_rem = 0x40001344; -r_llm_activity_free_get = 0x40001348; -r_llm_activity_free_set = 0x4000134c; -r_llm_activity_syncing_get = 0x40001350; -r_llm_adv_con_len_check = 0x40001354; -r_llm_adv_hdl_to_id = 0x40001358; -r_llm_adv_rep_flow_control_check = 0x4000135c; -r_llm_adv_rep_flow_control_update = 0x40001360; -r_llm_adv_reports_list_check = 0x40001364; -r_llm_adv_set_all_release = 0x40001368; -r_llm_adv_set_dft_params = 0x4000136c; -r_llm_adv_set_release = 0x40001370; -r_llm_aes_res_cb = 0x40001374; -r_llm_ble_update_adv_flow_control = 0x40001378; -r_llm_ch_map_update = 0x4000137c; -r_llm_cmd_cmp_send = 0x40001380; -r_llm_cmd_stat_send = 0x40001384; -r_llm_dev_list_empty_entry = 0x40001388; -r_llm_dev_list_search = 0x4000138c; -r_llm_env_adv_dup_filt_deinit = 0x40001390; -r_llm_env_adv_dup_filt_init = 0x40001394; -r_llm_init_ble_adv_report_flow_contol = 0x40001398; -r_llm_is_dev_connected = 0x4000139c; -r_llm_is_dev_synced = 0x400013a0; -r_llm_is_non_con_act_ongoing_check = 0x400013a4; -r_llm_is_wl_accessible = 0x400013a8; -r_llm_le_evt_mask_check = 0x400013ac; -r_llm_link_disc = 0x400013b4; -r_llm_master_ch_map_get = 0x400013b8; -r_llm_msg_handler_tab_p_get = 0x400013bc; -r_llm_no_activity = 0x400013c0; -r_llm_per_adv_slot_dur = 0x400013c4; -r_llm_plan_elt_get = 0x400013c8; -r_llm_rx_path_comp_get = 0x400013cc; -r_llm_scan_start = 0x400013d0; -r_llm_scan_sync_acad_attach = 0x400013d4; -r_llm_scan_sync_acad_detach = 0x400013d8; -r_llm_send_adv_lost_event_to_host = 0x400013dc; -r_llm_tx_path_comp_get = 0x400013e0; -r_misc_deinit = 0x400013e4; -r_misc_free_em_buf_in_isr = 0x400013e8; -r_misc_init = 0x400013ec; -r_misc_msg_handler_tab_p_get = 0x400013f0; -r_notEqual256 = 0x400013f4; -r_phy_upd_proc_start = 0x400013f8; -r_platform_reset = 0x400013fc; -r_rf_em_init = 0x40001404; -r_rf_force_agc_enable = 0x40001408; -r_rf_reg_rd = 0x4000140c; -r_rf_reg_wr = 0x40001410; -r_rf_reset = 0x40001414; -r_rf_rssi_convert = 0x40001418; -r_rf_rw_v9_le_disable = 0x4000141c; -r_rf_rw_v9_le_enable = 0x40001420; -r_rf_sleep = 0x40001424; -r_rf_util_cs_fmt_convert = 0x40001430; -r_rw_crypto_aes_ccm = 0x40001434; -r_rw_crypto_aes_encrypt = 0x40001438; -r_rw_crypto_aes_init = 0x4000143c; -r_rw_crypto_aes_k1 = 0x40001440; -r_rw_crypto_aes_k2 = 0x40001444; -r_rw_crypto_aes_k3 = 0x40001448; -r_rw_crypto_aes_k4 = 0x4000144c; -r_rw_crypto_aes_rand = 0x40001450; -r_rw_crypto_aes_result_handler = 0x40001454; -r_rw_crypto_aes_s1 = 0x40001458; -r_rw_cryto_aes_cmac = 0x4000145c; -r_rw_v9_init_em_radio_table = 0x40001460; -r_rwble_sleep_enter = 0x40001468; -r_rwble_sleep_wakeup_end = 0x4000146c; -/* r_rwbtdm_isr_wrapper = 0x40001470; */ -r_rwip_active_check = 0x40001474; -r_rwip_aes_encrypt = 0x40001478; -/* r_rwip_assert = 0x4000147c; */ -r_rwip_crypt_evt_handler = 0x40001480; -r_rwip_crypt_isr_handler = 0x40001484; -r_rwip_eif_get = 0x40001488; -r_rwip_half_slot_2_lpcycles = 0x4000148c; -r_rwip_hus_2_lpcycles = 0x40001490; -r_rwip_isr = 0x40001494; -r_rwip_lpcycles_2_hus = 0x40001498; -r_rwip_prevent_sleep_clear = 0x4000149c; -r_rwip_prevent_sleep_set = 0x400014a0; -r_rwip_schedule = 0x400014a4; -r_rwip_sleep = 0x400014a8; -r_rwip_sw_int_handler = 0x400014ac; -r_rwip_sw_int_req = 0x400014b0; -r_rwip_time_get = 0x400014b4; -r_rwip_timer_10ms_handler = 0x400014b8; -r_rwip_timer_10ms_set = 0x400014bc; -r_rwip_timer_hs_handler = 0x400014c0; -r_rwip_timer_hs_set = 0x400014c4; -r_rwip_timer_hus_handler = 0x400014c8; -r_rwip_timer_hus_set = 0x400014cc; -r_rwip_wakeup = 0x400014d0; -/* r_rwip_wakeup_end = 0x400014d4; */ -r_rwip_wlcoex_set = 0x400014d8; -r_sch_alarm_clear = 0x400014dc; -r_sch_alarm_init = 0x400014e0; -r_sch_alarm_prog = 0x400014e4; -r_sch_alarm_set = 0x400014e8; -r_sch_alarm_timer_isr = 0x400014ec; -r_sch_arb_conflict_check = 0x400014f0; -r_sch_arb_elt_cancel = 0x400014f4; -r_sch_arb_init = 0x400014fc; -r_sch_arb_insert = 0x40001500; -r_sch_arb_prog_timer = 0x40001504; -r_sch_arb_remove = 0x40001508; -r_sch_arb_sw_isr = 0x4000150c; -r_sch_plan_chk = 0x40001510; -r_sch_plan_clock_wrap_offset_update = 0x40001514; -r_sch_plan_init = 0x40001518; -r_sch_plan_interval_req = 0x4000151c; -r_sch_plan_offset_max_calc = 0x40001520; -r_sch_plan_offset_req = 0x40001524; -r_sch_plan_position_range_compute = 0x40001528; -r_sch_plan_rem = 0x4000152c; -r_sch_plan_req = 0x40001530; -r_sch_prog_init = 0x4000153c; -r_sch_prog_push = 0x40001540; -r_sch_prog_rx_isr = 0x40001544; -r_sch_prog_skip_isr = 0x40001548; -r_sch_prog_tx_isr = 0x4000154c; -r_sch_slice_bg_add = 0x40001550; -r_sch_slice_bg_remove = 0x40001554; -r_sch_slice_compute = 0x40001558; -r_sch_slice_fg_add = 0x4000155c; -r_sch_slice_fg_remove = 0x40001560; -r_sch_slice_init = 0x40001564; -r_sch_slice_per_add = 0x40001568; -r_sch_slice_per_remove = 0x4000156c; -r_sdk_config_get_bt_sleep_enable = 0x40001570; -r_sdk_config_get_hl_derived_opts = 0x40001574; -r_sdk_config_get_opts = 0x40001578; -r_sdk_config_get_priv_opts = 0x4000157c; -r_sdk_config_set_bt_sleep_enable = 0x40001580; -r_sdk_config_set_hl_derived_opts = 0x40001584; -r_sdk_config_set_opts = 0x40001588; -r_specialModP256 = 0x4000158c; -r_unloaded_area_init = 0x40001590; -r_vhci_flow_off = 0x40001594; -r_vhci_flow_on = 0x40001598; -r_vhci_notify_host_send_available = 0x4000159c; -r_vhci_send_to_host = 0x400015a0; -r_vnd_hci_command_handler = 0x400015a4; -r_vshci_init = 0x400015a8; -vnd_hci_command_handler_wrapper = 0x400015ac; -/* Data (.data, .bss, .rodata) */ -bt_rf_coex_cfg_p = 0x3fcdffcc; -bt_rf_coex_hooks_p = 0x3fcdffc8; -btdm_env_p = 0x3fcdffc4; -g_rw_controller_task_handle = 0x3fcdffc0; -g_rw_init_sem = 0x3fcdffbc; -g_rw_schd_queue = 0x3fcdffb8; -lld_init_env = 0x3fcdffb4; -lld_rpa_renew_env = 0x3fcdffb0; -lld_scan_env = 0x3fcdffac; -lld_scan_sync_env = 0x3fcdffa8; -lld_test_env = 0x3fcdffa4; -p_ble_util_buf_env = 0x3fcdffa0; -p_lld_env = 0x3fcdff9c; -p_llm_env = 0x3fcdff98; -r_h4tl_eif_p = 0x3fcdff94; -r_hli_funcs_p = 0x3fcdff90; -r_ip_funcs_p = 0x3fcdff8c; -r_modules_funcs_p = 0x3fcdff88; -r_osi_funcs_p = 0x3fcdff84; -r_plf_funcs_p = 0x3fcdff80; -vhci_env_p = 0x3fcdff7c; -aa_gen = 0x3fcdff78; -aes_env = 0x3fcdff6c; -bt_rf_coex_cfg_cb = 0x3fcdff1c; -btdm_pwr_state = 0x3fcdff18; -btdm_slp_err = 0x3fcdff14; -ecc_env = 0x3fcdff0c; -esp_handler = 0x3fcdff04; -esp_vendor_cmd = 0x3fcdfefc; -g_adv_delay_dis = 0x3fcdfef8; -g_conflict_elt = 0x3fcdfef4; -g_eif_api = 0x3fcdfee4; -g_event_empty = 0x3fcdfed8; -g_llc_state = 0x3fcdfecc; -g_llm_state = 0x3fcdfec8; -g_max_evt_env = 0x3fcdfec4; -g_misc_state = 0x3fcdfec0; -g_rma_rule_db = 0x3fcdfea4; -g_rtp_rule_db = 0x3fcdfe88; -g_scan_forever = 0x3fcdfe85; -g_time_msb = 0x3fcdfe84; -h4tl_env = 0x3fcdfe5c; -hci_env = 0x3fcdfe38; -hci_ext_host = 0x3fcdfe34; -hci_fc_env = 0x3fcdfe2c; -hci_tl_env = 0x3fcdfe00; -ke_env = 0x3fcdfdd0; -ke_event_env = 0x3fcdfd90; -ke_task_env = 0x3fcdfd14; -llc_env = 0x3fcdfcec; -lld_adv_env = 0x3fcdfcc4; -lld_con_env = 0x3fcdfc9c; -lld_exp_sync_pos_tab = 0x3fcdfc94; -lld_per_adv_env = 0x3fcdfc6c; -lld_sync_env = 0x3fcdfc44; -llm_le_adv_flow_env = 0x3fcdfc38; -rw_sleep_enable = 0x3fcdfc34; -rwble_env = 0x3fcdfc2c; -rwip_env = 0x3fcdfc10; -rwip_param = 0x3fcdfc04; -rwip_prog_delay = 0x3fcdfc00; -rwip_rf = 0x3fcdfbc8; -sch_alarm_env = 0x3fcdfbc0; -sch_arb_env = 0x3fcdfbac; -sch_plan_env = 0x3fcdfba4; -sch_prog_env = 0x3fcdfaa0; -sch_slice_env = 0x3fcdfa40; -sch_slice_params = 0x3fcdfa38; -timer_env = 0x3fcdfa30; -unloaded_area = 0x3fcdfa2c; -vshci_state = 0x3fcdfa28; -TASK_DESC_LLC = 0x3fcdfa1c; -TASK_DESC_LLM = 0x3fcdfa10; -TASK_DESC_VSHCI = 0x3fcdfa04; -co_default_bdaddr = 0x3fcdf9fc; -dbg_assert_block = 0x3fcdf9f8; -g_bt_plf_log_level = 0x3fcdf9f4; -hci_cmd_desc_tab_vs_esp = 0x3fcdf9d0; -hci_command_handler_tab_esp = 0x3fcdf9b8; -privacy_en = 0x3fcdf9b4; -sdk_cfg_priv_opts = 0x3fcdf96c; -BasePoint_x_256 = 0x3ff1ffdc; -BasePoint_y_256 = 0x3ff1ffbc; -DebugE256PublicKey_x = 0x3ff1ff9c; -DebugE256PublicKey_y = 0x3ff1ff7c; -DebugE256SecretKey = 0x3ff1ff5c; -ECC_4Win_Look_up_table = 0x3ff1f7a0; -LLM_AA_CT1 = 0x3ff1f79c; -LLM_AA_CT2 = 0x3ff1f798; -RF_TX_PW_CONV_TBL = 0x3ff1f790; -TASK_DESC_MISC = 0x3ff1f784; -adv_evt_prop2type = 0x3ff1f768; -adv_evt_type2prop = 0x3ff1f760; -aes_cmac_zero = 0x3ff1f750; -aes_k2_salt = 0x3ff1f740; -aes_k3_id64 = 0x3ff1f738; -aes_k3_salt = 0x3ff1f728; -aes_k4_id6 = 0x3ff1f724; -aes_k4_salt = 0x3ff1f714; -bigHexP256 = 0x3ff1f6e8; -byte_tx_time = 0x3ff1f6e0; -co_null_bdaddr = 0x3ff1f6d8; -co_phy_mask_to_rate = 0x3ff1f6d0; -co_phy_mask_to_value = 0x3ff1f6c8; -co_phy_to_rate = 0x3ff1f6c4; -co_phy_value_to_mask = 0x3ff1f6c0; -co_rate_to_byte_dur_us = 0x3ff1f6b8; -co_rate_to_phy = 0x3ff1f6b0; -co_rate_to_phy_mask = 0x3ff1f6ac; -co_sca2ppm = 0x3ff1f69c; -coef_B = 0x3ff1f670; -connect_req_dur_tab = 0x3ff1f668; -ecc_Jacobian_InfinityPoint256 = 0x3ff1f5e4; -em_base_reg_lut = 0x3ff1f518; -fixed_tx_time = 0x3ff1f510; -h4tl_msgtype2hdrlen = 0x3ff1f508; -hci_cmd_desc_root_tab = 0x3ff1f4d8; -hci_cmd_desc_tab_ctrl_bb = 0x3ff1f46c; -hci_cmd_desc_tab_info_par = 0x3ff1f43c; -hci_cmd_desc_tab_le = 0x3ff1f0a0; -hci_cmd_desc_tab_lk_ctrl = 0x3ff1f088; -hci_cmd_desc_tab_stat_par = 0x3ff1f07c; -hci_cmd_desc_tab_vs = 0x3ff1f040; -hci_evt_desc_tab = 0x3ff1eff8; -hci_evt_le_desc_tab = 0x3ff1ef58; -hci_evt_le_desc_tab_esp = 0x3ff1ef50; -hci_rsvd_evt_msk = 0x3ff1ef48; -lld_aux_phy_to_rate = 0x3ff1ef44; -lld_init_max_aux_dur_tab = 0x3ff1ef3c; -lld_scan_map_legacy_pdu_to_evt_type = 0x3ff1ef34; -lld_scan_max_aux_dur_tab = 0x3ff1ef2c; -lld_sync_max_aux_dur_tab = 0x3ff1ef24; -llm_local_le_feats = 0x3ff1ef1c; -llm_local_le_states = 0x3ff1ef14; -llm_local_supp_cmds = 0x3ff1eeec; -maxSecretKey_256 = 0x3ff1eecc; -max_data_tx_time = 0x3ff1eec4; -one_bits = 0x3ff1eeb4; -rwip_coex_cfg = 0x3ff1eeac; -rwip_priority = 0x3ff1ee94; -veryBigHexP256 = 0x3ff1ee48; - -/* bluetooth hook funcs */ -r_llc_loc_encrypt_proc_continue_hook = 0x40001c60; -r_llc_loc_phy_upd_proc_continue_hook = 0x40001c64; -r_llc_rem_phy_upd_proc_continue_hook = 0x40001c68; -r_lld_scan_frm_eof_isr_hook = 0x40001c6c; -r_lld_scan_evt_start_cbk_hook = 0x40001c70; -r_lld_scan_process_pkt_rx_ext_adv_hook = 0x40001c78; -r_lld_scan_sched_hook = 0x40001c7c; -r_lld_adv_evt_start_cbk_hook = 0x40001c84; -r_lld_adv_aux_evt_start_cbk_hook = 0x40001c88; -r_lld_adv_frm_isr_hook = 0x40001c8c; -r_lld_adv_start_init_evt_param_hook = 0x40001c90; -r_lld_con_evt_canceled_cbk_hook = 0x40001c94; -r_lld_con_frm_isr_hook = 0x40001c98; -r_lld_con_tx_hook = 0x40001c9c; -r_lld_con_rx_hook = 0x40001ca0; -r_lld_con_evt_start_cbk_hook = 0x40001ca4; -r_lld_con_tx_prog_new_packet_hook = 0x40001cac; -r_lld_init_frm_eof_isr_hook = 0x40001cb0; -r_lld_init_evt_start_cbk_hook = 0x40001cb4; -r_lld_init_sched_hook = 0x40001cbc; -r_lld_init_process_pkt_tx_hook = 0x40001cc0; -r_lld_per_adv_evt_start_cbk_hook = 0x40001cc4; -r_lld_per_adv_frm_isr_hook = 0x40001cc8; -r_lld_per_adv_start_hook = 0x40001ccc; -r_lld_sync_frm_eof_isr_hook = 0x40001cd0; -r_lld_sync_evt_start_cbk_hook = 0x40001cd4; -r_lld_sync_start_hook = 0x40001cd8; -r_lld_sync_process_pkt_rx_pkt_check_hook = 0x40001cdc; -r_sch_arb_insert_hook = 0x40001ce0; -r_sch_plan_offset_req_hook = 0x40001ce4; - -/*************************************** - Group rom_pp - ***************************************/ - -/* Functions */ -esp_pp_rom_version_get = 0x400015b0; -RC_GetBlockAckTime = 0x400015b4; -ebuf_list_remove = 0x400015b8; -/*esf_buf_alloc = 0x400015bc;*/ -GetAccess = 0x400015c8; -hal_mac_is_low_rate_enabled = 0x400015cc; -hal_mac_tx_get_blockack = 0x400015d0; -/* hal_mac_tx_set_ppdu = 0x400015d4;*/ -ic_get_trc = 0x400015d8; -/* ic_mac_deinit = 0x400015dc; */ -ic_mac_init = 0x400015e0; -ic_interface_enabled = 0x400015e4; -is_lmac_idle = 0x400015e8; -/*lmacAdjustTimestamp = 0x400015ec;*/ -lmacDiscardAgedMSDU = 0x400015f0; -/*lmacDiscardMSDU = 0x400015f4;*/ -/*lmacEndFrameExchangeSequence = 0x400015f8;*/ -lmacIsIdle = 0x400015fc; -lmacIsLongFrame = 0x40001600; -/*lmacMSDUAged = 0x40001604;*/ -lmacPostTxComplete = 0x40001608; -lmacProcessAllTxTimeout = 0x4000160c; -lmacProcessCollisions = 0x40001610; -lmacProcessRxSucData = 0x40001614; -lmacReachLongLimit = 0x40001618; -lmacReachShortLimit = 0x4000161c; -lmacRecycleMPDU = 0x40001620; -lmacRxDone = 0x40001624; -/*lmacSetTxFrame = 0x40001628;*/ -/*lmacTxFrame = 0x40001630;*/ -mac_tx_set_duration = 0x40001634; -/* mac_tx_set_htsig = 0x40001638;*/ -mac_tx_set_plcp0 = 0x4000163c; -/* mac_tx_set_plcp1 = 0x40001640;*/ -mac_tx_set_plcp2 = 0x40001644; -/* pm_check_state = 0x40001648; */ -pm_disable_dream_timer = 0x4000164c; -pm_disable_sleep_delay_timer = 0x40001650; -/*pm_dream = 0x40001654;*/ -pm_mac_wakeup = 0x40001658; -pm_mac_sleep = 0x4000165c; -pm_enable_active_timer = 0x40001660; -pm_enable_sleep_delay_timer = 0x40001664; -pm_local_tsf_process = 0x40001668; -pm_set_beacon_filter = 0x4000166c; -pm_is_in_wifi_slice_threshold = 0x40001670; -pm_is_waked = 0x40001674; -pm_keep_alive = 0x40001678; -/* pm_on_beacon_rx = 0x4000167c; */ -pm_on_data_rx = 0x40001680; -pm_on_tbtt = 0x40001684; -/* pm_parse_beacon = 0x40001688;*/ -/* pm_process_tim = 0x4000168c; */ -/*pm_rx_beacon_process = 0x40001690;*/ -/* pm_rx_data_process = 0x40001694; */ -/*pm_sleep = 0x40001698;*/ -pm_sleep_for = 0x4000169c; -/* pm_tbtt_process = 0x400016a0; */ -ppAMPDU2Normal = 0x400016a4; -/*ppAssembleAMPDU = 0x400016a8;*/ -ppCalFrameTimes = 0x400016ac; -ppCalSubFrameLength = 0x400016b0; -/*ppCalTxAMPDULength = 0x400016b4;*/ -ppCheckTxAMPDUlength = 0x400016b8; -ppDequeueRxq_Locked = 0x400016bc; -ppDequeueTxQ = 0x400016c0; -ppEmptyDelimiterLength = 0x400016c4; -ppEnqueueRxq = 0x400016c8; -ppEnqueueTxDone = 0x400016cc; -ppGetTxQFirstAvail_Locked = 0x400016d0; -ppGetTxframe = 0x400016d4; -ppProcessRxPktHdr = 0x400016e0; -ppProcessTxQ = 0x400016e4; -ppRecordBarRRC = 0x400016e8; -lmacRequestTxopQueue = 0x400016ec; -lmacReleaseTxopQueue = 0x400016f0; -ppRecycleAmpdu = 0x400016f4; -ppRecycleRxPkt = 0x400016f8; -ppResortTxAMPDU = 0x400016fc; -ppResumeTxAMPDU = 0x40001700; -/* ppRxFragmentProc = 0x40001704; */ -/* ppRxPkt = 0x40001708; */ -ppRxProtoProc = 0x4000170c; -ppSearchTxQueue = 0x40001710; -ppSearchTxframe = 0x40001714; -ppSelectNextQueue = 0x40001718; -ppSubFromAMPDU = 0x4000171c; -ppTask = 0x40001720; -ppTxPkt = 0x40001724; -ppTxProtoProc = 0x40001728; -ppTxqUpdateBitmap = 0x4000172c; -/*pp_coex_tx_request = 0x40001730;*/ -pp_hdrsize = 0x40001734; -pp_post = 0x40001738; -pp_process_hmac_waiting_txq = 0x4000173c; -rcGetAmpduSched = 0x40001740; -rcUpdateRxDone = 0x40001744; -rc_get_trc = 0x40001748; -rc_get_trc_by_index = 0x4000174c; -rcAmpduLowerRate = 0x40001750; -rcampduuprate = 0x40001754; -rcClearCurAMPDUSched = 0x40001758; -rcClearCurSched = 0x4000175c; -rcClearCurStat = 0x40001760; -rcLowerSched = 0x40001768; -rcSetTxAmpduLimit = 0x4000176c; -/* rcTxUpdatePer = 0x40001770;*/ -rcUpdateAckSnr = 0x40001774; -/*rcUpdateRate = 0x40001778;*/ -/* rcUpdateTxDone = 0x4000177c; */ -rcUpdateTxDoneAmpdu2 = 0x40001780; -rcUpSched = 0x40001784; -rssi_margin = 0x40001788; -rx11NRate2AMPDULimit = 0x4000178c; -TRC_AMPDU_PER_DOWN_THRESHOLD = 0x40001790; -TRC_AMPDU_PER_UP_THRESHOLD = 0x40001794; -trc_calc_duration = 0x40001798; -trc_isTxAmpduOperational = 0x4000179c; -trc_onAmpduOp = 0x400017a0; -TRC_PER_IS_GOOD = 0x400017a4; -trc_SetTxAmpduState = 0x400017a8; -trc_tid_isTxAmpduOperational = 0x400017ac; -trcAmpduSetState = 0x400017b0; -/*wDev_AppendRxBlocks = 0x400017b8;*/ -wDev_DiscardFrame = 0x400017bc; -wDev_GetNoiseFloor = 0x400017c0; -wDev_IndicateAmpdu = 0x400017c4; -/*wDev_IndicateFrame = 0x400017c8;*/ -wdev_bank_store = 0x400017cc; -wdev_bank_load = 0x400017d0; -wdev_mac_reg_load = 0x400017d4; -wdev_mac_reg_store = 0x400017d8; -wdev_mac_special_reg_load = 0x400017dc; -wdev_mac_special_reg_store = 0x400017e0; -wdev_mac_wakeup = 0x400017e4; -wdev_mac_sleep = 0x400017e8; -hal_mac_is_dma_enable = 0x400017ec; -/*wDev_ProcessFiq = 0x400017f0;*/ -/*wDev_ProcessRxSucData = 0x400017f4;*/ -wdevProcessRxSucDataAll = 0x400017f8; -wdev_csi_len_align = 0x400017fc; -ppDequeueTxDone_Locked = 0x40001800; -/*pm_tx_data_done_process = 0x40001808;*/ -config_is_cache_tx_buf_enabled = 0x4000180c; -//ppMapWaitTxq = 0x40001810; -ppProcessWaitingQueue = 0x40001814; -ppDisableQueue = 0x40001818; -pm_allow_tx = 0x4000181c; -/* Data (.data, .bss, .rodata) */ -our_instances_ptr = 0x3ff1ee44; -pTxRx = 0x3fcdf968; -lmacConfMib_ptr = 0x3fcdf964; -our_wait_eb = 0x3fcdf960; -our_tx_eb = 0x3fcdf95c; -pp_wdev_funcs = 0x3fcdf958; -g_osi_funcs_p = 0x3fcdf954; -wDevCtrl_ptr = 0x3fcdf950; -g_wdev_last_desc_reset_ptr = 0x3ff1ee40; -wDevMacSleep_ptr = 0x3fcdf94c; -g_lmac_cnt_ptr = 0x3fcdf948; -our_controls_ptr = 0x3ff1ee3c; -pp_sig_cnt_ptr = 0x3fcdf944; -g_eb_list_desc_ptr = 0x3fcdf940; -s_fragment_ptr = 0x3fcdf93c; -if_ctrl_ptr = 0x3fcdf938; -g_intr_lock_mux = 0x3fcdf934; -g_wifi_global_lock = 0x3fcdf930; -s_wifi_queue = 0x3fcdf92c; -pp_task_hdl = 0x3fcdf928; -s_pp_task_create_sem = 0x3fcdf924; -s_pp_task_del_sem = 0x3fcdf920; -g_wifi_menuconfig_ptr = 0x3fcdf91c; -xphyQueue = 0x3fcdf918; -ap_no_lr_ptr = 0x3fcdf914; -rc11BSchedTbl_ptr = 0x3fcdf910; -rc11NSchedTbl_ptr = 0x3fcdf90c; -rcLoRaSchedTbl_ptr = 0x3fcdf908; -BasicOFDMSched_ptr = 0x3fcdf904; -trc_ctl_ptr = 0x3fcdf900; -g_pm_cnt_ptr = 0x3fcdf8fc; -g_pm_ptr = 0x3fcdf8f8; -g_pm_cfg_ptr = 0x3fcdf8f4; -g_esp_mesh_quick_funcs_ptr = 0x3fcdf8f0; -g_txop_queue_status_ptr = 0x3fcdf8ec; -g_mac_sleep_en_ptr = 0x3fcdf8e8; -g_mesh_is_root_ptr = 0x3fcdf8e4; -g_mesh_topology_ptr = 0x3fcdf8e0; -g_mesh_init_ps_type_ptr = 0x3fcdf8dc; -g_mesh_is_started_ptr = 0x3fcdf8d8; -g_config_func = 0x3fcdf8d4; -g_net80211_tx_func = 0x3fcdf8d0; -g_timer_func = 0x3fcdf8cc; -s_michael_mic_failure_cb = 0x3fcdf8c8; -wifi_sta_rx_probe_req = 0x3fcdf8c4; -g_tx_done_cb_func = 0x3fcdf8c0; -g_per_conn_trc = 0x3fcdf874; -s_encap_amsdu_func = 0x3fcdf870; - - -/*************************************** - Group rom_net80211 - ***************************************/ - -/* Functions */ -esp_net80211_rom_version_get = 0x40001820; -ampdu_dispatch = 0x40001824; -ampdu_dispatch_all = 0x40001828; -ampdu_dispatch_as_many_as_possible = 0x4000182c; -ampdu_dispatch_movement = 0x40001830; -ampdu_dispatch_upto = 0x40001834; -chm_is_at_home_channel = 0x40001838; -cnx_node_is_existing = 0x4000183c; -cnx_node_search = 0x40001840; -ic_ebuf_recycle_rx = 0x40001844; -ic_ebuf_recycle_tx = 0x40001848; -ic_reset_rx_ba = 0x4000184c; -ieee80211_align_eb = 0x40001850; -/*ieee80211_ampdu_reorder = 0x40001854;*/ -ieee80211_ampdu_start_age_timer = 0x40001858; -/*ieee80211_encap_esfbuf = 0x4000185c;*/ -ieee80211_is_tx_allowed = 0x40001860; -ieee80211_output_pending_eb = 0x40001864; -/*ieee80211_output_process = 0x40001868;*/ -ieee80211_set_tx_desc = 0x4000186c; -rom_sta_input = 0x40001870; -wifi_get_macaddr = 0x40001874; -wifi_rf_phy_disable = 0x40001878; -wifi_rf_phy_enable = 0x4000187c; -ic_ebuf_alloc = 0x40001880; -/*ieee80211_classify = 0x40001884;*/ -ieee80211_copy_eb_header = 0x40001888; -ieee80211_recycle_cache_eb = 0x4000188c; -ieee80211_search_node = 0x40001890; -roundup2 = 0x40001894; -ieee80211_crypto_encap = 0x40001898; -/* ieee80211_crypto_decap = 0x4000189c; */ -/* ieee80211_decap = 0x400018a0; */ -ieee80211_set_tx_pti = 0x400018a4; -wifi_is_started = 0x400018a8; -/* Data (.data, .bss, .rodata) */ -net80211_funcs = 0x3fcdf86c; -g_scan = 0x3fcdf868; -g_chm = 0x3fcdf864; -g_ic_ptr = 0x3fcdf860; -g_hmac_cnt_ptr = 0x3fcdf85c; -g_tx_cacheq_ptr = 0x3fcdf858; -s_netstack_free = 0x3fcdf854; -mesh_rxcb = 0x3fcdf850; -sta_rxcb = 0x3fcdf84c; - - -/*************************************** - Group rom_coexist - ***************************************/ - -/* Functions */ -esp_coex_rom_version_get = 0x400018ac; -coex_bt_release = 0x400018b0; -coex_bt_request = 0x400018b4; -coex_core_ble_conn_dyn_prio_get = 0x400018b8; -/*coex_core_event_duration_get = 0x400018bc;*/ -coex_core_pti_get = 0x400018c0; -coex_core_release = 0x400018c4; -coex_core_request = 0x400018c8; -coex_core_status_get = 0x400018cc; -/*coex_core_timer_idx_get = 0x400018d0;*/ -coex_event_duration_get = 0x400018d4; -coex_hw_timer_disable = 0x400018d8; -coex_hw_timer_enable = 0x400018dc; -coex_hw_timer_set = 0x400018e0; -coex_schm_interval_set = 0x400018e4; -coex_schm_lock = 0x400018e8; -coex_schm_unlock = 0x400018ec; -coex_status_get = 0x400018f0; -coex_wifi_release = 0x400018f4; -esp_coex_ble_conn_dynamic_prio_get = 0x400018f8; -/* Data (.data, .bss, .rodata) */ -coex_env_ptr = 0x3fcdf848; -coex_pti_tab_ptr = 0x3fcdf844; -coex_schm_env_ptr = 0x3fcdf840; -coexist_funcs = 0x3fcdf83c; -g_coa_funcs_p = 0x3fcdf838; -g_coex_param_ptr = 0x3fcdf834; - - -/*************************************** - Group rom_phy - ***************************************/ - -/* Functions */ -phy_get_romfuncs = 0x400018fc; -rom_abs_temp = 0x40001900; -rom_bb_bss_cbw40_dig = 0x40001904; -rom_bb_wdg_test_en = 0x40001908; -rom_bb_wdt_get_status = 0x4000190c; -rom_bb_wdt_int_enable = 0x40001910; -rom_bb_wdt_rst_enable = 0x40001914; -rom_bb_wdt_timeout_clear = 0x40001918; -rom_cbw2040_cfg = 0x4000191c; -rom_check_noise_floor = 0x40001920; -rom_chip_i2c_readReg = 0x40001924; -rom_chip_i2c_writeReg = 0x40001928; -rom_correct_rf_ana_gain = 0x4000192c; -rom_dc_iq_est = 0x40001930; -rom_disable_agc = 0x40001934; -rom_en_pwdet = 0x40001938; -rom_enable_agc = 0x4000193c; -rom_get_bbgain_db = 0x40001940; -rom_get_data_sat = 0x40001944; -rom_get_i2c_read_mask = 0x40001948; -rom_get_pwctrl_correct = 0x4000194c; -rom_get_rf_gain_qdb = 0x40001950; -rom_i2c_readReg = 0x40001954; -rom_i2c_readReg_Mask = 0x40001958; -rom_i2c_writeReg = 0x4000195c; -rom_i2c_writeReg_Mask = 0x40001960; -/* rom_index_to_txbbgain = 0x40001964; */ -rom_iq_est_disable = 0x40001968; -rom_iq_est_enable = 0x4000196c; -rom_linear_to_db = 0x40001970; -rom_loopback_mode_en = 0x40001974; -rom_mhz2ieee = 0x40001978; -rom_noise_floor_auto_set = 0x4000197c; -rom_pbus_debugmode = 0x40001980; -rom_pbus_force_mode = 0x40001984; -rom_pbus_force_test = 0x40001988; -rom_pbus_rd = 0x4000198c; -rom_pbus_rd_addr = 0x40001990; -rom_pbus_rd_shift = 0x40001994; -rom_pbus_set_dco = 0x40001998; -rom_pbus_set_rxgain = 0x4000199c; -rom_pbus_workmode = 0x400019a0; -rom_pbus_xpd_rx_off = 0x400019a4; -rom_pbus_xpd_rx_on = 0x400019a8; -rom_pbus_xpd_tx_off = 0x400019ac; -/* rom_pbus_xpd_tx_on = 0x400019b0; */ -rom_phy_byte_to_word = 0x400019b4; -rom_phy_disable_cca = 0x400019b8; -rom_phy_enable_cca = 0x400019bc; -rom_phy_get_noisefloor = 0x400019c0; -rom_phy_get_rx_freq = 0x400019c4; -rom_phy_set_bbfreq_init = 0x400019c8; -rom_pow_usr = 0x400019cc; -rom_pwdet_sar2_init = 0x400019d0; -rom_read_hw_noisefloor = 0x400019d4; -rom_read_sar_dout = 0x400019d8; -rom_set_cal_rxdc = 0x400019dc; -rom_set_chan_cal_interp = 0x400019e0; -rom_set_loopback_gain = 0x400019e4; -rom_set_noise_floor = 0x400019e8; -rom_set_rxclk_en = 0x400019ec; -/* rom_set_tx_dig_gain = 0x400019f0; */ -/* rom_set_txcap_reg = 0x400019f4; */ -rom_set_txclk_en = 0x400019f8; -rom_spur_cal = 0x400019fc; -rom_spur_reg_write_one_tone = 0x40001a00; -rom_target_power_add_backoff = 0x40001a04; -rom_tx_pwctrl_bg_init = 0x40001a08; -/* rom_txbbgain_to_index = 0x40001a0c; */ -rom_wifi_11g_rate_chg = 0x40001a10; -rom_write_gain_mem = 0x40001a14; -chip726_phyrom_version = 0x40001a18; -rom_disable_wifi_agc = 0x40001a1c; -rom_enable_wifi_agc = 0x40001a20; -rom_set_tx_gain_table = 0x40001a24; -rom_bt_index_to_bb = 0x40001a28; -rom_bt_bb_to_index = 0x40001a2c; -rom_wr_bt_tx_atten = 0x40001a30; -rom_wr_bt_tx_gain_mem = 0x40001a34; -rom_spur_coef_cfg = 0x40001a38; -rom_bb_bss_cbw40 = 0x40001a3c; -rom_set_cca = 0x40001a40; -rom_tx_paon_set = 0x40001a44; -rom_i2cmst_reg_init = 0x40001a48; -rom_iq_corr_enable = 0x40001a4c; -rom_fe_reg_init = 0x40001a50; -/* rom_agc_reg_init = 0x40001a54; */ -/* rom_bb_reg_init = 0x40001a58; */ -rom_mac_enable_bb = 0x40001a5c; -rom_bb_wdg_cfg = 0x40001a60; -rom_force_txon = 0x40001a64; -rom_fe_txrx_reset = 0x40001a68; -rom_set_rx_comp = 0x40001a6c; -/* rom_set_pbus_reg = 0x40001a70; */ -rom_write_chan_freq = 0x40001a74; -/* rom_phy_xpd_rf = 0x40001a78; */ -rom_set_xpd_sar = 0x40001a7c; -rom_write_dac_gain2 = 0x40001a80; -rom_rtc_sar2_init = 0x40001a84; -rom_get_target_power_offset = 0x40001a88; -/* rom_write_txrate_power_offset = 0x40001a8c; */ -rom_get_rate_fcc_index = 0x40001a90; -rom_get_rate_target_power = 0x40001a94; -rom_write_wifi_dig_gain = 0x40001a98; -rom_bt_correct_rf_ana_gain = 0x40001a9c; -rom_pkdet_vol_start = 0x40001aa0; -rom_read_sar2_code = 0x40001aa4; -rom_get_sar2_vol = 0x40001aa8; -rom_get_pll_vol = 0x40001aac; -rom_get_phy_target_power = 0x40001ab0; -/* rom_temp_to_power = 0x40001ab4; */ -rom_phy_track_pll_cap = 0x40001ab8; -rom_phy_pwdet_always_en = 0x40001abc; -rom_phy_pwdet_onetime_en = 0x40001ac0; -rom_get_i2c_mst0_mask = 0x40001ac4; -rom_get_i2c_hostid = 0x40001ac8; -rom_enter_critical_phy = 0x40001acc; -rom_exit_critical_phy = 0x40001ad0; -rom_chip_i2c_readReg_org = 0x40001ad4; -rom_i2c_paral_set_mst0 = 0x40001ad8; -rom_i2c_paral_set_read = 0x40001adc; -rom_i2c_paral_read = 0x40001ae0; -rom_i2c_paral_write = 0x40001ae4; -rom_i2c_paral_write_num = 0x40001ae8; -rom_i2c_paral_write_mask = 0x40001aec; -rom_bb_bss_cbw40_ana = 0x40001af0; -rom_chan_to_freq = 0x40001af4; -/* rom_open_i2c_xpd = 0x40001af8; */ -rom_dac_rate_set = 0x40001afc; -/* rom_tsens_read_init = 0x40001b00; */ -/* rom_tsens_code_read = 0x40001b04; */ -rom_tsens_index_to_dac = 0x40001b08; -rom_tsens_index_to_offset = 0x40001b0c; -/* rom_tsens_dac_cal = 0x40001b10; */ -rom_code_to_temp = 0x40001b14; -rom_write_pll_cap_mem = 0x40001b18; -rom_pll_correct_dcap = 0x40001b1c; -rom_phy_en_hw_set_freq = 0x40001b20; -rom_phy_dis_hw_set_freq = 0x40001b24; -/* rom_pll_vol_cal = 0x40001b28; */ -memset = 0x40000354; -memcpy = 0x40000358; -memmove = 0x4000035c; -memcmp = 0x40000360; - -strncmp = 0x40000370; -strncpy = 0x40000368; -strcpy = 0x40000364; - -abs = 0x40000424; - -PROVIDE(cache_dbus_mmu_set = 0x40000564); - -PROVIDE( strcat = 0x400003d8 ); -PROVIDE( strcmp = 0x4000036c ); -PROVIDE( strchr = 0x400003e0 ); -PROVIDE( strlcpy = 0x400003f0 ); -PROVIDE( strstr = 0x40000378 ); -PROVIDE( strcasecmp = 0x400003d0 ); - -PROVIDE( memchr = 0x400003c8 ); - -/** - * ROM APIs - */ -PROVIDE ( esp_rom_crc32_le = crc32_le ); -PROVIDE ( esp_rom_crc16_le = crc16_le ); -PROVIDE ( esp_rom_crc8_le = crc8_le ); -PROVIDE ( esp_rom_crc32_be = crc32_be ); -PROVIDE ( esp_rom_crc16_be = crc16_be ); -PROVIDE ( esp_rom_crc8_be = crc8_be ); - -PROVIDE ( esp_rom_gpio_pad_select_gpio = gpio_pad_select_gpio ); -PROVIDE ( esp_rom_gpio_pad_pullup_only = gpio_pad_pullup ); -PROVIDE ( esp_rom_gpio_pad_set_drv = gpio_pad_set_drv ); -PROVIDE ( esp_rom_gpio_pad_unhold = gpio_pad_unhold ); -PROVIDE ( esp_rom_gpio_connect_in_signal = gpio_matrix_in ); -PROVIDE ( esp_rom_gpio_connect_out_signal = gpio_matrix_out ); - -PROVIDE ( esp_rom_efuse_mac_address_crc8 = esp_crc8 ); -PROVIDE ( esp_rom_efuse_get_flash_gpio_info = ets_efuse_get_spiconfig ); -PROVIDE ( esp_rom_efuse_is_secure_boot_enabled = ets_efuse_secure_boot_enabled ); -PROVIDE ( esp_rom_efuse_get_flash_wp_gpio = ets_efuse_get_wp_pad ); - -PROVIDE ( esp_rom_uart_flush_tx = uart_tx_flush ); -PROVIDE ( esp_rom_uart_tx_one_char = uart_tx_one_char ); -PROVIDE ( esp_rom_uart_tx_wait_idle = uart_tx_wait_idle ); -PROVIDE ( esp_rom_uart_rx_one_char = uart_rx_one_char ); -PROVIDE ( esp_rom_uart_rx_string = UartRxString ); - -PROVIDE ( esp_rom_output_flush_tx = uart_tx_flush ); -PROVIDE ( esp_rom_output_tx_one_char = uart_tx_one_char ); -PROVIDE ( esp_rom_output_tx_wait_idle = uart_tx_wait_idle ); -PROVIDE ( esp_rom_output_rx_one_char = uart_rx_one_char ); -PROVIDE ( esp_rom_output_rx_string = UartRxString ); - -PROVIDE ( esp_rom_md5_init = MD5Init ); -PROVIDE ( esp_rom_md5_update = MD5Update ); -PROVIDE ( esp_rom_md5_final = MD5Final ); - -PROVIDE ( esp_rom_software_reset_system = software_reset ); -PROVIDE ( esp_rom_software_reset_cpu = software_reset_cpu ); - -PROVIDE ( esp_rom_printf = ets_printf ); -PROVIDE ( esp_rom_delay_us = ets_delay_us ); -PROVIDE ( esp_rom_get_reset_reason = rtc_get_reset_reason ); -PROVIDE ( esp_rom_route_intr_matrix = intr_matrix_set ); -PROVIDE ( esp_rom_get_cpu_ticks_per_us = ets_get_cpu_frequency ); -PROVIDE ( esp_rom_set_cpu_ticks_per_us = ets_update_cpu_frequency ); - -PROVIDE ( esp_rom_spiflash_clear_bp = esp_rom_spiflash_unlock ); -PROVIDE ( esp_rom_spiflash_write_enable = SPI_write_enable ); -PROVIDE ( esp_rom_spiflash_erase_area = SPIEraseArea ); - -PROVIDE ( esp_rom_spiflash_fix_dummylen = spi_dummy_len_fix ); -PROVIDE ( esp_rom_spiflash_set_drvs = SetSpiDrvs); -PROVIDE ( esp_rom_spiflash_select_padsfunc = SelectSpiFunction ); -PROVIDE ( esp_rom_spiflash_common_cmd = SPI_Common_Command ); - -PROVIDE ( esp_rom_regi2c_read = rom_i2c_readReg ); -PROVIDE ( esp_rom_regi2c_read_mask = rom_i2c_readReg_Mask ); -PROVIDE ( esp_rom_regi2c_write = rom_i2c_writeReg ); -PROVIDE ( esp_rom_regi2c_write_mask = rom_i2c_writeReg_Mask ); - -/* -ESP32C3 ECO3 ROM address table -Version 3 API's imported from the ROM -*/ - -/*esf_buf_alloc_dynamic = 0x400015c0;*/ -/*esf_buf_recycle = 0x400015c4;*/ -/*lmacTxDone = 0x4000162c;*/ -/*ppMapTxQueue = 0x400016d8;*/ -/*rcGetSched = 0x40001764;*/ -wDevCheckBlockError = 0x400017b4; -/*ppProcTxDone = 0x40001804;*/ -/*sta_input = rom_sta_input;*/ - -/*************************************** - Group rom_phy - ***************************************/ - -/* Functions */ -rom_index_to_txbbgain = 0x40001964; -rom_pbus_xpd_tx_on = 0x400019b0; -rom_set_tx_dig_gain = 0x400019f0; -rom_set_txcap_reg = 0x400019f4; -rom_txbbgain_to_index = 0x40001a0c; -rom_agc_reg_init = 0x40001a54; -rom_bb_reg_init = 0x40001a58; -rom_set_pbus_reg = 0x40001a70; -rom_phy_xpd_rf = 0x40001a78; -rom_write_txrate_power_offset = 0x40001a8c; -rom_temp_to_power = 0x40001ab4; -rom_open_i2c_xpd = 0x40001af8; -rom_tsens_read_init = 0x40001b00; -rom_tsens_code_read = 0x40001b04; -rom_tsens_dac_cal = 0x40001b10; -rom_pll_vol_cal = 0x40001b28; - -/*************************************** - Group eco3_wifi - ***************************************/ - -/* Functions */ -wdev_is_data_in_rxlist = 0x40001b2c; -ppProcTxCallback = 0x40001b30; -ieee80211_gettid = 0x40001b34; - - -/*************************************** - Group eco3_bluetooth - ***************************************/ - -/* Functions */ -r_lld_legacy_adv_dynamic_pti_get = 0x40001b38; -r_lld_legacy_adv_dynamic_pti_process = 0x40001b3c; -r_lld_ext_adv_dynamic_pti_get = 0x40001b40; -r_lld_ext_adv_dynamic_aux_pti_process = 0x40001b44; -r_lld_ext_adv_dynamic_pti_process = 0x40001b48; -r_lld_adv_ext_pkt_prepare_set = 0x40001b4c; -r_lld_adv_ext_chain_connectable_construct = 0x40001b54; -r_lld_adv_pkt_rx_connect_post = 0x40001b5c; -r_lld_adv_start_init_evt_param = 0x40001b60; -r_lld_adv_start_set_cs = 0x40001b64; -/* r_lld_adv_start_update_filter_policy = 0x40001b68; */ -r_lld_adv_start_schedule_asap = 0x40001b6c; -r_lld_con_tx_prog_new_packet_coex = 0x40001b70; -r_lld_per_adv_dynamic_pti_get = 0x40001b78; -r_lld_per_adv_evt_start_chm_upd = 0x40001b7c; -r_lld_ext_scan_dynamic_pti_get = 0x40001b80; -r_lld_sync_insert = 0x40001b88; -r_sch_prog_ble_push = 0x40001b8c; -r_sch_prog_bt_push = 0x40001b90; -r_lld_init_evt_end_type_set = 0x40001b94; -r_lld_init_evt_end_type_get = 0x40001b98; -r_lld_adv_direct_adv_use_rpa_addr_state_set = 0x40001b9c; -r_lld_adv_direct_adv_use_rpa_addr_state_get = 0x40001ba0; -r_lld_init_evt_end_type_check_state_set = 0x40001ba4; -r_lld_init_evt_end_type_check_state_get = 0x40001ba8; - - -/*************************************** - Group eco3_phy - ***************************************/ - -/* Functions */ -phy_bbpll_en_usb = 0x400021e0; -rom_wrtie_pll_cap = 0x40001bac; -rom_set_tx_gain_mem = 0x40001bb0; -rom_bt_tx_dig_gain = 0x40001bb4; -rom_bt_get_tx_gain = 0x40001bb8; -rom_get_chan_target_power = 0x40001bbc; -rom_get_tx_gain_value = 0x40001bc0; -rom_wifi_tx_dig_gain = 0x40001bc4; -rom_wifi_get_tx_gain = 0x40001bc8; -rom_fe_i2c_reg_renew = 0x40001bcc; -rom_wifi_agc_sat_gain = 0x40001bd0; -rom_i2c_master_reset = 0x40001bd4; -rom_bt_filter_reg = 0x40001bd8; -rom_phy_bbpll_cal = 0x40001bdc; -rom_i2c_sar2_init_code = 0x40001be0; -rom_phy_param_addr = 0x40001be4; -rom_phy_reg_init = 0x40001be8; -rom_set_chan_reg = 0x40001bec; -rom_phy_wakeup_init = 0x40001bf0; -rom_phy_i2c_init1 = 0x40001bf4; -rom_tsens_temp_read = 0x40001bf8; -rom_bt_track_pll_cap = 0x40001bfc; -rom_wifi_track_pll_cap = 0x40001c00; -rom_wifi_set_tx_gain = 0x40001c04; -rom_txpwr_cal_track = 0x40001c08; -rom_tx_pwctrl_background = 0x40001c0c; -rom_bt_set_tx_gain = 0x40001c10; -rom_noise_check_loop = 0x40001c14; -rom_phy_close_rf = 0x40001c18; -rom_phy_xpd_tsens = 0x40001c1c; -rom_phy_freq_mem_backup = 0x40001c20; -rom_phy_ant_init = 0x40001c24; -rom_bt_track_tx_power = 0x40001c28; -rom_wifi_track_tx_power = 0x40001c2c; -rom_phy_dig_reg_backup = 0x40001c30; -chip726_phyrom_version_num = 0x40001c34; -/* Data (.data, .bss, .rodata) */ -phy_param_rom = 0x3fcdf830; - -/*************************************** - Group eco3_esp_flash - ***************************************/ - -/* Functions */ -PROVIDE( esp_flash_read_chip_id = 0x40001c38 ); -PROVIDE( detect_spi_flash_chip = 0x40001c3c ); -PROVIDE( esp_rom_spiflash_write_disable = 0x40001c40 ); - -/* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -esf_buf_alloc = 0x400015bc; -esf_buf_alloc_dynamic = 0x400015c0; -esf_buf_recycle = 0x400015c4; -/*hal_mac_tx_set_ppdu = 0x400015d4;*/ -ic_mac_deinit = 0x400015dc; -lmacDiscardMSDU = 0x400015f4; -/*lmacSetTxFrame = 0x40001628;*/ -lmacTxDone = 0x4000162c; -lmacTxFrame = 0x40001630; -mac_tx_set_htsig = 0x40001638; -mac_tx_set_plcp1 = 0x40001640; -pm_check_state = 0x40001648; -/*pm_on_beacon_rx = 0x4000167c;*/ -/*pm_parse_beacon = 0x40001688;*/ -pm_process_tim = 0x4000168c; -pm_rx_beacon_process = 0x40001690; -pm_rx_data_process = 0x40001694; -/* pm_sleep = 0x40001698;*/ -/* pm_tbtt_process = 0x400016a0;*/ -ppMapTxQueue = 0x400016d8; -ppProcTxSecFrame = 0x400016dc; -/*ppRxFragmentProc = 0x40001704;*/ -/* rcGetSched = 0x40001764;*/ -rcTxUpdatePer = 0x40001770; -rcUpdateTxDone = 0x4000177c; -wDevCheckBlockError = 0x400017b4; -/* wDev_IndicateFrame = 0x400017c8;*/ -wDev_ProcessFiq = 0x400017f0; -/*wDev_ProcessRxSucData = 0x400017f4;*/ -/*ppProcTxDone = 0x40001804;*/ -/*pm_tx_data_done_process = 0x40001808;*/ -ppMapWaitTxq = 0x40001810; -/*ieee80211_encap_esfbuf = 0x4000185c;*/ -/*sta_input = 0x40001870;*/ -ieee80211_crypto_decap = 0x4000189c; -ieee80211_decap = 0x400018a0; -/*coex_core_timer_idx_get = 0x400018d0;*/ -rom1_chip_i2c_readReg = 0x40001924; -rom1_chip_i2c_writeReg = 0x40001928; -rom_index_to_txbbgain = 0x40001964; -rom_pbus_xpd_tx_on = 0x400019b0; -rom1_set_noise_floor = 0x400019e8; -rom_set_tx_dig_gain = 0x400019f0; -rom_set_txcap_reg = 0x400019f4; -rom_txbbgain_to_index = 0x40001a0c; -rom1_disable_wifi_agc = 0x40001a1c; -rom1_enable_wifi_agc = 0x40001a20; -rom1_tx_paon_set = 0x40001a44; -rom_agc_reg_init = 0x40001a54; -rom_bb_reg_init = 0x40001a58; -rom1_set_pbus_reg = 0x40001a70; -rom_phy_xpd_rf = 0x40001a78; -rom_write_txrate_power_offset = 0x40001a8c; -rom1_get_rate_fcc_index = 0x40001a90; -rom1_read_sar2_code = 0x40001aa4; -rom2_temp_to_power1 = 0x40001ab4; -rom1_get_i2c_hostid = 0x40001ac8; -rom_open_i2c_xpd = 0x40001af8; -rom2_tsens_read_init1 = 0x40001b00; -rom_tsens_code_read = 0x40001b04; -rom_tsens_dac_cal = 0x40001b10; -rom1_phy_en_hw_set_freq = 0x40001b20; -rom1_phy_dis_hw_set_freq = 0x40001b24; -rom_pll_vol_cal = 0x40001b28; - -rom1_bt_get_tx_gain = 0x40001bb8; -rom1_get_chan_target_power = 0x40001bbc; -rom2_get_tx_gain_value1 = 0x40001bc0; -rom1_wifi_tx_dig_gain = 0x40001bc4; -rom1_wifi_get_tx_gain = 0x40001bc8; -rom1_fe_i2c_reg_renew = 0x40001bcc; -rom1_i2c_master_reset = 0x40001bd4; -rom1_phy_wakeup_init = 0x40001bf0; -rom1_phy_i2c_init1 = 0x40001bf4; -rom1_tsens_temp_read = 0x40001bf8; -rom1_bt_track_pll_cap = 0x40001bfc; -rom1_wifi_set_tx_gain = 0x40001c04; -rom1_txpwr_cal_track = 0x40001c08; -rom1_bt_set_tx_gain = 0x40001c10; -rom1_phy_close_rf = 0x40001c18; - - -/*************************************** - Group eco7_uart - ***************************************/ - -/* Functions */ -uart_tx_switch = 0x40001c44; - - -/*************************************** - Group eco7_bluetooth - ***************************************/ - -/* Functions */ -r_lld_con_count_get = 0x40001c48; -r_lld_update_con_offset = 0x40001c4c; -r_lld_con_update_last_clock = 0x40001c50; -r_lld_con_llcp_ind_info_clear = 0x40001c54; -r_lld_con_update_terminte_info_init = 0x40001c58; -r_lld_con_terminate_max_evt_update = 0x40001c5c; -r_llc_pref_param_compute_eco = 0x40001ce8; -r_llc_hci_con_upd_info_send_eco = 0x40001cec; -r_llc_rem_encrypt_proc_continue_eco = 0x40001cf0; -r_llc_start_eco = 0x40001cf8; -r_lld_ext_adv_dynamic_aux_pti_process_eco = 0x40001cfc; -r_lld_adv_start_eco = 0x40001d04; -r_lld_con_evt_canceled_cbk_eco = 0x40001d08; -r_lld_con_evt_time_update_eco = 0x40001d0c; -r_lld_con_start_eco = 0x40001d10; -r_lld_con_frm_isr_eco = 0x40001d14; -r_lld_con_tx_eco = 0x40001d18; -r_lld_ext_scan_dynamic_pti_process_eco = 0x40001d28; -r_lld_scan_frm_eof_isr_eco = 0x40001d2c; -r_lld_sync_start_eco = 0x40001d30; -r_lld_sync_insert_eco = 0x40001d34; -r_llm_adv_rep_flow_control_update_eco = 0x40001d38; -r_llm_env_adv_dup_filt_init_eco = 0x40001d3c; -r_llm_env_adv_dup_filt_deinit_eco = 0x40001d40; -r_llm_adv_rep_flow_control_check_eco = 0x40001d44; -r_llm_scan_start_eco = 0x40001d48; -r_llm_update_duplicate_scan_count = 0x40001d4c; -r_llc_hci_command_handler_pre = 0x40001d50; -r_llc_hci_command_handler_get = 0x40001d54; -r_llc_hci_command_handler_search = 0x40001d58; -r_llc_llcp_pdu_handler_get_overwrite = 0x40001d5c; -r_llc_llcp_pdu_handler_pre = 0x40001d60; -r_llc_llcp_pdu_handler_end = 0x40001d64; -r_llc_con_conflict_check = 0x40001d6c; -r_sch_prog_hw_reset_try = 0x40001d70; -r_sch_prog_et_state_reset = 0x40001d74; -r_sch_prog_end_isr_handler = 0x40001d78; -r_sch_plan_conflict_check = 0x40001d7c; -r_rwble_isr_hw_fixed = 0x40001d80; -r_bt_bb_recorrect_is_dead = 0x40001d84; -r_bt_bb_restart_hw_recorrect = 0x40001d88; -r_ke_task_handler_pre = 0x40001da0; -r_ke_task_handler_end = 0x40001da4; -r_lld_scan_frm_skip_isr_eco = 0x40001db0; -r_lld_ext_scan_dynamic_pti_reset = 0x40001db4; -r_llc_rem_phy_upd_proc_continue_eco = 0x40001db8; -r_llm_get_preferred_phys = 0x40001dbc; -r_lld_hw_cca_isr_eco = 0x40001dc0; -r_lld_sw_cca_isr_eco = 0x40001dc4; -r_lld_cca_chan_prn_e = 0x40001dc8; -r_lld_cca_chan_prn_s = 0x40001dcc; -r_lld_cca_chan_sel_remap = 0x40001dd0; -r_lld_cca_chan_sel_1 = 0x40001dd4; -r_lld_cca_chan_sel_2 = 0x40001dd8; -r_lld_cca_set_thresh = 0x40001ddc; -r_lld_cca_con_start = 0x40001de0; -r_lld_cca_con_end = 0x40001de4; -r_lld_cca_chm_restore = 0x40001de8; -r_lld_cca_chan_unused_check = 0x40001dec; -r_lld_cca_chm_update_check = 0x40001df0; -r_lld_cca_busy_mode_handle = 0x40001df4; -r_lld_cca_lbt_handle = 0x40001df8; -r_lld_cca_scst_timeout_check = 0x40001dfc; -r_lld_cca_chan_avl_timeout_check = 0x40001e00; - -r_lld_con_start_hook = 0x40001ca8; - -/* ble Functions eco */ -r_bt_bb_isr = 0x40000b9c; -r_bt_rf_coex_conn_phy_coded_data_time_limit_en_get = 0x40000ba8; -r_bt_rtp_get_txpwr_idx_by_act = 0x40000c00; -r_btdm_task_post = 0x40000c14; -r_btdm_task_post_from_isr = 0x40000c18; -r_btdm_task_recycle = 0x40000c1c; -r_hci_register_vendor_desc_tab = 0x40000d9c; -r_ke_task_schedule = 0x40000e80; -r_llc_hci_command_handler = 0x40000ef0; -r_llc_loc_con_upd_proc_continue = 0x40000f60; -r_llc_loc_phy_upd_proc_continue = 0x40000f78; -r_llc_rem_con_upd_proc_continue = 0x40000fb4; -r_lld_con_sched = 0x40001118; -r_lld_con_stop = 0x40001124; -r_lld_llcp_rx_ind_handler = 0x400011b0; -r_lld_per_adv_sched = 0x400011f8; -r_lld_scan_process_pkt_rx_adv_rep = 0x40001284; -r_register_esp_vendor_cmd_handler = 0x40001400; -r_rf_txpwr_cs_get = 0x40001428; -r_rf_txpwr_dbm_get = 0x4000142c; -r_sch_arb_event_start_isr = 0x400014f8; -r_sch_plan_set = 0x40001534; -r_sch_prog_end_isr = 0x40001538; -r_lld_adv_ext_chain_scannable_construct = 0x40001b58; - -r_lld_scan_process_pkt_rx = 0x40001280; -r_llm_le_features_get = 0x400013b0; - -/* ble functions rename */ -r_lld_init_start_hack = 0x400011a4; - -/* ble functions disable */ -/* -r_lld_adv_frm_isr_eco = 0x40001d00; -r_lld_res_list_clear = 0x40004638; -r_lld_res_list_rem = 0x40004680; -r_lld_adv_start_hook = 0x40001c80; -r_lld_con_evt_start_cbk_eco = 0x40001d1c; -r_lld_con_tx_prog_new_packet = 0x40001b74; -r_lld_adv_ext_chain_none_construct = 0x40001b50; -r_llc_llcp_send_eco = 0x40001cf4; -r_llc_llcp_channel_map_ind_ack = 0x40001d68; -r_rwble_isr = 0x40001464; -r_lld_scan_start_eco = 0x40001d24; -r_lld_scan_try_sched_eco = 0x40001dac; -r_lld_scan_start_hook = 0x40001c74; -r_lld_init_start_hook = 0x40001cb8; -r_lld_scan_evt_start_cbk_eco = 0x40001d20; -r_ke_task_handler_get_overwrite = 0x40001da8; -*/ - - -/*************************************** - Group eco7_phy - ***************************************/ - -/* Functions */ -rom2_pll_cap_mem_update = 0x40001e04; -rom2_phy_i2c_enter_critical = 0x40001e08; -rom2_phy_i2c_exit_critical = 0x40001e0c; -rom2_rfpll_cap_correct = 0x40001e10; -rom2_write_pll_cap = 0x40001e14; -rom2_read_pll_cap = 0x40001e18; -rom2_tester_wifi_cali = 0x40001e1c; -rom2_wait_hw_freq_busy = 0x40001e20; -rom2_rfpll_cap_track = 0x40001e24; -rom2_ulp_code_track = 0x40001e28; -rom2_ulp_ext_code_set = 0x40001e2c; -rom2_phy_set_tsens_power = 0x40001e30; -rom2_phy_get_tsens_value = 0x40001e34; -rom_mac_tx_chan_offset = 0x40001e38; -rom_rx_gain_force = 0x40001e3c; - -/* ROM function interface esp32c3.rom.libgcc.ld for esp32c3 - * - * - * Generated from ./interface-esp32c3.yml md5sum 93b28a9e1fe42d212018eb4336849208 - * - * Compatible with ROM where ECO version equal or greater to 0. - * - * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT. - */ - -/*************************************** - Group libgcc - ***************************************/ - -/* Functions */ -__absvdi2 = 0x40000764; -__absvsi2 = 0x40000768; -__adddf3 = 0x4000076c; -__addsf3 = 0x40000770; -__addvdi3 = 0x40000774; -__addvsi3 = 0x40000778; -__ashldi3 = 0x4000077c; -__ashrdi3 = 0x40000780; -__bswapdi2 = 0x40000784; -__bswapsi2 = 0x40000788; -__clear_cache = 0x4000078c; -__clrsbdi2 = 0x40000790; -__clrsbsi2 = 0x40000794; -__clzdi2 = 0x40000798; -__clzsi2 = 0x4000079c; -__cmpdi2 = 0x400007a0; -__ctzdi2 = 0x400007a4; -__ctzsi2 = 0x400007a8; -__divdc3 = 0x400007ac; -__divdf3 = 0x400007b0; -__divdi3 = 0x400007b4; -__divsc3 = 0x400007b8; -__divsf3 = 0x400007bc; -__divsi3 = 0x400007c0; -__eqdf2 = 0x400007c4; -__eqsf2 = 0x400007c8; -__extendsfdf2 = 0x400007cc; -__ffsdi2 = 0x400007d0; -__ffssi2 = 0x400007d4; -__fixdfdi = 0x400007d8; -__fixdfsi = 0x400007dc; -__fixsfdi = 0x400007e0; -__fixsfsi = 0x400007e4; -__fixunsdfsi = 0x400007e8; -__fixunssfdi = 0x400007ec; -__fixunssfsi = 0x400007f0; -__floatdidf = 0x400007f4; -__floatdisf = 0x400007f8; -__floatsidf = 0x400007fc; -__floatsisf = 0x40000800; -__floatundidf = 0x40000804; -__floatundisf = 0x40000808; -__floatunsidf = 0x4000080c; -__floatunsisf = 0x40000810; -__gcc_bcmp = 0x40000814; -__gedf2 = 0x40000818; -__gesf2 = 0x4000081c; -__gtdf2 = 0x40000820; -__gtsf2 = 0x40000824; -__ledf2 = 0x40000828; -__lesf2 = 0x4000082c; -__lshrdi3 = 0x40000830; -__ltdf2 = 0x40000834; -__ltsf2 = 0x40000838; -__moddi3 = 0x4000083c; -__modsi3 = 0x40000840; -__muldc3 = 0x40000844; -__muldf3 = 0x40000848; -__muldi3 = 0x4000084c; -__mulsc3 = 0x40000850; -__mulsf3 = 0x40000854; -__mulsi3 = 0x40000858; -__mulvdi3 = 0x4000085c; -__mulvsi3 = 0x40000860; -__nedf2 = 0x40000864; -__negdf2 = 0x40000868; -__negdi2 = 0x4000086c; -__negsf2 = 0x40000870; -__negvdi2 = 0x40000874; -__negvsi2 = 0x40000878; -__nesf2 = 0x4000087c; -__paritysi2 = 0x40000880; -__popcountdi2 = 0x40000884; -__popcountsi2 = 0x40000888; -__powidf2 = 0x4000088c; -__powisf2 = 0x40000890; -__subdf3 = 0x40000894; -__subsf3 = 0x40000898; -__subvdi3 = 0x4000089c; -__subvsi3 = 0x400008a0; -__truncdfsf2 = 0x400008a4; -__ucmpdi2 = 0x400008a8; -__udivdi3 = 0x400008ac; -__udivmoddi4 = 0x400008b0; -__udivsi3 = 0x400008b4; -__udiv_w_sdiv = 0x400008b8; -__umoddi3 = 0x400008bc; -__umodsi3 = 0x400008c0; -__unorddf2 = 0x400008c4; -__unordsf2 = 0x400008c8; - -/* ROM version variables for esp32c3 - * - * These addresses should be compatible with any ROM version for this chip. - * - * THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT. - */ -_rom_chip_id = 0x40000010; -_rom_eco_version = 0x40000014; diff --git a/port/espressif/esp/src/hal.zig b/port/espressif/esp/src/hal.zig index 3d281520b..378c4f405 100644 --- a/port/espressif/esp/src/hal.zig +++ b/port/espressif/esp/src/hal.zig @@ -21,6 +21,10 @@ comptime { _ = @import("hal/atomic.zig"); } +pub const HAL_Options = struct { + radio: ?radio.Options = null, +}; + /// Clock config applied by the default `init()` function of the hal. pub const clock_config: clocks.Config = .init_comptime(160_000_000); diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index aaee112d0..a55fc5bcf 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -21,7 +21,23 @@ const osi = @import("radio/osi.zig"); const timer = @import("radio/timer.zig"); const multitasking = @import("radio/multitasking.zig"); -/// You should enable interrupts after/before this. +pub const Options = struct { + wifi_interrupt: microzig.cpu.Interrupt, + timer_interrupt: microzig.cpu.Interrupt, + // TODO: we could probably do a context switch without this + yield_interrupt: microzig.cpu.Interrupt, + // TODO: support other timers and other systimer units + /// What alarm to use for preemption in systimer unit 0. + systimer_alarm: systimer.Alarm = .alarm0, +}; +pub const options = microzig.options.hal.radio orelse + @compileError("Please specify options if you want to use radio."); + +// TODO: We should allow the user to select the scheduling algorithm. We should +// pass something like the `IO` interface alongside an allocator. + +/// Radio uses the official esp drivers. You should enable interrupts +/// after/before this. pub fn init(allocator: Allocator) Allocator.Error!void { // TODO: check that clock frequency is higher or equal to 80mhz @@ -34,16 +50,17 @@ pub fn init(allocator: Allocator) Allocator.Error!void { osi.allocator = allocator; - setup_timer_periodic_alarm(); - setup_interrupts(); - // TODO: errdefer deinit try multitasking.init(allocator); - try timer.init(allocator); + + setup_timer_periodic_alarm(); + setup_interrupts(); } multitasking.yield_task(); + // try timer.init(allocator); + log.debug("initialization complete", .{}); // TODO: config @@ -52,10 +69,18 @@ pub fn init(allocator: Allocator) Allocator.Error!void { }; } -// TODO: deinit +// TODO +// should free everything +pub fn deinit() void { + +} + +pub fn tick() void { + timer.tick(); +} // TODO: maybe this can be moved in an efuse hal -pub fn read_mac() [6]u8 { +pub fn read_base_mac() [6]u8 { const EFUSE = microzig.chip.peripherals.EFUSE; var mac: [6]u8 = undefined; @@ -68,6 +93,20 @@ pub fn read_mac() [6]u8 { return mac; } +pub fn read_mac(iface: enum { + sta, + ap, + bt, +}) [6]u8 { + var mac = read_base_mac(); + switch (iface) { + .sta => {}, + .ap => mac[5] += 1, + .bt => mac[5] += 2, + } + return mac; +} + fn enable_wifi_power_domain_and_init_clocks() void { const system_wifibb_rst: u32 = 1 << 0; const system_fe_rst: u32 = 1 << 1; @@ -127,38 +166,39 @@ fn setup_interrupts() void { microzig.cpu.interrupt.set_priority_threshold(.zero); - microzig.cpu.interrupt.map(.wifi_mac, .interrupt1); - microzig.cpu.interrupt.map(.wifi_pwr, .interrupt1); - microzig.cpu.interrupt.map(.systimer_target0, .interrupt2); - microzig.cpu.interrupt.map(.from_cpu_intr0, .interrupt3); + microzig.cpu.interrupt.map(.wifi_mac, options.wifi_interrupt); + microzig.cpu.interrupt.map(.wifi_pwr, options.wifi_interrupt); + microzig.cpu.interrupt.map(.systimer_target0, options.timer_interrupt); + microzig.cpu.interrupt.map(.from_cpu_intr0, options.yield_interrupt); - inline for (&.{ .interrupt1, .interrupt2, .interrupt3 }) |int| { + inline for (&.{ options.wifi_interrupt, options.timer_interrupt, options.yield_interrupt }) |int| { microzig.cpu.interrupt.set_type(int, .level); microzig.cpu.interrupt.set_priority(int, .lowest); } - inline for (&.{ .interrupt2, .interrupt3 }) |int| { + inline for (&.{ options.timer_interrupt, options.yield_interrupt }) |int| { microzig.cpu.interrupt.enable(int); } } // TODO: config (even other timers) -const timer_alarm: systimer.Alarm = .alarm0; const preemt_interval: time.Duration = .from_ms(10); fn setup_timer_periodic_alarm() void { + const alarm = options.systimer_alarm; + // unit0 is already enabled as it is used by `hal.time`. - timer_alarm.set_unit(.unit0); + alarm.set_unit(.unit0); // sets the period to one second. - timer_alarm.set_period(@intCast(preemt_interval.to_us() * systimer.ticks_per_us())); + alarm.set_period(@intCast(preemt_interval.to_us() * systimer.ticks_per_us())); // to enable period mode you have to first clear the mode bit. - timer_alarm.set_mode(.target); - timer_alarm.set_mode(.period); + alarm.set_mode(.target); + alarm.set_mode(.period); - timer_alarm.set_interrupt_enabled(true); - timer_alarm.set_enabled(true); + alarm.set_interrupt_enabled(true); + alarm.set_enabled(true); } pub const interrupt_handlers = struct { @@ -174,8 +214,7 @@ pub const interrupt_handlers = struct { } pub fn timer(trap_frame: *TrapFrame) linksection(".ram_text") callconv(.c) void { - timer_alarm.clear_interrupt(); - + options.systimer_alarm.clear_interrupt(); multitasking.switch_task(trap_frame); } diff --git a/port/espressif/esp/src/hal/radio/global_state.zig b/port/espressif/esp/src/hal/radio/global_state.zig deleted file mode 100644 index e69de29bb..000000000 diff --git a/port/espressif/esp/src/hal/radio/multitasking.zig b/port/espressif/esp/src/hal/radio/multitasking.zig index bd8a7217b..553e6829f 100644 --- a/port/espressif/esp/src/hal/radio/multitasking.zig +++ b/port/espressif/esp/src/hal/radio/multitasking.zig @@ -56,6 +56,7 @@ var main_task: *Task = undefined; /// running. It can't be deleted. pub var current_task: *Task = undefined; +/// Must be called before the preemption timer interrupt is enabled. pub fn init(allocator: Allocator) !void { const task: *Task = try allocator.create(Task); task.* = .{ @@ -68,6 +69,9 @@ pub fn init(allocator: Allocator) !void { } pub fn schedule_task(task: *Task) void { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + task.next = current_task.next; current_task.next = task; } @@ -88,6 +92,8 @@ fn copy_context(dst: *TrapFrame, src: *const TrapFrame) void { } pub fn yield_task() void { + std.debug.assert(microzig.cpu.interrupt.globally_enabled()); + // TODO: config SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ .CPU_INTR_FROM_CPU_0 = 1, diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 32cda6a75..0fd3353ae 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const std = @import("std"); const log = std.log.scoped(.esp_radio_osi); @@ -36,57 +37,55 @@ fn syslog(fmt: ?[*:0]const u8, va_list: std.builtin.VaList) callconv(.c) void { // ----- exports ----- -pub export var WIFI_EVENT: c.esp_event_base_t = "WIFI_EVENT"; - -// pub export fn strlen(str: ?[*:0]const u8) callconv(.c) usize { -// const s = str orelse return 0; -// -// return std.mem.len(s); -// } - -pub export fn strnlen(str: ?[*:0]const u8, _: usize) callconv(.c) usize { +pub fn strlen(str: ?[*:0]const u8) callconv(.c) usize { const s = str orelse return 0; - // TODO Actually provide a complete implementation. return std.mem.len(s); } -pub export fn strrchr(_: ?[*:0]const u8, _: u32) callconv(.c) usize { - @panic("strrchr"); -} +pub fn strnlen(str: ?[*:0]const u8, _: usize) callconv(.c) usize { + // const s = str orelse return 0; + // return if (std.mem.indexOfScalar(u8, s[0..n], 0)) |index| index + 1 else n; + const s = str orelse return 0; -pub export fn strdup(_: ?[*:0]const u8) callconv(.c) ?[*:0]const u8 { - @panic("strdup"); + return std.mem.len(s); } -// pub export fn atoi(_: ?[*:0]const u8) callconv(.c) i32 { -// @panic("atoi"); -// } +pub fn strrchr(str: ?[*:0]const u8, chr: u32) callconv(.c) ?[*:0]const u8 { + const s = str orelse return null; -pub export fn strcasecmp(_: ?[*:0]const u8, _: ?[*:0]const u8) callconv(.c) i32 { - @panic("atoi"); -} + // Should return even the index of the zero byte if requested. This + // implementation only works with single byte characters. -pub export fn mktime(_: ?*const anyopaque) callconv(.c) i64 { - @panic("mktime"); + if (std.mem.lastIndexOfScalar(u8, s[0 .. std.mem.len(s) + 1], @intCast(chr))) |index| { + return @ptrFromInt(@intFromPtr(s) + index); + } else { + return null; + } } -pub export fn __assert_func( +pub fn __assert_func( file: ?[*:0]const u8, line: u32, func: ?[*:0]const u8, failed_expr: ?[*:0]const u8, -) void { - log.err("assertion `{s}` failed: file `{s}`, line {}, function `{s}`", .{ - failed_expr orelse "", - file orelse "", - line, - func orelse "", - }); - @panic("assertion failed"); +) callconv(.c) void { + switch (builtin.mode) { + .Debug, .ReleaseSafe => { + log.err("assertion failed: `{?s}` in file {?s}, line {}, function {?s}", .{ + failed_expr orelse "", + file orelse "", + line, + func orelse "", + }); + @panic("assertion failed"); + }, + .ReleaseSmall => @panic("assertion failed"), + .ReleaseFast => unreachable, + } } -pub export fn malloc(len: usize) callconv(.c) ?*anyopaque { +pub fn malloc(len: usize) callconv(.c) ?*anyopaque { log.debug("malloc {}", .{len}); const buf = allocator.rawAlloc(@sizeOf(usize) + len, .@"4", @returnAddress()) orelse { @@ -99,7 +98,7 @@ pub export fn malloc(len: usize) callconv(.c) ?*anyopaque { return @ptrFromInt(@intFromPtr(buf) + @sizeOf(usize)); } -pub export fn calloc(number: usize, size: usize) callconv(.c) ?*anyopaque { +pub fn calloc(number: usize, size: usize) callconv(.c) ?*anyopaque { const total_size: usize = number * size; if (malloc(total_size)) |ptr| { @memset(@as([*]u8, @ptrCast(ptr))[0..total_size], 0); @@ -108,7 +107,7 @@ pub export fn calloc(number: usize, size: usize) callconv(.c) ?*anyopaque { return null; } -pub export fn free(ptr: ?*anyopaque) callconv(.c) void { +pub fn free(ptr: ?*anyopaque) callconv(.c) void { log.debug("free {?}", .{ptr}); std.debug.assert(ptr != null); @@ -118,42 +117,52 @@ pub export fn free(ptr: ?*anyopaque) callconv(.c) void { allocator.rawFree(buf_ptr[0 .. @sizeOf(usize) + buf_len.*], .@"4", @returnAddress()); } -pub export fn esp_wifi_free_internal_heap(_: ?*anyopaque) callconv(.c) void { - @panic("esp_wifi_free_internal_heap"); +pub fn puts(ptr: ?*anyopaque) callconv(.c) void { + const s: []const u8 = std.mem.span(@as([*:0]const u8, @ptrCast(ptr))); + log_esp_wifi_driver_internal.debug("{s}", .{s}); } -pub export fn esp_wifi_allocate_from_internal_ram() callconv(.c) void { - @panic("esp_wifi_allocate_from_internal_ram"); +pub fn gettimeofday(tv: ?*c.timeval, _: ?*anyopaque) callconv(.c) i32 { + if (tv) |time_val| { + const usec = hal.time.get_time_since_boot().to_us(); + time_val.tv_sec = usec / 1_000_000; + time_val.tv_usec = @intCast(usec % 1_000_000); + } + + return 0; } -pub export fn esp_wifi_deallocate_from_internal_ram() callconv(.c) void { - @panic("esp_wifi_deallocate_from_internal_ram"); +pub fn sleep(time_sec: c_uint) callconv(.c) c_int { + task_delay(time_sec * 1_000_000); + return 0; } -pub export fn _putchar(byte: u8) callconv(.c) void { - // NOTE: not interrupt safe +pub fn usleep(time_us: u32) callconv(.c) c_int { + task_delay(time_us); + return 0; +} - const static = struct { - var buf: [256]u8 = undefined; - var bytes_written: usize = 0; - }; +comptime { + // provide some weak links so they can be overriten - static.buf[static.bytes_written] = byte; - static.bytes_written += 1; + @export(&strlen, .{ .name = "strlen", .linkage = .weak }); + @export(&strnlen, .{ .name = "strnlen", .linkage = .weak }); + @export(&strrchr, .{ .name = "strrchr", .linkage = .weak }); - if (static.bytes_written >= 256) { - log_esp_wifi_driver_internal.debug("_putchar: {s}", .{&static.buf}); - static.bytes_written = 0; - } -} + @export(&__assert_func, .{ .name = "__assert_func", .linkage = .weak }); -pub export fn puts(ptr: ?*anyopaque) callconv(.c) void { - // NOTE: not interrupt safe + @export(&malloc, .{ .name = "malloc", .linkage = .weak }); + @export(&calloc, .{ .name = "calloc", .linkage = .weak }); + @export(&free, .{ .name = "free", .linkage = .weak }); + @export(&puts, .{ .name = "puts", .linkage = .weak }); - const s: []const u8 = std.mem.span(@as([*:0]const u8, @ptrCast(ptr))); - log_esp_wifi_driver_internal.debug("{s}", .{s}); + @export(&gettimeofday, .{ .name = "gettimeofday", .linkage = .weak }); + @export(&sleep, .{ .name = "sleep", .linkage = .weak }); + @export(&usleep, .{ .name = "usleep", .linkage = .weak }); } +pub export var WIFI_EVENT: c.esp_event_base_t = "WIFI_EVENT"; + pub export fn rtc_printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { syslog(fmt, @cVaStart()); } @@ -174,63 +183,12 @@ pub export fn pp_printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { syslog(fmt, @cVaStart()); } -pub export fn gettimeofday(tv: ?*c.timeval, _: ?*anyopaque) i32 { - if (tv) |time_val| { - const usec = hal.time.get_time_since_boot().to_us(); - time_val.tv_sec = usec / 1_000_000; - time_val.tv_usec = @intCast(usec % 1_000_000); - } - - return 0; -} - -pub export fn sleep(time_sec: c_uint) callconv(.c) c_int { - hal.rom.delay_us(time_sec * 1_000_000); - return 0; -} - -pub export fn usleep(time_us: u32) callconv(.c) c_int { - hal.rom.delay_us(time_us); - return 0; -} - pub export fn esp_fill_random(buf: [*c]u8, len: usize) callconv(.c) void { log.debug("esp_fill_random {any} {}", .{ buf, len }); hal.rng.read(buf[0..len]); } -comptime { - _ = WIFI_EVENT; - - // _ = strlen; - _ = strnlen; - _ = strrchr; - _ = strdup; - // _ = atoi; - _ = strcasecmp; - _ = mktime; - _ = __assert_func; - _ = malloc; - _ = calloc; - _ = free; - _ = esp_wifi_free_internal_heap; - _ = esp_wifi_allocate_from_internal_ram; - _ = esp_wifi_deallocate_from_internal_ram; - _ = _putchar; - _ = puts; - _ = rtc_printf; - _ = phy_printf; - _ = coexist_printf; - _ = net80211_printf; - _ = pp_printf; - _ = gettimeofday; - _ = sleep; - _ = usleep; - _ = esp_fill_random; - _ = rand; -} - // ----- end of exports ----- pub fn env_is_chip() callconv(.c) bool { @@ -281,11 +239,12 @@ pub fn set_isr( } } -// TODO: I don't think we need to do anything here. +// TODO pub fn ints_on(mask: u32) callconv(.c) void { log.debug("ints_on {}", .{mask}); } +// TODO pub fn ints_off(mask: u32) callconv(.c) void { std.debug.panic("ints_off {}", .{mask}); } @@ -498,6 +457,7 @@ pub fn mutex_unlock(ptr: ?*anyopaque) callconv(.c) i32 { } } +// TODO: maybe use atomics? maybe it is spsc? pub const Queue = struct { len: usize = 0, capacity: usize, @@ -696,13 +656,7 @@ fn task_create_common( log.warn("failed to create task", .{}); return 0; }; - - { - const cs = enter_critical_section(); - cs.leave(); - - multitasking.schedule_task(task); - } + multitasking.schedule_task(task); @as(*usize, @alignCast(@ptrCast(task_handle))).* = @intFromPtr(task); @@ -780,31 +734,11 @@ pub fn task_get_max_priority() callconv(.c) i32 { return -1; } -pub fn event_post( - base: [*c]const u8, - id: i32, - data: ?*anyopaque, - data_size: usize, - ticks_to_wait: u32, -) callconv(.c) i32 { - _ = base; // autofix - _ = data; // autofix - _ = data_size; // autofix - _ = ticks_to_wait; // autofix - - const event: wifi.Event = @enumFromInt(id); - log.info("received event: {s}", .{@tagName(event)}); - - wifi.update_sta_state(event); - - return 0; -} - pub fn get_free_heap_size() callconv(.c) void { @panic("get_free_heap_size: not implemented"); } -pub export fn rand() callconv(.c) u32 { +pub fn rand() callconv(.c) u32 { return hal.rng.random_u32(); } @@ -1005,41 +939,14 @@ pub fn phy_update_country_info(country: [*c]const u8) callconv(.c) c_int { pub fn read_mac(mac: [*c]u8, typ: c_uint) callconv(.c) c_int { log.debug("read_mac {*} {}", .{ mac, typ }); - const mac_tmp: [6]u8 = hal.radio.read_mac(); + const mac_tmp: [6]u8 = hal.radio.read_mac(switch (typ) { + 0 => .sta, + 1 => .ap, + 2 => .bt, + else => @panic("unknown mac typ"), + }); @memcpy(mac[0..6], &mac_tmp); - // idk what this means - switch (typ) { - // esp_mac_wifi_softap - 1 => { - const tmp = mac[0]; - var i: u6 = 0; - while (i < 64) : (i += 1) { - mac[0] |= 0x02; - mac[0] ^= @intCast(i << 2); - - if (mac[0] != tmp) { - break; - } - } - }, - // esp_mac_bt - 2 => { - const tmp = mac[0]; - var i: u6 = 0; - while (i < 64) : (i += 1) { - mac[0] |= 0x02; - mac[0] ^= @intCast(i << 2); - - if (mac[0] != tmp) { - break; - } - } - mac[5] += 1; - }, - else => {}, - } - return 0; } @@ -1052,7 +959,6 @@ pub fn timer_disarm(ets_timer_ptr: ?*anyopaque) callconv(.c) void { const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); - // TODO: locking const cs = enter_critical_section(); defer cs.leave(); @@ -1068,7 +974,6 @@ pub fn timer_done(ets_timer_ptr: ?*anyopaque) callconv(.c) void { const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); - // TODO: locking const cs = enter_critical_section(); defer cs.leave(); @@ -1087,7 +992,6 @@ pub fn timer_setfn(ets_timer_ptr: ?*anyopaque, callback_ptr: ?*anyopaque, arg: ? const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); const callback: timer.CallbackFn = @alignCast(@ptrCast(callback_ptr)); - // TODO: locking const cs = enter_critical_section(); defer cs.leave(); @@ -1265,11 +1169,12 @@ pub fn wifi_realloc() callconv(.c) void { pub const wifi_calloc = calloc; pub const wifi_zalloc = zalloc_internal; -var wifi_queue_handle: *Queue = undefined; +var wifi_queue_handle: ?*Queue = null; pub fn wifi_create_queue(capacity: c_int, item_len: c_int) callconv(.c) ?*anyopaque { log.debug("wifi_create_queue {} {}", .{ capacity, item_len }); + std.debug.assert(wifi_queue_handle == null); wifi_queue_handle = Queue.create(@intCast(capacity), @intCast(item_len)) catch { log.warn("failed to allocate wifi queue", .{}); return null; @@ -1283,12 +1188,12 @@ pub fn wifi_create_queue(capacity: c_int, item_len: c_int) callconv(.c) ?*anyopa pub fn wifi_delete_queue(ptr: ?*anyopaque) callconv(.c) void { log.debug("wifi_delete_queue {?}", .{ptr}); - // if (wifi_queue_handle != ptr) { - // @panic("unknown queue when trying to delete wifi queue"); - // } + std.debug.assert(ptr == @as(?*anyopaque, @ptrCast(wifi_queue_handle))); const queue: *Queue = @alignCast(@ptrCast(ptr)); queue.destroy(); + + wifi_queue_handle = null; } pub fn coex_init() callconv(.c) c_int { diff --git a/port/espressif/esp/src/hal/radio/timer.zig b/port/espressif/esp/src/hal/radio/timer.zig index b75f170d0..b50861b94 100644 --- a/port/espressif/esp/src/hal/radio/timer.zig +++ b/port/espressif/esp/src/hal/radio/timer.zig @@ -24,10 +24,10 @@ const TimerListNode = TimerList.Node; var timer_list: TimerList = .{}; -pub fn init(allocator: Allocator) !void { - const task = try multitasking.Task.create(allocator, timer_task, null, 8192); - multitasking.schedule_task(task); -} +// pub fn init(allocator: Allocator) !void { +// const task = try multitasking.Task.create(allocator, timer_task, null, 8192); +// multitasking.schedule_task(task); +// } pub fn add(allocator: Allocator, ets_timer: *c.ets_timer, callback: CallbackFn, arg: ?*anyopaque) !void { const timer: Timer = .{ @@ -47,11 +47,9 @@ pub fn add(allocator: Allocator, ets_timer: *c.ets_timer, callback: CallbackFn, } pub fn remove(allocator: Allocator, timer: *Timer) void { - _ = allocator; // autofix - _ = timer; // autofix - // const node: *TimerListNode = @fieldParentPtr("data", timer); - // timer_list.remove(node); - // allocator.destroy(node); + const node: *TimerListNode = @fieldParentPtr("data", timer); + timer_list.remove(node); + allocator.destroy(node); } pub fn find(ets_timer: *c.ets_timer) ?*Timer { @@ -65,18 +63,7 @@ pub fn find(ets_timer: *c.ets_timer) ?*Timer { return null; } -fn find_next_due(now: time.Absolute) ?*Timer { - var current_node = timer_list.first; - while (current_node) |node| : (current_node = node.next) { - const timer = &node.data; - if (timer.deadline.is_reached_by(now)) { - return timer; - } - } - return null; -} - -fn timer_task(_: ?*anyopaque) callconv(.c) noreturn { +pub fn tick() void { while (true) { const now = hal.time.get_time_since_boot(); @@ -100,7 +87,21 @@ fn timer_task(_: ?*anyopaque) callconv(.c) noreturn { if (maybe_call) |callback| { callback(arg); } else { - multitasking.yield_task(); + break; } } } + +fn find_next_due(now: time.Absolute) ?*Timer { + var current_node = timer_list.first; + while (current_node) |node| : (current_node = node.next) { + const timer = &node.data; + if (timer.deadline.is_reached_by(now)) { + return timer; + } + } + return null; +} + +// fn timer_task(_: ?*anyopaque) callconv(.c) noreturn { +// } diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index cf956c3be..88c64e098 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -63,93 +63,140 @@ pub fn deinit() void { inited = false; } -pub const SetConfigError = InternalError || error{ +pub const ApplyConfigError = InternalError || error{ InvalidConfig, }; -pub const ClientConfig = struct { - /// The SSID of the Wi-Fi network. - ssid: []const u8, +pub const Config = union(enum) { + ap: AccessPoint, + sta: Station, + ap_sta: struct { + ap: AccessPoint, + sta: Station, + }, + // TODO: eap_sta - /// The BSSID (MAC address) of the client. - bssid: ?[6]u8 = null, + pub const AccessPoint = struct { + /// The SSID of the access point. + ssid: []const u8, - /// Authentication config for the Wi-Fi connection. - auth: ?AuthConfig = null, + /// Whether the SSID is hidden or visible. + ssid_hidden: bool, - /// The Wi-Fi channel to connect to. - channel: u8 = 0, + /// The channel the access point will operate on. Ignored in `ap_sta` + /// mode. + channel: u8, - scan_method: ScanMethod = .fast, + /// The secondary channel configuration. + secondary_channel: ?u8, - listen_interval: u16 = 3, + /// Authentication config to be used by the access point. + auth: ?Auth, - failure_retry_cnt: u8 = 1, -}; + /// The maximum number of connections allowed on the access point. + max_connections: u8, + }; -pub fn set_client_config(config: ClientConfig) SetConfigError!void { - if (config.ssid.len > 32) { - return error.InvalidConfig; - } + pub const Station = struct { + /// The SSID of the Wi-Fi network. + ssid: []const u8, - if (config.auth) |auth| { - if (auth.password.len > 64) { - return error.InvalidConfig; - } - } + /// The BSSID (MAC address) of the client. + bssid: ?[6]u8 = null, - var sta_cfg: wifi_sta_config_t = .{ - .scan_method = @intFromEnum(config.scan_method), - .bssid_set = config.bssid != null, - .bssid = config.bssid orelse @splat(0), - .channel = config.channel, - // TODO: config - .listen_interval = config.listen_interval, - .sort_method = c.WIFI_CONNECT_AP_BY_SIGNAL, - .threshold = .{ - .rssi = -99, - .authmode = if (config.auth) |auth| @intFromEnum(auth.method) else c.WIFI_AUTH_OPEN, - }, - .pmf_cfg = .{ - .capable = true, - .required = false, - }, - .failure_retry_cnt = config.failure_retry_cnt, + /// Authentication config for the Wi-Fi connection. + auth: ?Auth = null, + + /// The Wi-Fi channel to connect to. + channel: u8 = 0, + + scan_method: ScanMethod = .fast, + + listen_interval: u16 = 3, + + failure_retry_cnt: u8 = 1, + + pub const ScanMethod = enum(u32) { + fast = c.WIFI_FAST_SCAN, + all_channel = c.WIFI_ALL_CHANNEL_SCAN, + }; }; - @memcpy(sta_cfg.ssid[0..config.ssid.len], config.ssid); - if (config.auth) |auth| { - @memcpy(sta_cfg.password[0..auth.password.len], auth.password); - } + pub const Auth = struct { + password: []const u8, + method: Method, - var tmp: wifi_config_t = .{ .sta = sta_cfg }; - try c_result(esp_wifi_set_config(c.WIFI_IF_STA, &tmp)); -} + pub const Method = enum(u32) { + /// Wired Equivalent Privacy (WEP) authentication. + wep = c.WIFI_AUTH_WEP, -pub const AccessPointConfig = struct { - /// The SSID of the access point. - ssid: []const u8, + /// Wi-Fi Protected Access (WPA) authentication. + wpa = c.WIFI_AUTH_WPA_PSK, - /// Whether the SSID is hidden or visible. - ssid_hidden: bool, + /// Wi-Fi Protected Access 2 (WPA2) Personal authentication (default). + wpa2_personal = c.WIFI_AUTH_WPA2_PSK, - /// The channel the access point will operate on. - channel: u8, + /// WPA/WPA2 Personal authentication (supports both). + wpa_wpa2_personal = c.WIFI_AUTH_WPA_WPA2_PSK, - /// The secondary channel configuration. - secondary_channel: ?u8, + /// WPA2 Enterprise authentication. + wpa2_enterprise = c.WIFI_AUTH_WPA2_ENTERPRISE, - /// The set of protocols supported by the access point. - protocols: []const Protocol, + /// WPA3 Personal authentication. + wpa3_personal = c.WIFI_AUTH_WPA3_PSK, - /// Authentication config to be used by the access point. - auth: ?AuthConfig, + /// WPA2/WPA3 Personal authentication (supports both). + wpa2_wpa3_personal = c.WIFI_AUTH_WPA2_WPA3_PSK, - /// The maximum number of connections allowed on the access point. - max_connections: u8, + /// WLAN Authentication and Privacy Infrastructure (WAPI). + wapi_personal = c.WIFI_AUTH_WAPI_PSK, + }; + }; }; -pub fn set_access_point_config(config: AccessPointConfig) SetConfigError!void { +pub fn apply(config: Config) ApplyConfigError!void { + switch (config) { + .ap => |ap_config| { + try set_mode(.ap); + try apply_access_point_config(ap_config); + }, + .sta => |sta_config| { + try set_mode(.sta); + try apply_station_config(sta_config); + }, + .ap_sta => |ap_sta_config| { + try set_mode(.ap_sta); + try apply_access_point_config(ap_sta_config.ap); + try apply_station_config(ap_sta_config.sta); + }, + } +} + +pub const WifiMode = enum(u32) { + sta = c.WIFI_MODE_STA, + ap = c.WIFI_MODE_AP, + ap_sta = c.WIFI_MODE_APSTA, + + pub fn is_sta(self: WifiMode) bool { + return self == .sta or self == .ap_sta; + } + + pub fn is_ap(self: WifiMode) bool { + return self == .ap or self == .ap_sta; + } +}; + +pub fn get_mode() InternalError!WifiMode { + var mode: c.wifi_mode_t = undefined; + try c_result(c.esp_wifi_get_mode(&mode)); + return @enumFromInt(mode); +} + +fn set_mode(mode: WifiMode) InternalError!void { + try c_result(c.esp_wifi_set_mode(@intFromEnum(mode))); +} + +fn apply_access_point_config(config: Config.AccessPoint) ApplyConfigError!void { if (config.ssid.len > 32) { return error.InvalidConfig; } @@ -160,7 +207,7 @@ pub fn set_access_point_config(config: AccessPointConfig) SetConfigError!void { } } - var ap_cfg: wifi_ap_config_t = .{ + var ap_cfg: c_patched.wifi_ap_config_t = .{ .ssid_len = @intCast(config.ssid.len), .channel = config.channel, .authmode = if (config.auth) |auth| @intFromEnum(auth.method) else c.WIFI_AUTH_OPEN, @@ -183,45 +230,47 @@ pub fn set_access_point_config(config: AccessPointConfig) SetConfigError!void { @memcpy(ap_cfg.password[0..auth.password.len], auth.password); } - var tmp: wifi_config_t = .{ .ap = ap_cfg }; - try c_result(esp_wifi_set_config(c.WIFI_IF_AP, &tmp)); + var tmp: c_patched.wifi_config_t = .{ .ap = ap_cfg }; + try c_result(c_patched.esp_wifi_set_config(c.WIFI_IF_AP, &tmp)); } -pub const AuthConfig = struct { - password: []const u8, - method: Method, - - pub const Method = enum(u32) { - /// Wired Equivalent Privacy (WEP) authentication. - wep = c.WIFI_AUTH_WEP, - - /// Wi-Fi Protected Access (WPA) authentication. - wpa = c.WIFI_AUTH_WPA_PSK, - - /// Wi-Fi Protected Access 2 (WPA2) Personal authentication (default). - wpa2_personal = c.WIFI_AUTH_WPA2_PSK, - - /// WPA/WPA2 Personal authentication (supports both). - wpa_wpa2_personal = c.WIFI_AUTH_WPA_WPA2_PSK, - - /// WPA2 Enterprise authentication. - wpa2_enterprise = c.WIFI_AUTH_WPA2_ENTERPRISE, - - /// WPA3 Personal authentication. - wpa3_personal = c.WIFI_AUTH_WPA3_PSK, +fn apply_station_config(config: Config.Station) ApplyConfigError!void { + if (config.ssid.len > 32) { + return error.InvalidConfig; + } - /// WPA2/WPA3 Personal authentication (supports both). - wpa2_wpa3_personal = c.WIFI_AUTH_WPA2_WPA3_PSK, + if (config.auth) |auth| { + if (auth.password.len > 64) { + return error.InvalidConfig; + } + } - /// WLAN Authentication and Privacy Infrastructure (WAPI). - wapi_personal = c.WIFI_AUTH_WAPI_PSK, + var sta_cfg: c_patched.wifi_sta_config_t = .{ + .scan_method = @intFromEnum(config.scan_method), + .bssid_set = config.bssid != null, + .bssid = config.bssid orelse @splat(0), + .channel = config.channel, + .listen_interval = config.listen_interval, + .sort_method = c.WIFI_CONNECT_AP_BY_SIGNAL, + .threshold = .{ + .rssi = -99, + .authmode = if (config.auth) |auth| @intFromEnum(auth.method) else c.WIFI_AUTH_OPEN, + }, + .pmf_cfg = .{ + .capable = true, + .required = false, + }, + .failure_retry_cnt = config.failure_retry_cnt, }; -}; -pub const ScanMethod = enum(u32) { - fast = c.WIFI_FAST_SCAN, - all_channel = c.WIFI_ALL_CHANNEL_SCAN, -}; + @memcpy(sta_cfg.ssid[0..config.ssid.len], config.ssid); + if (config.auth) |auth| { + @memcpy(sta_cfg.password[0..auth.password.len], auth.password); + } + + var tmp: c_patched.wifi_config_t = .{ .sta = sta_cfg }; + try c_result(c_patched.esp_wifi_set_config(c.WIFI_IF_STA, &tmp)); +} pub const PowerSaveMode = enum(u32) { none = c.WIFI_PS_NONE, @@ -233,22 +282,6 @@ pub fn set_power_save_mode(mode: PowerSaveMode) InternalError!void { try c_result(c.esp_wifi_set_ps(@intFromEnum(mode))); } -pub const WifiMode = enum(u32) { - sta = c.WIFI_MODE_STA, - ap = c.WIFI_MODE_AP, - ap_sta = c.WIFI_MODE_APSTA, -}; - -pub fn get_mode() InternalError!WifiMode { - var mode: c.wifi_mode_t = undefined; - try c_result(c.esp_wifi_get_mode(&mode)); - return @enumFromInt(mode); -} - -pub fn set_mode(mode: WifiMode) InternalError!void { - try c_result(c.esp_wifi_set_mode(@intFromEnum(mode))); -} - pub const Protocol = enum(u8) { /// 802.11b protocol. P802D11B = c.WIFI_PROTOCOL_11B, @@ -269,24 +302,18 @@ pub const Protocol = enum(u8) { P802D11BGNAX = c.WIFI_PROTOCOL_11B | c.WIFI_PROTOCOL_11G | c.WIFI_PROTOCOL_11N | c.WIFI_PROTOCOL_11AX, }; -fn set_protocol(protocols: []const Protocol) InternalError!void { +pub fn set_protocol(protocols: []const Config.AccessPoint.Protocol) InternalError!void { var combined: u8 = 0; for (protocols) |protocol| { combined |= @intFromEnum(protocol); } const mode = try get_mode(); - switch (mode) { - .sta => { - try c_result(c.esp_wifi_set_protocol(c.WIFI_IF_STA, combined)); - }, - .ap => { - try c_result(c.esp_wifi_set_protocol(c.WIFI_IF_AP, combined)); - }, - .ap_sta => { - try c_result(c.esp_wifi_set_protocol(c.WIFI_IF_STA, combined)); - try c_result(c.esp_wifi_set_protocol(c.WIFI_IF_AP, combined)); - }, + if (mode.is_sta()) { + try c_result(c.esp_wifi_set_protocol(c.WIFI_IF_STA, combined)); + } + if (mode.is_ap()) { + try c_result(c.esp_wifi_set_protocol(c.WIFI_IF_AP, combined)); } } @@ -294,23 +321,17 @@ pub fn start() InternalError!void { try c_result(c.esp_wifi_start()); } +pub fn stop() InternalError!void { + try c_result(c.esp_wifi_stop()); +} + pub fn connect() InternalError!void { try c_result(c.esp_wifi_connect()); } -pub const Interface = enum(u32) { - sta = c.WIFI_IF_STA, - ap = c.WIFI_IF_AP, - - pub fn is_active(self: Interface) InternalError!bool { - const mode = try get_mode(); - if (mode == .ap_sta) return true; - return switch (self) { - .sta => mode == .sta, - .ap => mode == .ap, - }; - } -}; +pub fn disconnect() InternalError!void { + try c_result(c.esp_wifi_disconnect()); +} pub const Event = enum(i32) { /// Wi-Fi is ready for operation. @@ -415,7 +436,27 @@ pub const Event = enum(i32) { StaNeighborRep, }; -const StaState = enum { +fn event_post( + base: [*c]const u8, + id: i32, + data: ?*anyopaque, + data_size: usize, + ticks_to_wait: u32, +) callconv(.c) i32 { + _ = base; // autofix + _ = data; // autofix + _ = data_size; // autofix + _ = ticks_to_wait; // autofix + + const event: Event = @enumFromInt(id); + log.info("received event: {s}", .{@tagName(event)}); + + update_sta_state(event); + + return 0; +} + +pub const StaState = enum { none, sta_started, sta_connected, @@ -425,8 +466,7 @@ const StaState = enum { var sta_state: StaState = .none; -/// Internal method called on each event post. -pub fn update_sta_state(event: Event) void { +fn update_sta_state(event: Event) void { const new_sta_state: StaState = switch (event) { .StaStart => .sta_started, .StaConnected => .sta_connected, @@ -443,6 +483,19 @@ pub fn get_sta_state() StaState { // TODO: ApState +pub const Interface = enum(u32) { + sta = c.WIFI_IF_STA, + ap = c.WIFI_IF_AP, + + pub fn is_active(self: Interface) InternalError!bool { + const mode = try get_mode(); + return switch (self) { + .sta => mode.is_sta(), + .ap => mode.is_ap(), + }; + } +}; + var packets_in_flight: usize = 0; pub fn send_packet(iface: Interface, data: []const u8) (error{TooManyPacketsInFlight} || InternalError)!void { @@ -471,46 +524,46 @@ fn tx_done_cb( } /// Every packet buffer must be deinited by the user in the callback. -pub const PacketBuffer = struct { +pub const ReceivedPacket = struct { data: []const u8, eb: ?*anyopaque, - pub fn deinit(self: PacketBuffer) void { + pub fn deinit(self: ReceivedPacket) void { c.esp_wifi_internal_free_rx_buffer(self.eb); } }; -pub const RxCallback = *const fn (pb: PacketBuffer) void; +pub const RxCallback = *const fn (packet: ReceivedPacket) void; var packet_rx_sta_callback: ?RxCallback = null; var packet_rx_ap_callback: ?RxCallback = null; pub fn set_packet_rx_sta_callback(cb: RxCallback) void { - packet_rx_sta_callback = cb; + @atomicStore(?RxCallback, &packet_rx_sta_callback, cb, .unordered); } fn recv_cb_sta(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_err_t { log.debug("recv_cb_sta {?} {} {?}", .{ buf, len, eb }); - const pb: PacketBuffer = .{ + const packet: ReceivedPacket = .{ .data = @as([*]const u8, @ptrCast(buf))[0..len], .eb = eb, }; - if (packet_rx_sta_callback) |cb| { - cb(pb); + if (@atomicLoad(?RxCallback, &packet_rx_sta_callback, .unordered)) |cb| { + cb(packet); } return c.ESP_OK; } fn recv_cb_ap(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_err_t { - const pb: PacketBuffer = .{ + const pb: ReceivedPacket = .{ .data = @as([*]const u8, @ptrCast(buf))[0..len], .eb = eb, }; - if (packet_rx_ap_callback) |cb| { + if (@atomicLoad(?RxCallback, &packet_rx_ap_callback, .unordered)) |cb| { cb(pb); } @@ -603,7 +656,7 @@ export var g_wifi_osi_funcs: c.wifi_osi_funcs_t = .{ ._task_get_max_priority = osi.task_get_max_priority, ._malloc = osi.malloc, ._free = osi.free, - ._event_post = osi.event_post, + ._event_post = event_post, ._get_free_heap_size = @ptrCast(&osi.get_free_heap_size), ._rand = osi.rand, ._dport_access_stall_other_cpu_start_wrap = osi.dport_access_stall_other_cpu_start_wrap, @@ -755,76 +808,78 @@ pub fn c_result(err_code: i32) InternalError!void { } } -pub const wifi_ap_config_t = extern struct { - ssid: [32]u8 = std.mem.zeroes([32]u8), - password: [64]u8 = std.mem.zeroes([64]u8), - ssid_len: u8 = std.mem.zeroes(u8), - channel: u8 = std.mem.zeroes(u8), - authmode: c.wifi_auth_mode_t = std.mem.zeroes(c.wifi_auth_mode_t), - ssid_hidden: u8 = std.mem.zeroes(u8), - max_connection: u8 = std.mem.zeroes(u8), - beacon_interval: u16 = std.mem.zeroes(u16), - csa_count: u8 = std.mem.zeroes(u8), - dtim_period: u8 = std.mem.zeroes(u8), - pairwise_cipher: c.wifi_cipher_type_t = std.mem.zeroes(c.wifi_cipher_type_t), - ftm_responder: bool = std.mem.zeroes(bool), - pmf_cfg: c.wifi_pmf_config_t = std.mem.zeroes(c.wifi_pmf_config_t), - sae_pwe_h2e: c.wifi_sae_pwe_method_t = std.mem.zeroes(c.wifi_sae_pwe_method_t), -}; - -pub const wifi_sta_config_t = extern struct { - // NOTE: maybe a little more imagination - pub const Packed1 = packed struct { - rm_enabled: bool, - btm_enabled: bool, - mbo_enabled: bool, - ft_enabled: bool, - owe_enabled: bool, - transition_disable: bool, - reserved: u26, +pub const c_patched = struct { + pub const wifi_ap_config_t = extern struct { + ssid: [32]u8 = std.mem.zeroes([32]u8), + password: [64]u8 = std.mem.zeroes([64]u8), + ssid_len: u8 = std.mem.zeroes(u8), + channel: u8 = std.mem.zeroes(u8), + authmode: c.wifi_auth_mode_t = std.mem.zeroes(c.wifi_auth_mode_t), + ssid_hidden: u8 = std.mem.zeroes(u8), + max_connection: u8 = std.mem.zeroes(u8), + beacon_interval: u16 = std.mem.zeroes(u16), + csa_count: u8 = std.mem.zeroes(u8), + dtim_period: u8 = std.mem.zeroes(u8), + pairwise_cipher: c.wifi_cipher_type_t = std.mem.zeroes(c.wifi_cipher_type_t), + ftm_responder: bool = std.mem.zeroes(bool), + pmf_cfg: c.wifi_pmf_config_t = std.mem.zeroes(c.wifi_pmf_config_t), + sae_pwe_h2e: c.wifi_sae_pwe_method_t = std.mem.zeroes(c.wifi_sae_pwe_method_t), }; - pub const Packed2 = packed struct { - he_dcm_set: u1, - he_dcm_max_constellation_tx: u2, - he_dcm_max_constellation_rx: u2, - he_mcs9_enabled: u1, - he_su_beamformee_disabled: u1, - he_trig_su_bmforming_feedback_disabled: u1, - he_trig_mu_bmforming_partial_feedback_disabled: u1, - he_trig_cqi_feedback_disable: u1, - he_reserved: u22, + pub const wifi_sta_config_t = extern struct { + // NOTE: maybe a little more imagination + pub const Packed1 = packed struct { + rm_enabled: bool, + btm_enabled: bool, + mbo_enabled: bool, + ft_enabled: bool, + owe_enabled: bool, + transition_disable: bool, + reserved: u26, + }; + + pub const Packed2 = packed struct { + he_dcm_set: u1, + he_dcm_max_constellation_tx: u2, + he_dcm_max_constellation_rx: u2, + he_mcs9_enabled: u1, + he_su_beamformee_disabled: u1, + he_trig_su_bmforming_feedback_disabled: u1, + he_trig_mu_bmforming_partial_feedback_disabled: u1, + he_trig_cqi_feedback_disable: u1, + he_reserved: u22, + }; + + ssid: [32]u8 = std.mem.zeroes([32]u8), + password: [64]u8 = std.mem.zeroes([64]u8), + scan_method: c.wifi_scan_method_t = std.mem.zeroes(c.wifi_scan_method_t), + bssid_set: bool = std.mem.zeroes(bool), + bssid: [6]u8 = std.mem.zeroes([6]u8), + channel: u8 = std.mem.zeroes(u8), + listen_interval: u16 = std.mem.zeroes(u16), + sort_method: c.wifi_sort_method_t = std.mem.zeroes(c.wifi_sort_method_t), + threshold: c.wifi_scan_threshold_t = std.mem.zeroes(c.wifi_scan_threshold_t), + pmf_cfg: c.wifi_pmf_config_t = std.mem.zeroes(c.wifi_pmf_config_t), + packed1: Packed1 = std.mem.zeroes(Packed1), + sae_pwe_h2e: c.wifi_sae_pwe_method_t = std.mem.zeroes(c.wifi_sae_pwe_method_t), + sae_pk_mode: c.wifi_sae_pk_mode_t = std.mem.zeroes(c.wifi_sae_pk_mode_t), + failure_retry_cnt: u8 = std.mem.zeroes(u8), + packed2: Packed2 = std.mem.zeroes(Packed2), + sae_h2e_identifier: [32]u8 = std.mem.zeroes([32]u8), }; - ssid: [32]u8 = std.mem.zeroes([32]u8), - password: [64]u8 = std.mem.zeroes([64]u8), - scan_method: c.wifi_scan_method_t = std.mem.zeroes(c.wifi_scan_method_t), - bssid_set: bool = std.mem.zeroes(bool), - bssid: [6]u8 = std.mem.zeroes([6]u8), - channel: u8 = std.mem.zeroes(u8), - listen_interval: u16 = std.mem.zeroes(u16), - sort_method: c.wifi_sort_method_t = std.mem.zeroes(c.wifi_sort_method_t), - threshold: c.wifi_scan_threshold_t = std.mem.zeroes(c.wifi_scan_threshold_t), - pmf_cfg: c.wifi_pmf_config_t = std.mem.zeroes(c.wifi_pmf_config_t), - packed1: Packed1 = std.mem.zeroes(Packed1), - sae_pwe_h2e: c.wifi_sae_pwe_method_t = std.mem.zeroes(c.wifi_sae_pwe_method_t), - sae_pk_mode: c.wifi_sae_pk_mode_t = std.mem.zeroes(c.wifi_sae_pk_mode_t), - failure_retry_cnt: u8 = std.mem.zeroes(u8), - packed2: Packed2 = std.mem.zeroes(Packed2), - sae_h2e_identifier: [32]u8 = std.mem.zeroes([32]u8), -}; + pub const wifi_nan_config_t = extern struct { + op_channel: u8 = std.mem.zeroes(u8), + master_pref: u8 = std.mem.zeroes(u8), + scan_time: u8 = std.mem.zeroes(u8), + warm_up_sec: u16 = std.mem.zeroes(u16), + }; -pub const wifi_nan_config_t = extern struct { - op_channel: u8 = std.mem.zeroes(u8), - master_pref: u8 = std.mem.zeroes(u8), - scan_time: u8 = std.mem.zeroes(u8), - warm_up_sec: u16 = std.mem.zeroes(u16), -}; + pub const wifi_config_t = extern union { + ap: wifi_ap_config_t, + sta: wifi_sta_config_t, + nan: wifi_nan_config_t, + }; -pub const wifi_config_t = extern union { - ap: wifi_ap_config_t, - sta: wifi_sta_config_t, - nan: wifi_nan_config_t, + extern fn esp_wifi_set_config(interface: c.wifi_interface_t, conf: ?*wifi_config_t) c.esp_err_t; }; - -extern fn esp_wifi_set_config(interface: c.wifi_interface_t, conf: ?*wifi_config_t) c.esp_err_t; From cf54bcfc7d0126cb1d2057d63d21b616d41c0628 Mon Sep 17 00:00:00 2001 From: tact1m4n3 Date: Sat, 19 Jul 2025 14:44:58 +0300 Subject: [PATCH 06/43] commit --- examples/espressif/esp/src/wifi.zig | 34 ++++------- port/espressif/esp/src/hal/radio.zig | 4 +- .../esp/src/hal/radio/multitasking.zig | 6 +- port/espressif/esp/src/hal/radio/osi.zig | 14 +++-- port/espressif/esp/src/hal/radio/wifi.zig | 60 +++++++++++-------- 5 files changed, 62 insertions(+), 56 deletions(-) diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index a60727db7..cfb0acde8 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -32,7 +32,7 @@ pub const microzig_options: microzig.Options = .{ .interrupts = .{ .interrupt1 = radio.interrupt_handlers.wifi_xxx, .interrupt2 = radio.interrupt_handlers.timer, - .interrupt3 = radio.interrupt_handlers.software, + .interrupt3 = radio.interrupt_handlers.yield, }, .hal = .{ .radio = .{ @@ -78,10 +78,7 @@ pub fn main() !void { }, }); - radio.wifi.set_packet_rx_sta_callback(rx_callback); - try radio.wifi.start(); - // non blocking try radio.wifi.connect(); var connected: bool = false; @@ -101,16 +98,17 @@ pub fn main() !void { connected = false; } - const maybe_pbuf = blk: { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); + while (radio.wifi.recv_packet(.sta)) |packet| { + defer packet.deinit(); - break :blk rx_queue.dequeue(); - }; + const maybe_pbuf: ?*c.struct_pbuf = c.pbuf_alloc(c.PBUF_RAW, @intCast(packet.data.len), c.PBUF_POOL); + if (maybe_pbuf) |pbuf| { + _ = c.pbuf_take(pbuf, packet.data.ptr, @intCast(packet.data.len)); + defer _ = c.pbuf_free(pbuf); - if (maybe_pbuf) |pbuf| { - if (netif.input.?(pbuf, &netif) != c.ERR_OK) { - _ = c.pbuf_free(pbuf); + if (c.netif_input(pbuf, &netif) != c.ERR_OK) { + std.log.warn("lwip netif input failed", .{}); + } } } @@ -180,18 +178,6 @@ fn rx_callback(packet: radio.wifi.ReceivedPacket) void { // std.log.info("receiving packet", .{}); - const maybe_pbuf: ?*c.struct_pbuf = c.pbuf_alloc(c.PBUF_RAW, @intCast(packet.data.len), c.PBUF_POOL); - if (maybe_pbuf) |pbuf| { - _ = c.pbuf_take(pbuf, packet.data.ptr, @intCast(packet.data.len)); - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - rx_queue.enqueue(pbuf) catch { - std.log.warn("packet dropped", .{}); - _ = c.pbuf_free(pbuf); - }; - } } fn netif_init(netif_c: [*c]c.struct_netif) callconv(.c) c.err_t { diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index a55fc5bcf..109b33494 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -29,6 +29,8 @@ pub const Options = struct { // TODO: support other timers and other systimer units /// What alarm to use for preemption in systimer unit 0. systimer_alarm: systimer.Alarm = .alarm0, + + wifi: wifi.Options = .{}, }; pub const options = microzig.options.hal.radio orelse @compileError("Please specify options if you want to use radio."); @@ -218,7 +220,7 @@ pub const interrupt_handlers = struct { multitasking.switch_task(trap_frame); } - pub fn software(trap_frame: *TrapFrame) linksection(".ram_text") callconv(.c) void { + pub fn yield(trap_frame: *TrapFrame) linksection(".ram_text") callconv(.c) void { // TODO: config SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ .CPU_INTR_FROM_CPU_0 = 0, diff --git a/port/espressif/esp/src/hal/radio/multitasking.zig b/port/espressif/esp/src/hal/radio/multitasking.zig index 553e6829f..41cb9cd34 100644 --- a/port/espressif/esp/src/hal/radio/multitasking.zig +++ b/port/espressif/esp/src/hal/radio/multitasking.zig @@ -92,10 +92,14 @@ fn copy_context(dst: *TrapFrame, src: *const TrapFrame) void { } pub fn yield_task() void { - std.debug.assert(microzig.cpu.interrupt.globally_enabled()); + if (!microzig.cpu.interrupt.globally_enabled()) { + @panic("can't yield when interrupts are disabled"); + } // TODO: config SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ .CPU_INTR_FROM_CPU_0 = 1, }); + + microzig.cpu.wfi(); } diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 0fd3353ae..a78273cfc 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -589,14 +589,16 @@ pub fn queue_recv(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) const item: [*]u8 = @alignCast(@ptrCast(item_ptr)); while (true) { - const cs = enter_critical_section(); - defer cs.leave(); + { + const cs = enter_critical_section(); + defer cs.leave(); - if (queue.dequeue(item)) |_| { - log.debug(">>>> return from queue recv: {*}", .{queue}); + if (queue.dequeue(item)) |_| { + log.debug(">>>> return from queue recv: {*}", .{queue}); - return 1; - } else |_| {} + return 1; + } else |_| {} + } if (!forever and hal.time.get_time_since_boot().diff(start).to_us() > timeout) { log.warn(">>>> return from queue recv from timeout: {*}", .{queue}); diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index 88c64e098..74d944165 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -2,7 +2,10 @@ const std = @import("std"); const log = std.log.scoped(.esp_radio_wifi); const microzig = @import("microzig"); +const SPSC_Queue = microzig.concurrency.SPSC_Queue; +const radio = @import("../radio.zig"); +const options = radio.options.wifi; const osi = @import("osi.zig"); pub const c = @import("esp-wifi-driver"); @@ -11,6 +14,11 @@ pub const InternalError = error{InternalError}; var inited: bool = false; +pub const Options = struct { + rx_queue_len: usize = 15, + tx_queue_len: usize = 15, +}; + pub fn init() InternalError!void { if (inited) { @panic("wifi already initialized"); @@ -30,8 +38,8 @@ pub fn init() InternalError!void { try c_result(c.esp_wifi_set_tx_done_cb(tx_done_cb)); - try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_STA, recv_cb_sta)); try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_AP, recv_cb_ap)); + try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_STA, recv_cb_sta)); { // TODO: config @@ -325,6 +333,7 @@ pub fn stop() InternalError!void { try c_result(c.esp_wifi_stop()); } +/// Non-blocking. pub fn connect() InternalError!void { try c_result(c.esp_wifi_connect()); } @@ -456,6 +465,8 @@ fn event_post( return 0; } +// TODO: ApState + pub const StaState = enum { none, sta_started, @@ -481,17 +492,15 @@ pub fn get_sta_state() StaState { return @atomicLoad(StaState, &sta_state, .unordered); } -// TODO: ApState - pub const Interface = enum(u32) { - sta = c.WIFI_IF_STA, ap = c.WIFI_IF_AP, + sta = c.WIFI_IF_STA, pub fn is_active(self: Interface) InternalError!bool { const mode = try get_mode(); return switch (self) { - .sta => mode.is_sta(), .ap => mode.is_ap(), + .sta => mode.is_sta(), }; } }; @@ -499,10 +508,8 @@ pub const Interface = enum(u32) { var packets_in_flight: usize = 0; pub fn send_packet(iface: Interface, data: []const u8) (error{TooManyPacketsInFlight} || InternalError)!void { - std.debug.assert(try iface.is_active()); // tried to send packet on invalid interface - const pkts_in_flight = @atomicLoad(usize, &packets_in_flight, .acquire); - if (pkts_in_flight >= 15) { + if (pkts_in_flight >= options.tx_queue_len) { log.warn("too many packets in flight", .{}); return error.TooManyPacketsInFlight; } @@ -533,39 +540,44 @@ pub const ReceivedPacket = struct { } }; -pub const RxCallback = *const fn (packet: ReceivedPacket) void; - -var packet_rx_sta_callback: ?RxCallback = null; -var packet_rx_ap_callback: ?RxCallback = null; +var ap_rx_queue: SPSC_Queue(ReceivedPacket, options.rx_queue_len) = .empty; +var sta_rx_queue: SPSC_Queue(ReceivedPacket, options.rx_queue_len) = .empty; -pub fn set_packet_rx_sta_callback(cb: RxCallback) void { - @atomicStore(?RxCallback, &packet_rx_sta_callback, cb, .unordered); +pub fn recv_packet(comptime iface: Interface) ?ReceivedPacket { + return switch (iface) { + .ap => ap_rx_queue.dequeue(), + .sta => sta_rx_queue.dequeue(), + }; } -fn recv_cb_sta(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_err_t { - log.debug("recv_cb_sta {?} {} {?}", .{ buf, len, eb }); +fn recv_cb_ap(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_err_t { + log.debug("recv_cb_ap {?} {} {?}", .{ buf, len, eb }); const packet: ReceivedPacket = .{ .data = @as([*]const u8, @ptrCast(buf))[0..len], .eb = eb, }; - if (@atomicLoad(?RxCallback, &packet_rx_sta_callback, .unordered)) |cb| { - cb(packet); - } + ap_rx_queue.enqueue(packet) catch { + log.warn("ap rx queue full. packet dropped.", .{}); + packet.deinit(); + }; return c.ESP_OK; } -fn recv_cb_ap(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_err_t { - const pb: ReceivedPacket = .{ +fn recv_cb_sta(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_err_t { + log.debug("recv_cb_sta {?} {} {?}", .{ buf, len, eb }); + + const packet: ReceivedPacket = .{ .data = @as([*]const u8, @ptrCast(buf))[0..len], .eb = eb, }; - if (@atomicLoad(?RxCallback, &packet_rx_ap_callback, .unordered)) |cb| { - cb(pb); - } + sta_rx_queue.enqueue(packet) catch { + log.warn("sta rx queue full. packet dropped.", .{}); + packet.deinit(); + }; return c.ESP_OK; } From 78da3030c17614c47a0c454749c4d7659cfc0688 Mon Sep 17 00:00:00 2001 From: tact1m4n3 Date: Mon, 21 Jul 2025 19:21:03 +0300 Subject: [PATCH 07/43] more cleanup --- examples/espressif/esp/src/wifi.zig | 2 +- port/espressif/esp/src/hal/radio.zig | 6 +- .../esp/src/hal/radio/multitasking.zig | 6 - port/espressif/esp/src/hal/radio/osi.zig | 14 +- port/espressif/esp/src/hal/radio/wifi.zig | 2 +- port/espressif/esp/src/hal/wifi.zig | 1752 ----------------- 6 files changed, 14 insertions(+), 1768 deletions(-) delete mode 100644 port/espressif/esp/src/hal/wifi.zig diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index cfb0acde8..a25a0d03e 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -120,7 +120,7 @@ pub fn main() !void { std.log.info("used memory: {}K ({})", .{ used_mem / 1024, used_mem }); last_mem_show = now; } - hal.time.sleep_ms(100); + hal.time.sleep_ms(10); // if (events.wifi.contains(.Connected)) { // c.netif_set_link_up(&netif); diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index 109b33494..d065821ff 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -35,8 +35,8 @@ pub const Options = struct { pub const options = microzig.options.hal.radio orelse @compileError("Please specify options if you want to use radio."); -// TODO: We should allow the user to select the scheduling algorithm. We should -// pass something like the `IO` interface alongside an allocator. +// TODO: We should allow the user to select the scheduling algorithm. Maybe at +// compile time? /// Radio uses the official esp drivers. You should enable interrupts /// after/before this. @@ -59,7 +59,7 @@ pub fn init(allocator: Allocator) Allocator.Error!void { setup_interrupts(); } - multitasking.yield_task(); + // multitasking.yield_task(); // try timer.init(allocator); diff --git a/port/espressif/esp/src/hal/radio/multitasking.zig b/port/espressif/esp/src/hal/radio/multitasking.zig index 41cb9cd34..b2e85925b 100644 --- a/port/espressif/esp/src/hal/radio/multitasking.zig +++ b/port/espressif/esp/src/hal/radio/multitasking.zig @@ -92,14 +92,8 @@ fn copy_context(dst: *TrapFrame, src: *const TrapFrame) void { } pub fn yield_task() void { - if (!microzig.cpu.interrupt.globally_enabled()) { - @panic("can't yield when interrupts are disabled"); - } - // TODO: config SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ .CPU_INTR_FROM_CPU_0 = 1, }); - - microzig.cpu.wfi(); } diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index a78273cfc..99908b8af 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -226,14 +226,10 @@ pub fn set_isr( // don't need critical section because we enable the interrupt // bellow. - microzig.cpu.interrupt.disable(.interrupt1); - wifi_interrupt_handler = .{ .f = @alignCast(@ptrCast(f)), .arg = arg, }; - - microzig.cpu.interrupt.enable(.interrupt1); }, else => @panic("invalid interrupt number"), } @@ -242,11 +238,19 @@ pub fn set_isr( // TODO pub fn ints_on(mask: u32) callconv(.c) void { log.debug("ints_on {}", .{mask}); + + if (mask == 2) { + microzig.cpu.interrupt.enable(.interrupt1); + } } // TODO pub fn ints_off(mask: u32) callconv(.c) void { - std.debug.panic("ints_off {}", .{mask}); + log.debug("ints_off {}", .{mask}); + + if (mask == 2) { + microzig.cpu.interrupt.disable(.interrupt1); + } } pub fn is_from_isr() callconv(.c) bool { diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index 74d944165..06a2d84da 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -582,7 +582,7 @@ fn recv_cb_sta(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_e return c.ESP_OK; } -// I pupulated this with the defaults from rust. Some of it should be configurable. +// TODO: configurable var init_config: c.wifi_init_config_t = .{ .osi_funcs = &g_wifi_osi_funcs, // .wpa_crypto_funcs = c.g_wifi_default_wpa_crypto_funcs, diff --git a/port/espressif/esp/src/hal/wifi.zig b/port/espressif/esp/src/hal/wifi.zig deleted file mode 100644 index 3c445bf8c..000000000 --- a/port/espressif/esp/src/hal/wifi.zig +++ /dev/null @@ -1,1752 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; - -const c = @import("esp-wifi-driver"); -const microzig = @import("microzig"); -const TrapFrame = microzig.cpu.TrapFrame; -const time = microzig.drivers.time; -const peripherals = microzig.chip.peripherals; -const SYSTEM = peripherals.SYSTEM; -const RTC_CNTL = peripherals.RTC_CNTL; -const APB_CTRL = peripherals.APB_CTRL; -const hal = microzig.hal; -const systimer = hal.systimer; - -const log = std.log.scoped(.esp_wifi); // what should be the scope? - -// based on the rust crate esp-wifi: https://github.com/esp-rs/esp-hal/blob/main/esp-wifi - -pub const Error = error{ - OutOfMemory, - InternalWifiError, -}; - -pub fn init(allocator: Allocator) Error!void { - wifi_allocator = allocator; - - // TODO: check that clock frequency is higher or equal to 80mhz - - enable_wifi_power_domain_and_init_clocks(); - // phy_mem_init(); // only sets some global variable on esp32c3 - - microzig.cpu.interrupt.disable_interrupts(); - - setup_interrupts(); - setup_timer_periodic_alarm(); - try init_main_task(allocator); - - const task = try Task.create(allocator, timer_task, null, 8096); - schedule_task(task); - - microzig.cpu.interrupt.enable_interrupts(); - - // This would populate the main task's context. - // NOTE: we could probably remove this and wait for the first preemption interrupt - yield_task(); - - // NOTE: wifi driver log level should be configurable. - try c_result(c.esp_wifi_internal_set_log_level(c.WIFI_LOG_VERBOSE)); -} - -pub const wifi_controller = struct { - pub fn init() Error!void { - init_config.wpa_crypto_funcs = c.g_wifi_default_wpa_crypto_funcs; - - // coex init - - // TODO: if coex enabled - if (false) try c_result(c.coex_init()); - - try c_result(c.esp_wifi_init_internal(&init_config)); - - try c_result(c.esp_wifi_set_mode(c.WIFI_MODE_NULL)); - - try c_result(c.esp_supplicant_init()); - - // try c_result(c.esp_wifi_set_tx_done_cb(tx_done_cb)); - // - // try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_STA, recv_cb_sta)); - // try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_AP, recv_cb_ap)); - - { - // TODO: config - const country_code: [3]u8 = .{ 'C', 'N', 0 }; - const country: c.wifi_country_t = .{ - .cc = country_code, - .schan = 1, - .nchan = 13, - .max_tx_power = 20, - .policy = c.WIFI_COUNTRY_POLICY_MANUAL, - }; - try c_result(c.esp_wifi_set_country(&country)); - } - - try c_result(c.esp_wifi_start()); - } - - // I pupulated this with the defaults from rust. Some of it should be configurable. - var init_config: c.wifi_init_config_t = .{ - .osi_funcs = &osi_functions.table, - // .wpa_crypto_funcs = c.g_wifi_default_wpa_crypto_funcs, - .static_rx_buf_num = 10, - .dynamic_rx_buf_num = 32, - .tx_buf_type = c.CONFIG_ESP_WIFI_TX_BUFFER_TYPE, - .static_tx_buf_num = 0, - .dynamic_tx_buf_num = 32, - .rx_mgmt_buf_type = c.CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF, - .rx_mgmt_buf_num = c.CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF, - .cache_tx_buf_num = c.WIFI_CACHE_TX_BUFFER_NUM, - .csi_enable = 0, // TODO: WiFi channel state information enable flag. - .ampdu_rx_enable = 1, - .ampdu_tx_enable = 1, - .amsdu_tx_enable = 0, - .nvs_enable = 0, - .nano_enable = 0, - .rx_ba_win = 6, - .wifi_task_core_id = 0, - .beacon_max_len = c.WIFI_SOFTAP_BEACON_MAX_LEN, - .mgmt_sbuf_num = c.WIFI_MGMT_SBUF_NUM, - .feature_caps = wifi_feature_caps, - .sta_disconnected_pm = false, - .espnow_max_encrypt_num = c.CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM, - .tx_hetb_queue_num = 3, - .dump_hesigb_enable = false, - .magic = c.WIFI_INIT_CONFIG_MAGIC, - }; - - const wifi_enable_wpa3_sae: u64 = 1 << 0; - const wifi_enable_enterprise: u64 = 1 << 7; - // const wifi_ftm_initiator: u64 = 1 << 2; - // const wifi_ftm_responder: u64 = 1 << 3; - // const wifi_enable_gcmp: u64 = 1 << 4; - // const wifi_enable_gmac: u64 = 1 << 5; - // const wifi_enable_11r: u64 = 1 << 6; - - const wifi_feature_caps: u64 = wifi_enable_wpa3_sae | wifi_enable_enterprise; -}; - -fn enable_wifi_power_domain_and_init_clocks() void { - const system_wifibb_rst: u32 = 1 << 0; - const system_fe_rst: u32 = 1 << 1; - const system_wifimac_rst: u32 = 1 << 2; - const system_btbb_rst: u32 = 1 << 3; // bluetooth baseband - const system_btmac_rst: u32 = 1 << 4; // deprecated - const system_rw_btmac_rst: u32 = 1 << 9; // bluetooth mac - const system_rw_btmac_reg_rst: u32 = 1 << 11; // bluetooth mac registers - const system_btbb_reg_rst: u32 = 1 << 13; // bluetooth baseband registers - - const modem_reset_field_when_pu: u32 = system_wifibb_rst | - system_fe_rst | - system_wifimac_rst | - system_btbb_rst | - system_btmac_rst | - system_rw_btmac_rst | - system_rw_btmac_reg_rst | - system_btbb_reg_rst; - - RTC_CNTL.DIG_PWC.modify(.{ - .WIFI_FORCE_PD = 0, - .BT_FORCE_PD = 0, - }); - - APB_CTRL.WIFI_RST_EN.write(.{ .WIFI_RST = APB_CTRL.WIFI_RST_EN.read().WIFI_RST | modem_reset_field_when_pu }); - APB_CTRL.WIFI_RST_EN.write(.{ .WIFI_RST = APB_CTRL.WIFI_RST_EN.read().WIFI_RST & ~modem_reset_field_when_pu }); - - RTC_CNTL.DIG_ISO.modify(.{ - .WIFI_FORCE_ISO = 0, - .BT_FORCE_ISO = 0, - }); - - const system_wifi_clk_i2c_clk_en: u32 = 1 << 5; - const system_wifi_clk_unused_bit12: u32 = 1 << 12; - const wifi_bt_sdio_clk: u32 = system_wifi_clk_i2c_clk_en | system_wifi_clk_unused_bit12; - const system_wifi_clk_en: u32 = 0x00FB9FCF; - - APB_CTRL.WIFI_CLK_EN.write(.{ - .WIFI_CLK_EN = APB_CTRL.WIFI_CLK_EN.read().WIFI_CLK_EN & - ~wifi_bt_sdio_clk | - system_wifi_clk_en, - }); -} - -fn setup_interrupts() void { - // TODO: which interrupts are used should be configurable. - - microzig.cpu.interrupt.set_priority_threshold(.zero); - - microzig.cpu.interrupt.map(.wifi_mac, .interrupt1); - microzig.cpu.interrupt.map(.wifi_pwr, .interrupt1); - microzig.cpu.interrupt.map(.systimer_target0, .interrupt2); - microzig.cpu.interrupt.map(.from_cpu_intr0, .interrupt3); - - inline for (&.{ .interrupt1, .interrupt2, .interrupt3 }) |int| { - microzig.cpu.interrupt.set_type(int, .level); - microzig.cpu.interrupt.set_priority(int, .lowest); - } - - inline for (&.{ .interrupt2, .interrupt3 }) |int| { - microzig.cpu.interrupt.enable(int); - } -} - -const timer_alarm: systimer.Alarm = .alarm0; -const preemt_interval: time.Duration = .from_ms(10); - -fn setup_timer_periodic_alarm() void { - // unit0 is already enabled as it is used by `hal.time`. - timer_alarm.set_unit(.unit0); - - // sets the period to one second. - timer_alarm.set_period(@intCast(preemt_interval.to_us() * systimer.ticks_per_us())); - - // to enable period mode you have to first clear the mode bit. - timer_alarm.set_mode(.target); - timer_alarm.set_mode(.period); - - timer_alarm.set_interrupt_enabled(true); - timer_alarm.set_enabled(true); -} - -const Task = struct { - trap_frame: TrapFrame, - stack: []u8, - semaphore: u32 = 0, - next: *Task, - - pub fn create( - allocator: Allocator, - entry: *const fn (param: ?*anyopaque) callconv(.c) noreturn, - param: ?*anyopaque, - stack_size: usize, - ) !*Task { - const stack: []u8 = try allocator.alloc(u8, stack_size); - errdefer allocator.free(stack); - - const task: *Task = try allocator.create(Task); - errdefer allocator.destroy(task); - - const trap_frame: TrapFrame = blk: { - const stack_top_addr: usize = @intFromPtr(stack.ptr) + stack.len; - - var frame: TrapFrame = undefined; - @memset(std.mem.asBytes(&frame), 0); - - frame.pc = @intFromPtr(entry); - frame.a0 = @intFromPtr(param); - frame.sp = stack_top_addr - stack_top_addr % 16; - - break :blk frame; - }; - - task.* = .{ - .trap_frame = trap_frame, - .stack = stack, - .next = task, // loop back to this task - }; - - return task; - } - - pub fn destroy(task: *Task, allocator: Allocator) void { - allocator.free(task.stack); - allocator.destroy(task); - } -}; - -var main_task: *Task = undefined; -/// SAFETY: we don't need optionals because there will always be the main task -/// running. It can't be deleted. -var current_task: *Task = undefined; - -fn init_main_task(allocator: Allocator) !void { - const task: *Task = try allocator.create(Task); - task.* = .{ - .trap_frame = undefined, - .stack = &.{}, - .next = task, // loop back to this task - }; - current_task = task; - main_task = task; -} - -fn schedule_task(task: *Task) void { - task.next = current_task.next; - current_task.next = task; -} - -fn switch_task(trap_frame: *TrapFrame) void { - copy_context(¤t_task.trap_frame, trap_frame); - - // check if we need to delete any task - - current_task = current_task.next; - - copy_context(trap_frame, ¤t_task.trap_frame); -} - -fn copy_context(dst: *TrapFrame, src: *const TrapFrame) void { - // don't restore csrs - const size = @offsetOf(TrapFrame, "mstatus"); - @memcpy(std.mem.asBytes(dst)[0..size], std.mem.asBytes(src)[0..size]); -} - -fn yield_task() void { - SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ - .CPU_INTR_FROM_CPU_0 = 1, - }); -} - -const Timer = struct { - ets_timer: *c.ets_timer, - deadline: time.Deadline, - periodic: ?time.Duration, -}; - -const TimerList = std.SinglyLinkedList(Timer); -const TimerListNode = TimerList.Node; - -var timer_list: TimerList = .{}; - -fn add_timer(allocator: Allocator, ets_timer: *c.ets_timer) !void { - const timer: Timer = .{ - .ets_timer = ets_timer, - .deadline = .init_absolute(null), - .periodic = null, - }; - - const node = try allocator.create(TimerListNode); - node.* = .{ - .data = timer, - }; - timer_list.prepend(node); -} - -fn find_timer(ets_timer: *c.ets_timer) ?*Timer { - var current_node = timer_list.first; - while (current_node) |node| : (current_node = node.next) { - const timer = &node.data; - if (timer.ets_timer == ets_timer) { - return timer; - } - } - return null; -} - -fn find_next_timer_due(now: time.Absolute) ?*Timer { - var current_node = timer_list.first; - while (current_node) |node| : (current_node = node.next) { - const timer = &node.data; - if (timer.deadline.is_reached_by(now)) { - return timer; - } - } - return null; -} - -fn timer_task(_: ?*anyopaque) callconv(.c) noreturn { - while (true) { - const now = hal.time.get_time_since_boot(); - - const maybe_call, const arg = blk: { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - if (find_next_timer_due(now)) |timer| { - if (timer.periodic) |period| { - timer.deadline = .init_relative(now, period); - } else { - timer.deadline = .init_absolute(null); - } - - break :blk .{ timer.ets_timer.func, timer.ets_timer.priv }; - } else { - break :blk .{ null, null }; - } - }; - - if (maybe_call) |callback| { - callback(arg); - } else { - yield_task(); - } - } -} - -var wifi_interrupt_handler: struct { - f: *const fn (?*anyopaque) callconv(.c) void, - arg: ?*anyopaque, -} = undefined; - -pub const interrupt_handlers = struct { - pub fn wifi_xxx(_: *TrapFrame) linksection(".trap") callconv(.c) void { - log.debug("interrupt WIFI_xxx {} {?}", .{ - wifi_interrupt_handler.f, - wifi_interrupt_handler.arg, - }); - - wifi_interrupt_handler.f(wifi_interrupt_handler.arg); - } - - pub fn timer(trap_frame: *TrapFrame) linksection(".trap") callconv(.c) void { - switch_task(trap_frame); - - timer_alarm.clear_interrupt(); - } - - pub fn software(trap_frame: *TrapFrame) linksection(".trap") callconv(.c) void { - switch_task(trap_frame); - - SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ - .CPU_INTR_FROM_CPU_0 = 0, - }); - } -}; - -var wifi_allocator: Allocator = undefined; - -fn allocator_create_in_cs(comptime T: type) Error!*T { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - return wifi_allocator.create(T); -} - -fn allocator_destroy_in_cs(memory: anytype) void { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - wifi_allocator.destroy(memory); -} - -fn allocator_alloc_in_cs(comptime T: type, n: usize) Allocator.Error![]T { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - return wifi_allocator.alloc(T, n); -} - -fn allocator_free_in_cs(memory: anytype) void { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - return wifi_allocator.free(memory); -} - -extern fn vsnprintf(buffer: [*c]u8, len: usize, fmt: [*c]const u8, va_list: std.builtin.VaList) callconv(.c) void; - -const log_wifi_internal = std.log.scoped(.esp_wifi_internal); - -fn syslog(fmt: [*c]const u8, va_list: std.builtin.VaList) callconv(.c) void { - var buf: [512]u8 = undefined; - vsnprintf(&buf, 512, fmt, va_list); - log_wifi_internal.debug("{s}", .{&buf}); -} - -const c_other_exports = struct { - pub export var WIFI_EVENT: c.esp_event_base_t = "WIFI_EVENT"; -}; - -const c_functions = struct { - pub export fn strlen(ptr: ?*anyopaque) callconv(.c) usize { - return std.mem.len(@as([*:0]const u8, @ptrCast(ptr))); - } - - pub export fn strnlen(ptr: ?*anyopaque, _: usize) callconv(.c) usize { - // TODO Actually provide a complete implementation. - return std.mem.len(@as([*:0]const u8, @ptrCast(ptr))); - } - - pub export fn malloc(len: usize) callconv(.c) ?*anyopaque { - const maybe_alloc = blk: { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - break :blk wifi_allocator.rawAlloc(@sizeOf(usize) + len, .@"4", @returnAddress()); - }; - - if (maybe_alloc) |alloc| { - const alloc_len: *usize = @alignCast(@ptrCast(alloc)); - alloc_len.* = len; - - return @ptrFromInt(@intFromPtr(alloc) + @sizeOf(usize)); - } else { - return null; - } - } - - pub export fn calloc(number: usize, size: usize) callconv(.c) ?*anyopaque { - const total_size: usize = number * size; - if (malloc(total_size)) |ptr| { - @memset(@as([*]u8, @ptrCast(ptr))[0..total_size], 0); - return ptr; - } - return null; - } - - pub export fn free(ptr: ?*anyopaque) callconv(.c) void { - std.debug.assert(ptr != null); - - const alloc: [*]u8 = @ptrFromInt(@intFromPtr(ptr) - 4); - const alloc_len: *usize = @alignCast(@ptrCast(alloc)); - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - wifi_allocator.rawFree(alloc[0 .. 4 + alloc_len.*], .@"4", @returnAddress()); - } - - pub export fn _putchar(byte: u8) callconv(.c) void { - // NOTE: not interrupt safe - - const static = struct { - var buf: [256]u8 = undefined; - var bytes_written: usize = 0; - }; - - static.buf[static.bytes_written] = byte; - static.bytes_written += 1; - - if (static.bytes_written >= 256) { - log_wifi_internal.debug("_putchar: {s}", .{&static.buf}); - static.bytes_written = 0; - } - } - - pub export fn puts(ptr: ?*anyopaque) callconv(.c) void { - // NOTE: not interrupt safe - - const s: []const u8 = std.mem.span(@as([*:0]const u8, @ptrCast(ptr))); - log_wifi_internal.debug("{s}", .{s}); - } - - pub export fn rtc_printf(fmt: [*c]const u8, ...) callconv(.c) void { - syslog(fmt, @cVaStart()); - } - - pub export fn phy_printf(fmt: [*c]const u8, ...) callconv(.c) void { - syslog(fmt, @cVaStart()); - } - - pub export fn coexist_printf(fmt: [*c]const u8, ...) callconv(.c) void { - syslog(fmt, @cVaStart()); - } - - pub export fn net80211_printf(fmt: [*c]const u8, ...) callconv(.c) void { - syslog(fmt, @cVaStart()); - } - - pub export fn pp_printf(fmt: [*c]const u8, ...) callconv(.c) void { - syslog(fmt, @cVaStart()); - } -}; - -comptime { - std.testing.refAllDecls(c_functions); - std.testing.refAllDecls(c_other_exports); -} - -const osi_functions = struct { - pub var table: c.wifi_osi_funcs_t = .{ - ._version = c.ESP_WIFI_OS_ADAPTER_VERSION, - ._env_is_chip = osi_functions.env_is_chip, - ._set_intr = osi_functions.set_intr, - ._clear_intr = osi_functions.clear_intr, - ._set_isr = osi_functions.set_isr, - ._ints_on = osi_functions.ints_on, - ._ints_off = osi_functions.ints_off, - ._is_from_isr = osi_functions.is_from_isr, - ._spin_lock_create = osi_functions.spin_lock_create, - ._spin_lock_delete = osi_functions.spin_lock_delete, - ._wifi_int_disable = osi_functions.wifi_int_disable, - ._wifi_int_restore = osi_functions.wifi_int_restore, - ._task_yield_from_isr = osi_functions.task_yield_from_isr, - ._semphr_create = osi_functions.semphr_create, - ._semphr_delete = osi_functions.semphr_delete, - ._semphr_take = osi_functions.semphr_take, - ._semphr_give = osi_functions.semphr_give, - ._wifi_thread_semphr_get = osi_functions.wifi_thread_semphr_get, - ._mutex_create = osi_functions.mutex_create, - ._recursive_mutex_create = osi_functions.recursive_mutex_create, - ._mutex_delete = osi_functions.mutex_delete, - ._mutex_lock = osi_functions.mutex_lock, - ._mutex_unlock = osi_functions.mutex_unlock, - ._queue_create = osi_functions.queue_create, - ._queue_delete = osi_functions.queue_delete, - ._queue_send = osi_functions.queue_send, - ._queue_send_from_isr = osi_functions.queue_send_from_isr, - ._queue_send_to_back = @ptrCast(&osi_functions.queue_send_to_back), - ._queue_send_to_front = @ptrCast(&osi_functions.queue_send_to_front), - ._queue_recv = osi_functions.queue_recv, - ._queue_msg_waiting = osi_functions.queue_msg_waiting, - ._event_group_create = @ptrCast(&osi_functions.event_group_create), - ._event_group_delete = @ptrCast(&osi_functions.event_group_delete), - ._event_group_set_bits = @ptrCast(&osi_functions.event_group_set_bits), - ._event_group_clear_bits = @ptrCast(&osi_functions.event_group_clear_bits), - ._event_group_wait_bits = @ptrCast(&osi_functions.event_group_wait_bits), - ._task_create_pinned_to_core = osi_functions.task_create_pinned_to_core, - ._task_create = osi_functions.task_create, - ._task_delete = @ptrCast(&osi_functions.task_delete), - ._task_delay = osi_functions.task_delay, - ._task_ms_to_tick = osi_functions.task_ms_to_tick, - ._task_get_current_task = osi_functions.task_get_current_task, - ._task_get_max_priority = osi_functions.task_get_max_priority, - ._malloc = osi_functions.malloc, - ._free = &osi_functions.free, - ._event_post = @ptrCast(&osi_functions.event_post), - ._get_free_heap_size = @ptrCast(&osi_functions.get_free_heap_size), - ._rand = osi_functions.rand, - ._dport_access_stall_other_cpu_start_wrap = osi_functions.dport_access_stall_other_cpu_start_wrap, - ._dport_access_stall_other_cpu_end_wrap = osi_functions.dport_access_stall_other_cpu_end_wrap, - ._wifi_apb80m_request = osi_functions.wifi_apb80m_request, - ._wifi_apb80m_release = osi_functions.wifi_apb80m_release, - ._phy_disable = osi_functions.phy_disable, - ._phy_enable = osi_functions.phy_enable, - ._phy_update_country_info = osi_functions.phy_update_country_info, - ._read_mac = osi_functions.read_mac, - ._timer_arm = osi_functions.timer_arm, - ._timer_disarm = osi_functions.timer_disarm, - ._timer_done = @ptrCast(&osi_functions.timer_done), - ._timer_setfn = @ptrCast(&osi_functions.timer_setfn), - ._timer_arm_us = osi_functions.timer_arm_us, - ._wifi_reset_mac = osi_functions.wifi_reset_mac, - ._wifi_clock_enable = osi_functions.wifi_clock_enable, - ._wifi_clock_disable = osi_functions.wifi_clock_disable, - ._wifi_rtc_enable_iso = @ptrCast(&osi_functions.wifi_rtc_enable_iso), - ._wifi_rtc_disable_iso = @ptrCast(&osi_functions.wifi_rtc_disable_iso), - ._esp_timer_get_time = osi_functions.esp_timer_get_time, - ._nvs_set_i8 = @ptrCast(&osi_functions.nvs_set_i8), - ._nvs_get_i8 = @ptrCast(&osi_functions.nvs_get_i8), - ._nvs_set_u8 = @ptrCast(&osi_functions.nvs_set_u8), - ._nvs_get_u8 = @ptrCast(&osi_functions.nvs_get_u8), - ._nvs_set_u16 = @ptrCast(&osi_functions.nvs_set_u16), - ._nvs_get_u16 = @ptrCast(&osi_functions.nvs_get_u16), - ._nvs_open = @ptrCast(&osi_functions.nvs_open), - ._nvs_close = @ptrCast(&osi_functions.nvs_close), - ._nvs_commit = @ptrCast(&osi_functions.nvs_commit), - ._nvs_set_blob = @ptrCast(&osi_functions.nvs_set_blob), - ._nvs_get_blob = @ptrCast(&osi_functions.nvs_get_blob), - ._nvs_erase_key = @ptrCast(&osi_functions.nvs_erase_key), - ._get_random = osi_functions.get_random, - ._get_time = @ptrCast(&osi_functions.get_time), - ._random = &osi_functions.random, - ._slowclk_cal_get = osi_functions.slowclk_cal_get, - ._log_write = osi_functions.log_write, - ._log_writev = osi_functions.log_writev, - ._log_timestamp = osi_functions.log_timestamp, - ._malloc_internal = osi_functions.malloc_internal, - ._realloc_internal = @ptrCast(&osi_functions.realloc_internal), - ._calloc_internal = osi_functions.calloc_internal, - ._zalloc_internal = osi_functions.zalloc_internal, - ._wifi_malloc = osi_functions.wifi_malloc, - ._wifi_realloc = @ptrCast(&osi_functions.wifi_realloc), - ._wifi_calloc = osi_functions.wifi_calloc, - ._wifi_zalloc = osi_functions.wifi_zalloc, - ._wifi_create_queue = osi_functions.wifi_create_queue, - ._wifi_delete_queue = osi_functions.wifi_delete_queue, - ._coex_init = @ptrCast(&osi_functions.coex_init), - ._coex_deinit = @ptrCast(&osi_functions.coex_deinit), - ._coex_enable = @ptrCast(&osi_functions.coex_enable), - ._coex_disable = @ptrCast(&osi_functions.coex_disable), - ._coex_status_get = @ptrCast(&osi_functions.coex_status_get), - ._coex_condition_set = null, - ._coex_wifi_request = @ptrCast(&osi_functions.coex_wifi_request), - ._coex_wifi_release = @ptrCast(&osi_functions.coex_wifi_release), - ._coex_wifi_channel_set = @ptrCast(&osi_functions.coex_wifi_channel_set), - ._coex_event_duration_get = @ptrCast(&osi_functions.coex_event_duration_get), - ._coex_pti_get = @ptrCast(&osi_functions.coex_pti_get), - ._coex_schm_status_bit_clear = @ptrCast(&osi_functions.coex_schm_status_bit_clear), - ._coex_schm_status_bit_set = @ptrCast(&osi_functions.coex_schm_status_bit_set), - ._coex_schm_interval_set = @ptrCast(&osi_functions.coex_schm_interval_set), - ._coex_schm_interval_get = @ptrCast(&osi_functions.coex_schm_interval_get), - ._coex_schm_curr_period_get = @ptrCast(&osi_functions.coex_schm_curr_period_get), - ._coex_schm_curr_phase_get = @ptrCast(&osi_functions.coex_schm_curr_phase_get), - ._coex_schm_process_restart = @ptrCast(&osi_functions.coex_schm_process_restart), - ._coex_schm_register_cb = @ptrCast(&osi_functions.coex_schm_register_cb), - ._coex_register_start_cb = @ptrCast(&osi_functions.coex_register_start_cb), - ._coex_schm_flexible_period_set = @ptrCast(&osi_functions.coex_schm_flexible_period_set), - ._coex_schm_flexible_period_get = @ptrCast(&osi_functions.coex_schm_flexible_period_get), - ._magic = @bitCast(c.ESP_WIFI_OS_ADAPTER_MAGIC), - }; - - pub fn env_is_chip() callconv(.c) bool { - return true; - } - - pub fn set_intr(cpu_no: i32, intr_source: u32, intr_num: u32, intr_prio: i32) callconv(.c) void { - // TODO: maybe assert here argument values contain the values bellow - - log.debug("set_intr {} {} {} {}", .{ cpu_no, intr_source, intr_num, intr_prio }); - - // esp32c3 - // these are already set up - // possible calls: - // INFO - set_intr 0 2 1 1 (WIFI_PWR) - // INFO - set_intr 0 0 1 1 (WIFI_MAC) - } - - pub fn clear_intr(intr_source: u32, intr_num: u32) callconv(.c) void { - log.debug("clear_intr {} {}", .{ intr_source, intr_num }); - } - - pub fn set_isr( - n: i32, - f: ?*anyopaque, - arg: ?*anyopaque, - ) callconv(.c) void { - log.debug("set_isr {} {?} {?}", .{ n, f, arg }); - - switch (n) { - 0, 1 => { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - wifi_interrupt_handler = .{ - .f = @alignCast(@ptrCast(f)), - .arg = arg, - }; - }, - else => @panic("invalid interrupt number"), - } - - microzig.cpu.interrupt.enable(.interrupt1); - } - - // NOTE: temp until I see what bits are set. - pub fn ints_on(mask: u32) callconv(.c) void { - std.debug.panic("ints_on {}", .{mask}); - } - - pub fn ints_off(mask: u32) callconv(.c) void { - std.debug.panic("ints_off {}", .{mask}); - } - - pub fn is_from_isr() callconv(.c) bool { - return true; - } - - var FAKE_SPINLOCK: u8 = 1; - pub fn spin_lock_create() callconv(.c) ?*anyopaque { - log.debug("spin_lock_create", .{}); - return &FAKE_SPINLOCK; - } - - pub fn spin_lock_delete(_: ?*anyopaque) callconv(.c) void { - log.debug("spin_lock_delete", .{}); - } - - // NOTE: critical section for now. If we decide to run the code on multiple cpus (on other chips) we - // should change this. - pub fn wifi_int_disable(_: ?*anyopaque) callconv(.c) u32 { - log.debug("wifi_int_disable", .{}); - - const ints_enabled = microzig.cpu.interrupt.globally_enabled(); - if (ints_enabled) { - microzig.cpu.interrupt.disable_interrupts(); - } - return @intFromBool(ints_enabled); - } - - pub fn wifi_int_restore(_: ?*anyopaque, state: u32) callconv(.c) void { - log.debug("wifi_int_restore", .{}); - - // NOTE: is this required? - microzig.cpu.fence(); - - // check if interrupts were enabled before. - if (state != 0) { - microzig.cpu.interrupt.enable_interrupts(); - } - } - - pub fn task_yield_from_isr() callconv(.c) void { - log.debug("task_yield_from_isr", .{}); - - yield_task(); - } - - pub fn semphr_create(max_value: u32, init_value: u32) callconv(.c) ?*anyopaque { - log.debug("semphr_create {} {}", .{ max_value, init_value }); - - const sem = allocator_create_in_cs(u32) catch { - log.warn("failed to allocate semaphore", .{}); - return null; - }; - sem.* = init_value; - - return sem; - } - - pub fn semphr_delete(ptr: ?*anyopaque) callconv(.c) void { - log.debug("semphr_delete {?}", .{ptr}); - - allocator_destroy_in_cs(@as(*u32, @alignCast(@ptrCast(ptr)))); - } - - pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { - log.debug("semphr_take {?} {}", .{ ptr, tick }); - - const forever = tick == c.OSI_FUNCS_TIME_BLOCKING; - const timeout = tick; - const start = hal.time.get_time_since_boot(); - - const sem: *u32 = @alignCast(@ptrCast(ptr)); - - while (true) { - const res: bool = blk: { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - microzig.cpu.fence(); - - const cnt = sem.*; - if (cnt > 0) { - sem.* = cnt - 1; - break :blk true; - } else { - break :blk false; - } - }; - - if (res) { - return 1; - } - - if (!forever and hal.time.get_time_since_boot().diff(start).to_us() > timeout) { - break; - } - - yield_task(); - } - - return 0; - } - - pub fn semphr_give(ptr: ?*anyopaque) callconv(.c) i32 { - log.debug("semphr_give {?}", .{ptr}); - - const sem: *u32 = @alignCast(@ptrCast(ptr)); - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - const cnt = sem.*; - sem.* = cnt + 1; - return 1; - } - - pub fn wifi_thread_semphr_get() callconv(.c) ?*anyopaque { - return @ptrCast(¤t_task.semaphore); - } - - const Mutex = struct { - locking_task: usize, - count: u32, - recursive: bool, - }; - - // TODO: idk if we're gonna need to implement this - pub fn mutex_create() callconv(.c) ?*anyopaque { - @panic("mutex_create: not implemented"); - } - - pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { - log.debug("recursive_mutex_create", .{}); - - const mutex = allocator_create_in_cs(Mutex) catch { - log.warn("failed to allocate recursive mutex", .{}); - return null; - }; - mutex.* = .{ - .locking_task = 0xffff_ffff, - .count = 0, - .recursive = true, - }; - return mutex; - } - - pub fn mutex_delete(ptr: ?*anyopaque) callconv(.c) void { - log.debug("mutex_delete {?}", .{ptr}); - - const mutex: *Mutex = @alignCast(@ptrCast(ptr)); - allocator_destroy_in_cs(mutex); - } - - pub fn mutex_lock(ptr: ?*anyopaque) callconv(.c) i32 { - log.debug("mutex lock {?}", .{ptr}); - - const mutex: *Mutex = @alignCast(@ptrCast(ptr)); - const cur_task_id: usize = @intFromPtr(current_task); - - while (true) { - const mutex_locked = blk: { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - if (mutex.count == 0) { - mutex.locking_task = cur_task_id; - mutex.count += 1; - break :blk true; - } else if (mutex.locking_task == cur_task_id) { - mutex.count += 1; - break :blk true; - } else { - break :blk false; - } - }; - - microzig.cpu.fence(); - - if (mutex_locked) { - return 1; - } - - yield_task(); - } - } - - pub fn mutex_unlock(ptr: ?*anyopaque) callconv(.c) i32 { - log.debug("mutex unlock {?}", .{ptr}); - - const mutex: *Mutex = @alignCast(@ptrCast(ptr)); - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - microzig.cpu.fence(); - - if (mutex.count > 0) { - mutex.count -= 1; - return 1; - } else { - return 0; - } - } - - const Queue = struct { - capacity: usize, - item_len: usize, - read_index: usize = 0, - write_index: usize = 0, - storage: []u8, - - pub fn create(allocator: Allocator, capacity: usize, item_len: usize) error{OutOfMemory}!*Queue { - const queue = try allocator.create(Queue); - errdefer allocator.destroy(queue); - - queue.* = .{ - .capacity = capacity, - .item_len = item_len, - .read_index = 0, - .write_index = 0, - .storage = try allocator.alloc(u8, capacity * item_len), - }; - - return queue; - } - - pub fn destroy(self: *Queue, allocator: Allocator) void { - allocator.free(self.storage); - allocator.destroy(self); - } - - pub fn len(self: Queue) usize { - if (self.write_index >= self.read_index) { - return self.write_index - self.read_index; - } else { - return self.capacity - self.read_index + self.write_index; - } - } - - pub fn get(self: Queue, index: usize) []u8 { - const item_start = self.item_len * index; - return self.storage[item_start..][0..self.item_len]; - } - - pub fn enqueue(self: *Queue, item: [*]const u8) error{QueueFull}!void { - if (self.len() == self.capacity) { - return error.QueueFull; - } - - const slot = self.get(self.write_index); - @memcpy(slot, item[0..self.item_len]); - self.write_index = (self.write_index + 1) % self.capacity; - } - - pub fn dequeue(self: *Queue, item: [*]u8) error{QueueEmpty}!void { - if (self.len() == 0) { - return error.QueueEmpty; - } - - const slot = self.get(self.read_index); - @memcpy(item[0..self.item_len], slot); - self.read_index = (self.read_index + 1) % self.capacity; - } - }; - - pub fn queue_create(capacity: u32, item_len: u32) callconv(.c) ?*anyopaque { - log.debug("queue_create {} {}", .{ capacity, item_len }); - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - const queue = Queue.create(wifi_allocator, capacity, item_len) catch { - log.warn("failed to allocate queue", .{}); - return null; - }; - - return queue; - } - - pub fn queue_delete(ptr: ?*anyopaque) callconv(.c) void { - log.debug("queue_delete {?}", .{ptr}); - - const queue: *Queue = @alignCast(@ptrCast(ptr)); - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - queue.destroy(wifi_allocator); - } - - // NOTE: here we ignore the timeout. The rust version doesn't use it. - fn queue_send_common(ptr: ?*anyopaque, item_ptr: ?*anyopaque) callconv(.c) i32 { - const queue: *Queue = @alignCast(@ptrCast(ptr)); - const item: [*]const u8 = @alignCast(@ptrCast(item_ptr)); - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - queue.enqueue(item) catch { - log.warn("failed to add item to queue", .{}); - return 0; - }; - - return 1; - } - - pub fn queue_send(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) callconv(.c) i32 { - log.debug("queue_send {?} {?} {}", .{ ptr, item_ptr, block_time_tick }); - - return queue_send_common(ptr, item_ptr); - } - - pub fn queue_send_from_isr(ptr: ?*anyopaque, item_ptr: ?*anyopaque, _hptw: ?*anyopaque) callconv(.c) i32 { - log.debug("queue_send_from_isr {?} {?} {?}", .{ ptr, item_ptr, _hptw }); - - @as(*u32, @alignCast(@ptrCast(_hptw))).* = 1; - return queue_send_common(ptr, item_ptr); - } - - pub fn queue_send_to_back() callconv(.c) void { - @panic("queue_send_to_back: not implemented"); - } - - pub fn queue_send_to_front() callconv(.c) void { - @panic("queue_send_to_front: not implemented"); - } - - pub fn queue_recv(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) callconv(.c) i32 { - log.debug("queue_recv {?} {?} {}", .{ ptr, item_ptr, block_time_tick }); - - const forever = block_time_tick == c.OSI_FUNCS_TIME_BLOCKING; - const timeout = block_time_tick; - const start = hal.time.get_time_since_boot(); - - const queue: *Queue = @alignCast(@ptrCast(ptr)); - const item: [*]u8 = @alignCast(@ptrCast(item_ptr)); - - while (true) { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - if (queue.dequeue(item)) |_| { - return 1; - } else |_| {} - - if (!forever and hal.time.get_time_since_boot().diff(start).to_us() > timeout) { - return -1; - } - - yield_task(); - } - } - - pub fn queue_msg_waiting(ptr: ?*anyopaque) callconv(.c) u32 { - log.debug("queue_msg_waiting {?}", .{ptr}); - - const queue: *Queue = @alignCast(@ptrCast(ptr)); - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - return queue.len(); - } - - pub fn event_group_create() callconv(.c) void { - @panic("event_group_create: not implemented"); - } - - pub fn event_group_delete() callconv(.c) void { - @panic("event_group_delete: not implemented"); - } - - pub fn event_group_set_bits() callconv(.c) void { - @panic("event_group_set_bits: not implemented"); - } - - pub fn event_group_clear_bits() callconv(.c) void { - @panic("event_group_clear_bits: not implemented"); - } - - pub fn event_group_wait_bits() callconv(.c) void { - @panic("event_group_wait_bits: not implemented"); - } - - fn task_create_common( - task_func: ?*anyopaque, - name: [*c]const u8, - stack_depth: u32, - param: ?*anyopaque, - prio: u32, - task_handle: ?*anyopaque, - core_id: u32, - ) i32 { - _ = name; // autofix - _ = prio; // autofix - _ = core_id; // autofix - - const cs = microzig.interrupt.enter_critical_section(); - cs.leave(); - - const task: *Task = Task.create( - wifi_allocator, - @alignCast(@ptrCast(task_func)), - param, - stack_depth, - ) catch { - log.warn("failed to create task", .{}); - return 0; - }; - schedule_task(task); - - @as(*usize, @alignCast(@ptrCast(task_handle))).* = @intFromPtr(task); - - return 1; - } - - pub fn task_create_pinned_to_core( - task_func: ?*anyopaque, - name: [*c]const u8, - stack_depth: u32, - param: ?*anyopaque, - prio: u32, - task_handle: ?*anyopaque, - core_id: u32, - ) callconv(.c) i32 { - log.debug("task_create_pinned_to_core {?} {s} {} {?} {} {?} {}", .{ - task_func, - name, - stack_depth, - param, - prio, - task_handle, - core_id, - }); - - return task_create_common(task_func, name, stack_depth, param, prio, task_handle, core_id); - } - - pub fn task_create( - task_func: ?*anyopaque, - name: [*c]const u8, - stack_depth: u32, - param: ?*anyopaque, - prio: u32, - task_handle: ?*anyopaque, - ) callconv(.c) i32 { - log.debug("task_create {?} {s} {} {?} {} {?}", .{ - task_func, - name, - stack_depth, - param, - prio, - task_handle, - }); - - return task_create_common(task_func, name, stack_depth, param, prio, task_handle, 0); - } - - pub fn task_delete() callconv(.c) void { - @panic("task_delete: not implemented"); - } - - pub fn task_delay(tick: u32) callconv(.c) void { - log.debug("task_delay {}", .{tick}); - - const start = hal.time.get_time_since_boot(); - const delay: time.Duration = .from_us(tick); - - while (hal.time.get_time_since_boot().diff(start).less_than(delay)) { - yield_task(); - } - } - - // NOTE: we could probably use milliseconds directly. - pub fn task_ms_to_tick(ms: u32) callconv(.c) i32 { - // NOTE: idk about bitcast here. Seems weird that it returns i32. - return @intCast(ms * 1_000); - } - - pub fn task_get_current_task() callconv(.c) ?*anyopaque { - return current_task; - } - - pub fn task_get_max_priority() callconv(.c) i32 { - return -1; - } - - pub const malloc = c_functions.malloc; - pub const free = c_functions.free; - - pub fn event_post() callconv(.c) void { - @panic("event_post: not implemented"); - } - - pub fn get_free_heap_size() callconv(.c) void { - @panic("get_free_heap_size: not implemented"); - } - - pub fn rand() callconv(.c) u32 { - return hal.rng.random_u32(); - } - - pub fn dport_access_stall_other_cpu_start_wrap() callconv(.c) void { - log.debug("dport_access_stall_other_cpu_start_wrap", .{}); - } - - pub fn dport_access_stall_other_cpu_end_wrap() callconv(.c) void { - log.debug("dport_access_stall_other_cpu_end_wrap", .{}); - } - - pub fn wifi_apb80m_request() callconv(.c) void { - log.debug("wifi_apb80m_request", .{}); - } - - pub fn wifi_apb80m_release() callconv(.c) void { - log.debug("wifi_apb80m_release", .{}); - } - - fn phy_set_enabled(enable: bool) void { - log.debug("phy_set_enabled", .{}); - - const system_wifi_clk_wifi_bt_common_m: u32 = 0x78078F; - - var wifi_clk_en = APB_CTRL.WIFI_CLK_EN.read().WIFI_CLK_EN; - if (enable) { - wifi_clk_en |= system_wifi_clk_wifi_bt_common_m; - } else { - wifi_clk_en &= ~system_wifi_clk_wifi_bt_common_m; - } - APB_CTRL.WIFI_CLK_EN.write(.{ .WIFI_CLK_EN = wifi_clk_en }); - } - - pub fn phy_disable() callconv(.c) void { - log.debug("phy_disable", .{}); - phy_set_enabled(false); - } - - pub fn phy_enable() callconv(.c) void { - log.debug("phy_enable", .{}); - phy_set_enabled(true); - } - - pub fn phy_update_country_info(country: [*c]const u8) callconv(.c) c_int { - log.debug("phy_update_country_info {s}", .{country}); - return -1; - } - - pub fn read_mac(mac: [*c]u8, typ: c_uint) callconv(.c) c_int { - log.debug("read_mac {*} {}", .{ mac, typ }); - - const EFUSE = microzig.chip.peripherals.EFUSE; - - const low_32_bits: u32 = EFUSE.RD_MAC_SPI_SYS_0.read().MAC_0; - const high_16_bits: u16 = EFUSE.RD_MAC_SPI_SYS_1.read().MAC_1; - @memcpy(mac[0..4], std.mem.asBytes(&low_32_bits)); - @memcpy(mac[4..6], std.mem.asBytes(&high_16_bits)); - - // idk what this means - switch (typ) { - // esp_mac_wifi_softap - 1 => { - const tmp = mac[0]; - var i: u6 = 0; - while (i < 64) : (i += 1) { - mac[0] |= 0x02; - mac[0] ^= @intCast(i << 2); - - if (mac[0] != tmp) { - break; - } - } - }, - // esp_mac_bt - 2 => { - const tmp = mac[0]; - var i: u6 = 0; - while (i < 64) : (i += 1) { - mac[0] |= 0x02; - mac[0] ^= @intCast(i << 2); - - if (mac[0] != tmp) { - break; - } - } - mac[5] += 1; - }, - else => {}, - } - - return 0; - } - - pub fn timer_arm(ets_timer_ptr: ?*anyopaque, ms: u32, repeat: bool) callconv(.c) void { - timer_arm_us(ets_timer_ptr, ms * 1_000, repeat); - } - - pub fn timer_disarm(ets_timer_ptr: ?*anyopaque) callconv(.c) void { - log.debug("timer_disarm {?}", .{ets_timer_ptr}); - - const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - if (find_timer(ets_timer)) |timer| { - timer.deadline = .init_absolute(null); - } else { - log.warn("timer not found based on ets_timer", .{}); - } - } - - pub fn timer_done() callconv(.c) void { - @panic("timer_done: not implemented"); - } - - pub fn timer_setfn(ets_timer_ptr: ?*anyopaque, callback_ptr: ?*anyopaque, arg: ?*anyopaque) callconv(.c) void { - log.debug("timer_setfn {?} {?} {?}", .{ ets_timer_ptr, callback_ptr, arg }); - - const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); - - ets_timer.func = @alignCast(@ptrCast(callback_ptr)); - ets_timer.priv = arg; - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - if (find_timer(ets_timer)) |tim| { - tim.deadline = .init_absolute(null); - } else { - add_timer(wifi_allocator, ets_timer) catch { - log.warn("failed to allocate timer", .{}); - }; - } - } - - pub fn timer_arm_us(ets_timer_ptr: ?*anyopaque, us: u32, repeat: bool) callconv(.c) void { - log.debug("timer_arm_us {?} {} {}", .{ ets_timer_ptr, us, repeat }); - - const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - if (find_timer(ets_timer)) |timer| { - const period: time.Duration = .from_us(us); - timer.deadline = .init_relative(hal.time.get_time_since_boot(), period); - timer.periodic = if (repeat) period else null; - } else { - log.warn("timer not found based on ets_timer", .{}); - } - } - - pub fn wifi_reset_mac() callconv(.c) void { - log.debug("wifi_reset_mac", .{}); - - APB_CTRL.WIFI_RST_EN.write(.{ .WIFI_RST = APB_CTRL.WIFI_RST_EN.read().WIFI_RST | (1 << 2) }); - APB_CTRL.WIFI_RST_EN.write(.{ .WIFI_RST = APB_CTRL.WIFI_RST_EN.read().WIFI_RST & ~@as(u32, 1 << 2) }); - } - - pub fn wifi_clock_enable() callconv(.c) void { - log.debug("wifi_clock_enable", .{}); - - // no op on esp32c3 - } - - pub fn wifi_clock_disable() callconv(.c) void { - log.debug("wifi_clock_disable", .{}); - - // no op on esp32c3 - } - - pub fn wifi_rtc_enable_iso() callconv(.c) void { - @panic("wifi_rtc_enable_iso: not implemented"); - } - - pub fn wifi_rtc_disable_iso() callconv(.c) void { - @panic("wifi_rtc_disable_iso: not implemented"); - } - - pub fn esp_timer_get_time() callconv(.c) i64 { - log.debug("esp_timer_get_time", .{}); - - // TODO: or bit cast? - return @intCast(hal.time.get_time_since_boot().to_us()); - } - - pub fn nvs_set_i8() callconv(.c) void { - @panic("nvs_set_i8: not implemented"); - } - - pub fn nvs_get_i8() callconv(.c) void { - @panic("nvs_get_i8: not implemented"); - } - - pub fn nvs_set_u8() callconv(.c) void { - @panic("nvs_set_u8: not implemented"); - } - - pub fn nvs_get_u8() callconv(.c) void { - @panic("nvs_get_u8: not implemented"); - } - - pub fn nvs_set_u16() callconv(.c) void { - @panic("nvs_set_u16: not implemented"); - } - - pub fn nvs_get_u16() callconv(.c) void { - @panic("nvs_get_u16: not implemented"); - } - - pub fn nvs_open() callconv(.c) void { - @panic("nvs_open: not implemented"); - } - - pub fn nvs_close() callconv(.c) void { - @panic("nvs_close: not implemented"); - } - - pub fn nvs_commit() callconv(.c) void { - @panic("nvs_commit: not implemented"); - } - - pub fn nvs_set_blob() callconv(.c) void { - @panic("nvs_set_blob: not implemented"); - } - - pub fn nvs_get_blob() callconv(.c) void { - @panic("nvs_get_blob: not implemented"); - } - - pub fn nvs_erase_key() callconv(.c) void { - @panic("nvs_erase_key: not implemented"); - } - - pub fn get_random(buf: [*c]u8, len: usize) callconv(.c) c_int { - hal.rng.read(buf[0..len]); - return 0; - } - - pub fn get_time() callconv(.c) void { - @panic("get_time: not implemented"); - } - - pub fn random() callconv(.c) c_ulong { - return hal.rng.random_u32(); - } - - pub fn slowclk_cal_get() callconv(.c) u32 { - // NOTE: esp32c3 specific - return 28639; - } - - pub fn log_write(_: c_uint, _: [*c]const u8, fmt: [*c]const u8, ...) callconv(.c) void { - syslog(fmt, @cVaStart()); - } - - pub fn log_writev(_: c_uint, _: [*c]const u8, fmt: [*c]const u8, va_list: c.va_list) callconv(.c) void { - syslog(fmt, @ptrCast(va_list)); - } - - pub fn log_timestamp() callconv(.c) u32 { - return @truncate(hal.time.get_time_since_boot().to_us() * 1_000); - } - - pub const malloc_internal = malloc; - - pub fn realloc_internal() callconv(.c) void { - @panic("realloc_internal: not implemented"); - } - - pub const calloc_internal = c_functions.calloc; - - pub fn zalloc_internal(len: usize) callconv(.c) ?*anyopaque { - if (malloc(len)) |ptr| { - @memset(@as([*]u8, @ptrCast(ptr))[0..len], 0); - return ptr; - } - - return null; - } - - pub const wifi_malloc = malloc; - - pub fn wifi_realloc() callconv(.c) void { - @panic("wifi_realloc: not implemented"); - } - - pub const wifi_calloc = c_functions.calloc; - pub const wifi_zalloc = zalloc_internal; - - var wifi_queue_handle: *Queue = undefined; - - pub fn wifi_create_queue(capacity: c_int, item_len: c_int) callconv(.c) ?*anyopaque { - log.debug("wifi_create_queue {} {}", .{ capacity, item_len }); - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - wifi_queue_handle = Queue.create(wifi_allocator, @intCast(capacity), @intCast(item_len)) catch { - log.warn("failed to allocate wifi queue", .{}); - return null; - }; - - return @ptrCast(&wifi_queue_handle); - } - - pub fn wifi_delete_queue(ptr: ?*anyopaque) callconv(.c) void { - log.debug("wifi_delete_queue", .{}); - - const queue: *Queue = @alignCast(@ptrCast(ptr)); - - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - queue.destroy(wifi_allocator); - } - const coex_enabled = false; - - pub fn coex_init() callconv(.c) c_int { - log.debug("coex_init", .{}); - - return if (coex_enabled) c.coex_init() else 0; - } - - pub fn coex_deinit() callconv(.c) void { - log.debug("coex_deinit", .{}); - - if (coex_enabled) c.coex_deinit(); - } - - pub fn coex_enable() callconv(.c) c_int { - log.debug("coex_enable", .{}); - - return if (coex_enabled) c.coex_enable() else 0; - } - - pub fn coex_disable() callconv(.c) void { - log.debug("coex_disable", .{}); - - if (coex_enabled) c.coex_disable(); - } - - pub fn coex_status_get() callconv(.c) u32 { - log.debug("coex_status_get", .{}); - - return if (coex_enabled) c.coex_status_get() else 0; - } - - pub fn coex_wifi_request( - event: u32, - latency: u32, - duration: u32, - ) callconv(.c) c_int { - log.debug("coex_wifi_request", .{}); - - return if (coex_enabled) c.coex_wifi_request(event, latency, duration) else 0; - } - - pub fn coex_wifi_release(event: u32) callconv(.c) c_int { - log.debug("coex_wifi_release", .{}); - - return if (coex_enabled) c.coex_wifi_release(event) else 0; - } - - pub fn coex_wifi_channel_set( - primary: u8, - secondary: u8, - ) callconv(.c) c_int { - log.debug("coex_wifi_channel_set", .{}); - - return if (coex_enabled) c.coex_wifi_channel_set(primary, secondary) else 0; - } - - pub fn coex_event_duration_get(event: u32, duration: [*c]u32) callconv(.c) c_int { - log.debug("coex_event_duration_get", .{}); - - return if (coex_enabled) c.coex_event_duration_get(event, duration) else 0; - } - - pub fn coex_pti_get(event: u32, pti: [*c]u8) callconv(.c) c_int { - log.debug("coex_pti_get", .{}); - - return if (coex_enabled) c.coex_pti_get(event, pti) else 0; - } - - pub fn coex_schm_status_bit_clear(@"type": u32, status: u32) callconv(.c) void { - log.debug("coex_schm_status_bit_clear", .{}); - - if (coex_enabled) c.coex_schm_status_bit_clear(@"type", status); - } - - pub fn coex_schm_status_bit_set(@"type": u32, status: u32) callconv(.c) void { - log.debug("coex_schm_status_bit_set", .{}); - - if (coex_enabled) c.coex_schm_status_bit_set(@"type", status); - } - - pub fn coex_schm_interval_set(interval: u32) callconv(.c) c_int { - log.debug("coex_schm_interval_set", .{}); - - return if (coex_enabled) c.coex_schm_interval_set(interval) else 0; - } - - pub fn coex_schm_interval_get() callconv(.c) u32 { - log.debug("coex_schm_interval_get", .{}); - - return if (coex_enabled) c.coex_schm_interval_get() else 0; - } - - pub fn coex_schm_curr_period_get() callconv(.c) u8 { - log.debug("coex_schm_curr_period_get", .{}); - - return if (coex_enabled) c.coex_schm_curr_period_get() else 0; - } - - pub fn coex_schm_curr_phase_get() callconv(.c) ?*anyopaque { - log.debug("coex_schm_curr_phase_get", .{}); - - return if (coex_enabled) c.coex_schm_curr_phase_get() else null; - } - - pub fn coex_schm_process_restart() callconv(.c) c_int { - log.debug("coex_schm_process_restart", .{}); - - return if (coex_enabled) c.coex_schm_process_restart() else 0; - } - - pub fn coex_schm_register_cb( - @"type": c_int, - callback: ?*const fn (c_int) callconv(.c) c_int, - ) callconv(.c) c_int { - log.debug("coex_schm_register_cb", .{}); - - return if (coex_enabled) c.coex_schm_register_callback(@"type", callback) else 0; - } - - pub fn coex_register_start_cb(cb: ?*const fn () callconv(.c) c_int) callconv(.c) c_int { - log.debug("coex_register_start_cb", .{}); - - return if (coex_enabled) c.coex_register_start_cb(cb) else 0; - } - - extern fn ext_coex_schm_flexible_period_set(period: u8) i32; - pub fn coex_schm_flexible_period_set(period: u8) callconv(.c) c_int { - log.debug("coex_schm_flexible_period_set {}", .{period}); - - return if (coex_enabled) ext_coex_schm_flexible_period_set(period) else 0; - } - - extern fn ext_coex_schm_flexible_period_get() u8; - pub fn coex_schm_flexible_period_get() callconv(.c) u8 { - log.debug("coex_schm_flexible_period_get", .{}); - - return if (coex_enabled) ext_coex_schm_flexible_period_get() else 0; - } -}; - -fn c_result(err_code: i32) Error!void { - const InternalWifiError = enum(i32) { - /// Out of memory - esp_err_no_mem = 0x101, - - /// Invalid argument - esp_err_invalid_arg = 0x102, - - /// Wi_fi driver was not installed by esp_wifi_init - esp_err_wifi_not_init = 0x3001, - - /// Wi_fi driver was not started by esp_wifi_start - esp_err_wifi_not_started = 0x3002, - - /// Wi_fi driver was not stopped by esp_wifi_stop - esp_err_wifi_not_stopped = 0x3003, - - /// Wi_fi interface error - esp_err_wifi_if = 0x3004, - - /// Wi_fi mode error - esp_err_wifi_mode = 0x3005, - - /// Wi_fi internal state error - esp_err_wifi_state = 0x3006, - - /// Wi_fi internal control block of station or soft-AP error - esp_err_wifi_conn = 0x3007, - - /// Wi_fi internal NVS module error - esp_err_wifi_nvs = 0x3008, - - /// MAC address is invalid - esp_err_wifi_mac = 0x3009, - - /// SSID is invalid - esp_err_wifi_ssid = 0x300A, - - /// Password is invalid - esp_err_wifi_password = 0x300B, - - /// Timeout error - esp_err_wifi_timeout = 0x300C, - - /// Wi_fi is in sleep state(RF closed) and wakeup fail - esp_err_wifi_wake_fail = 0x300D, - - /// The caller would block - esp_err_wifi_would_block = 0x300E, - - /// Station still in disconnect status - esp_err_wifi_not_connect = 0x300F, - - /// Failed to post the event to Wi_fi task - esp_err_wifi_post = 0x3012, - - /// Invalid Wi_fi state when init/deinit is called - esp_err_wifi_init_state = 0x3013, - - /// Returned when Wi_fi is stopping - esp_err_wifi_stop_state = 0x3014, - - /// The Wi_fi connection is not associated - esp_err_wifi_not_assoc = 0x3015, - - /// The Wi_fi TX is disallowed - esp_err_wifi_tx_disallow = 0x3016, - }; - - if (err_code != c.ESP_OK) { - const err: InternalWifiError = @enumFromInt(err_code); - log.err("Internal wifi error occurred: {s}", .{@tagName(err)}); - return error.InternalWifiError; - } -} From 49f0e2421e50cd6990a65de58453d8a2394e0f27 Mon Sep 17 00:00:00 2001 From: tact1m4n3 Date: Wed, 23 Jul 2025 11:13:38 +0300 Subject: [PATCH 08/43] undo stack changes --- examples/espressif/esp/src/wifi.zig | 21 ------------------- .../esp/ld/esp32_c3/image_boot_sections.ld | 8 +------ port/espressif/esp/src/cpus/esp_riscv.zig | 3 +-- 3 files changed, 2 insertions(+), 30 deletions(-) diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index a25a0d03e..e2bb3113b 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -121,27 +121,6 @@ pub fn main() !void { last_mem_show = now; } hal.time.sleep_ms(10); - - // if (events.wifi.contains(.Connected)) { - // c.netif_set_link_up(&netif); - // } - // if (events.contains(.Disconnected)) { - // c.netif_set_link_down(&netif); - // } - // - // while (radio.wifi.recv_packet()) |packet| { - // netif.input.?(packet); - // } - // - // c.sys_check_timeouts(); - // - // const now = hal.time.get_time_since_boot(); - // if (!now.diff(last_mem_show).less_than(.from_ms(1000))) { - // const used_mem = 50 * 1024 - alloc.free_heap(); - // std.log.info("used memory: {}K ({})", .{ used_mem / 1024, used_mem }); - // last_mem_show = now; - // } - // hal.time.sleep_ms(100); } // var ssid: [1:0]u8 = @splat(0); diff --git a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld index 409bf6f45..e73d848f9 100644 --- a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld @@ -48,12 +48,6 @@ SECTIONS . += 16; } > IROM - .stack (NOLOAD) : ALIGN(4) - { - . += 0x8000; - end_of_stack = .; - } > DRAM - .ram_text : ALIGN(4) { . = ALIGN(4); @@ -80,7 +74,7 @@ SECTIONS .data.dummy (NOLOAD) : ALIGN(4) { - . = . + SIZEOF(.stack) + SIZEOF(.ram_text) + SIZEOF(.ram_text.wifi); + . = . + SIZEOF(.ram_text) + SIZEOF(.ram_text.wifi); } > DRAM .data : ALIGN(4) diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index 0eb15c729..003a80336 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -289,7 +289,6 @@ pub const startup_logic = switch (cpu_config.boot_mode) { const sections = struct { extern var microzig_bss_start: u8; extern var microzig_bss_end: u8; - extern var end_of_stack: u8; }; fn _start() callconv(.naked) noreturn { @@ -297,7 +296,7 @@ pub const startup_logic = switch (cpu_config.boot_mode) { \\mv sp, %[eos] \\jal _start_c : - : [eos] "r" (@as(u32, @intFromPtr(§ions.end_of_stack))), + : [eos] "r" (microzig.config.end_of_stack), ); } From 287ceeb7dd7abc6ee18fc477dcb250e86cca8e7c Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Wed, 26 Nov 2025 22:57:20 +0200 Subject: [PATCH 09/43] Remove pointer from fallback allocator interface --- core/src/Allocator.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/Allocator.zig b/core/src/Allocator.zig index 90d50a920..9c65f04c4 100644 --- a/core/src/Allocator.zig +++ b/core/src/Allocator.zig @@ -22,7 +22,7 @@ high_boundary: usize, /// An optional fallback allocator that is used when the allocator runs out of memory. /// This allows allocation from multiple disjoint memory regions. -fallback: ?*std.mem.Allocator = null, +fallback: ?std.mem.Allocator = null, /// A mutex used to protect access to the allocator. mutex: microzig.interrupt.Mutex = .{}, From 338099ba14050ee50a62bed0a63c71ccde523993 Mon Sep 17 00:00:00 2001 From: Andrea Coradi Date: Thu, 27 Nov 2025 07:09:40 +0100 Subject: [PATCH 10/43] Improve stability of ESP WiFi (#741) --- core/src/{Allocator.zig => allocator.zig} | 2 +- core/src/microzig.zig | 2 +- port/espressif/esp/src/hal/radio/osi.zig | 61 +++++++++++++---------- port/espressif/esp/src/hal/radio/wifi.zig | 7 ++- 4 files changed, 44 insertions(+), 28 deletions(-) rename core/src/{Allocator.zig => allocator.zig} (99%) diff --git a/core/src/Allocator.zig b/core/src/allocator.zig similarity index 99% rename from core/src/Allocator.zig rename to core/src/allocator.zig index 9c65f04c4..91e9e66bc 100644 --- a/core/src/Allocator.zig +++ b/core/src/allocator.zig @@ -22,7 +22,7 @@ high_boundary: usize, /// An optional fallback allocator that is used when the allocator runs out of memory. /// This allows allocation from multiple disjoint memory regions. -fallback: ?std.mem.Allocator = null, +fallback: ?*Alloc = null, /// A mutex used to protect access to the allocator. mutex: microzig.interrupt.Mutex = .{}, diff --git a/core/src/microzig.zig b/core/src/microzig.zig index 71dfcab19..67f78b92f 100644 --- a/core/src/microzig.zig +++ b/core/src/microzig.zig @@ -30,12 +30,12 @@ pub const board = if (config.has_board) @import("board") else void; /// Contains device-independent drivers for peripherial devices. pub const drivers = @import("drivers"); -pub const Allocator = @import("Allocator.zig"); pub const core = @import("core.zig"); pub const concurrency = @import("concurrency.zig"); pub const interrupt = @import("interrupt.zig"); pub const mmio = @import("mmio.zig"); pub const utilities = @import("utilities.zig"); +pub const Allocator = @import("allocator.zig"); /// The microzig default panic handler. Will disable interrupts and loop endlessly. pub const panic = std.debug.FullPanic(struct { diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 1be817888..811aca692 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -86,6 +86,10 @@ pub fn __assert_func( } pub fn malloc(len: usize) callconv(.c) ?*anyopaque { + // Avoid multiple allocations at the same time as it causes a panic + microzig.cpu.interrupt.disable_interrupts(); + defer microzig.cpu.interrupt.enable_interrupts(); + log.debug("malloc {}", .{len}); const buf = allocator.rawAlloc(@sizeOf(usize) + len, .@"4", @returnAddress()) orelse { @@ -93,7 +97,7 @@ pub fn malloc(len: usize) callconv(.c) ?*anyopaque { return null; }; - const alloc_len: *usize = @alignCast(@ptrCast(buf)); + const alloc_len: *usize = @ptrCast(@alignCast(buf)); alloc_len.* = len; return @ptrFromInt(@intFromPtr(buf) + @sizeOf(usize)); } @@ -108,12 +112,19 @@ pub fn calloc(number: usize, size: usize) callconv(.c) ?*anyopaque { } pub fn free(ptr: ?*anyopaque) callconv(.c) void { + // Avoid multiple frees at the same time as it causes a panic + microzig.cpu.interrupt.disable_interrupts(); + defer microzig.cpu.interrupt.enable_interrupts(); + log.debug("free {?}", .{ptr}); - std.debug.assert(ptr != null); + if (ptr == null) { + log.warn("ignoring free(null) called by 0x{x}", .{@returnAddress()}); + return; + } const buf_ptr: [*]u8 = @ptrFromInt(@intFromPtr(ptr) - @sizeOf(usize)); - const buf_len: *usize = @alignCast(@ptrCast(buf_ptr)); + const buf_len: *usize = @ptrCast(@alignCast(buf_ptr)); allocator.rawFree(buf_ptr[0 .. @sizeOf(usize) + buf_len.*], .@"4", @returnAddress()); } @@ -227,7 +238,7 @@ pub fn set_isr( // bellow. wifi_interrupt_handler = .{ - .f = @alignCast(@ptrCast(f)), + .f = @ptrCast(@alignCast(f)), .arg = arg, }; }, @@ -309,7 +320,7 @@ pub fn semphr_create(max_value: u32, init_value: u32) callconv(.c) ?*anyopaque { pub fn semphr_delete(ptr: ?*anyopaque) callconv(.c) void { log.debug("semphr_delete {?}", .{ptr}); - allocator.destroy(@as(*u32, @alignCast(@ptrCast(ptr)))); + allocator.destroy(@as(*u32, @ptrCast(@alignCast(ptr)))); } pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { @@ -319,7 +330,7 @@ pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { const timeout = tick; const start = hal.time.get_time_since_boot(); - const sem: *u32 = @alignCast(@ptrCast(ptr)); + const sem: *u32 = @ptrCast(@alignCast(ptr)); while (true) { const res: bool = blk: { @@ -356,7 +367,7 @@ pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { pub fn semphr_give(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("semphr_give {?}", .{ptr}); - const sem: *u32 = @alignCast(@ptrCast(ptr)); + const sem: *u32 = @ptrCast(@alignCast(ptr)); const cs = enter_critical_section(); defer cs.leave(); @@ -405,14 +416,14 @@ pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { pub fn mutex_delete(ptr: ?*anyopaque) callconv(.c) void { log.debug("mutex_delete {?}", .{ptr}); - const mutex: *Mutex = @alignCast(@ptrCast(ptr)); + const mutex: *Mutex = @ptrCast(@alignCast(ptr)); allocator.destroy(mutex); } pub fn mutex_lock(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("mutex lock {?}", .{ptr}); - const mutex: *Mutex = @alignCast(@ptrCast(ptr)); + const mutex: *Mutex = @ptrCast(@alignCast(ptr)); const cur_task_id: usize = @intFromPtr(multitasking.current_task); while (true) { @@ -446,7 +457,7 @@ pub fn mutex_lock(ptr: ?*anyopaque) callconv(.c) i32 { pub fn mutex_unlock(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("mutex unlock {?}", .{ptr}); - const mutex: *Mutex = @alignCast(@ptrCast(ptr)); + const mutex: *Mutex = @ptrCast(@alignCast(ptr)); const cs = enter_critical_section(); defer cs.leave(); @@ -541,14 +552,14 @@ pub fn queue_create(capacity: u32, item_len: u32) callconv(.c) ?*anyopaque { pub fn queue_delete(ptr: ?*anyopaque) callconv(.c) void { log.debug("queue_delete {?}", .{ptr}); - const queue: *Queue = @alignCast(@ptrCast(ptr)); + const queue: *Queue = @ptrCast(@alignCast(ptr)); queue.destroy(); } // NOTE: here we ignore the timeout. The rust version doesn't use it. fn queue_send_common(ptr: ?*anyopaque, item_ptr: ?*anyopaque) callconv(.c) i32 { - const queue: *Queue = @alignCast(@ptrCast(ptr)); - const item: [*]const u8 = @alignCast(@ptrCast(item_ptr)); + const queue: *Queue = @ptrCast(@alignCast(ptr)); + const item: [*]const u8 = @ptrCast(@alignCast(item_ptr)); const cs = enter_critical_section(); defer cs.leave(); @@ -570,7 +581,7 @@ pub fn queue_send(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) pub fn queue_send_from_isr(ptr: ?*anyopaque, item_ptr: ?*anyopaque, _hptw: ?*anyopaque) callconv(.c) i32 { log.debug("queue_send_from_isr {?} {?} {?}", .{ ptr, item_ptr, _hptw }); - @as(*u32, @alignCast(@ptrCast(_hptw))).* = 1; + @as(*u32, @ptrCast(@alignCast(_hptw))).* = 1; return queue_send_common(ptr, item_ptr); } @@ -589,8 +600,8 @@ pub fn queue_recv(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) const timeout = block_time_tick; const start = hal.time.get_time_since_boot(); - const queue: *Queue = @alignCast(@ptrCast(ptr)); - const item: [*]u8 = @alignCast(@ptrCast(item_ptr)); + const queue: *Queue = @ptrCast(@alignCast(ptr)); + const item: [*]u8 = @ptrCast(@alignCast(item_ptr)); while (true) { { @@ -616,7 +627,7 @@ pub fn queue_recv(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) pub fn queue_msg_waiting(ptr: ?*anyopaque) callconv(.c) u32 { log.debug("queue_msg_waiting {?}", .{ptr}); - const queue: *Queue = @alignCast(@ptrCast(ptr)); + const queue: *Queue = @ptrCast(@alignCast(ptr)); return queue.len; } @@ -655,7 +666,7 @@ fn task_create_common( const task: *multitasking.Task = multitasking.Task.create( allocator, - @alignCast(@ptrCast(task_func)), + @ptrCast(@alignCast(task_func)), param, stack_depth, ) catch { @@ -664,7 +675,7 @@ fn task_create_common( }; multitasking.schedule_task(task); - @as(*usize, @alignCast(@ptrCast(task_handle))).* = @intFromPtr(task); + @as(*usize, @ptrCast(@alignCast(task_handle))).* = @intFromPtr(task); return 1; } @@ -963,7 +974,7 @@ pub fn timer_arm(ets_timer_ptr: ?*anyopaque, ms: u32, repeat: bool) callconv(.c) pub fn timer_disarm(ets_timer_ptr: ?*anyopaque) callconv(.c) void { log.debug("timer_disarm {?}", .{ets_timer_ptr}); - const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); + const ets_timer: *c.ets_timer = @ptrCast(@alignCast(ets_timer_ptr)); const cs = enter_critical_section(); defer cs.leave(); @@ -978,7 +989,7 @@ pub fn timer_disarm(ets_timer_ptr: ?*anyopaque) callconv(.c) void { pub fn timer_done(ets_timer_ptr: ?*anyopaque) callconv(.c) void { log.debug("timer_done {?}", .{ets_timer_ptr}); - const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); + const ets_timer: *c.ets_timer = @ptrCast(@alignCast(ets_timer_ptr)); const cs = enter_critical_section(); defer cs.leave(); @@ -995,8 +1006,8 @@ pub fn timer_done(ets_timer_ptr: ?*anyopaque) callconv(.c) void { pub fn timer_setfn(ets_timer_ptr: ?*anyopaque, callback_ptr: ?*anyopaque, arg: ?*anyopaque) callconv(.c) void { log.debug("timer_setfn {?} {?} {?}", .{ ets_timer_ptr, callback_ptr, arg }); - const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); - const callback: timer.CallbackFn = @alignCast(@ptrCast(callback_ptr)); + const ets_timer: *c.ets_timer = @ptrCast(@alignCast(ets_timer_ptr)); + const callback: timer.CallbackFn = @ptrCast(@alignCast(callback_ptr)); const cs = enter_critical_section(); defer cs.leave(); @@ -1022,7 +1033,7 @@ pub fn timer_setfn(ets_timer_ptr: ?*anyopaque, callback_ptr: ?*anyopaque, arg: ? pub fn timer_arm_us(ets_timer_ptr: ?*anyopaque, us: u32, repeat: bool) callconv(.c) void { log.debug("timer_arm_us {?} {} {}", .{ ets_timer_ptr, us, repeat }); - const ets_timer: *c.ets_timer = @alignCast(@ptrCast(ets_timer_ptr)); + const ets_timer: *c.ets_timer = @ptrCast(@alignCast(ets_timer_ptr)); // TODO: locking const cs = enter_critical_section(); @@ -1196,7 +1207,7 @@ pub fn wifi_delete_queue(ptr: ?*anyopaque) callconv(.c) void { std.debug.assert(ptr == @as(?*anyopaque, @ptrCast(wifi_queue_handle))); - const queue: *Queue = @alignCast(@ptrCast(ptr)); + const queue: *Queue = @ptrCast(@alignCast(ptr)); queue.destroy(); wifi_queue_handle = null; diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index 06a2d84da..c51ab0ce7 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -516,7 +516,7 @@ pub fn send_packet(iface: Interface, data: []const u8) (error{TooManyPacketsInFl _ = @atomicRmw(usize, &packets_in_flight, .Add, 1, .acq_rel); errdefer _ = @atomicRmw(usize, &packets_in_flight, .Sub, 1, .acq_rel); - try c_result(c.esp_wifi_internal_tx(@intFromEnum(iface), @constCast(@ptrCast(data.ptr)), @intCast(data.len))); + try c_result(c.esp_wifi_internal_tx(@intFromEnum(iface), @ptrCast(@constCast(data.ptr)), @intCast(data.len))); } fn tx_done_cb( @@ -527,6 +527,11 @@ fn tx_done_cb( ) callconv(.c) void { log.debug("tx_done_cb", .{}); + if (packets_in_flight == 0) { + log.warn("ignoring tx_done_cb as there are no packets in flight", .{}); + return; + } + _ = @atomicRmw(usize, &packets_in_flight, .Sub, 1, .acq_rel); } From 9528da2571e4dd4099c39b2e777b013409fbeb0f Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Tue, 9 Dec 2025 19:36:25 +0200 Subject: [PATCH 11/43] Progress --- examples/espressif/esp/build.zig | 17 +- examples/espressif/esp/src/scheduler.zig | 78 ++++ .../esp/ld/esp32_c3/direct_boot_sections.ld | 7 + .../esp/ld/esp32_c3/flashless_sections.ld | 7 + .../esp/ld/esp32_c3/image_boot_sections.ld | 7 + port/espressif/esp/src/cpus/esp_riscv.zig | 163 +++----- port/espressif/esp/src/hal.zig | 5 +- port/espressif/esp/src/hal/Scheduler.zig | 347 ++++++++++++++++++ port/espressif/esp/src/hal/radio/osi.zig | 6 +- 9 files changed, 509 insertions(+), 128 deletions(-) create mode 100644 examples/espressif/esp/src/scheduler.zig create mode 100644 port/espressif/esp/src/hal/Scheduler.zig diff --git a/examples/espressif/esp/build.zig b/examples/espressif/esp/build.zig index 0d89f2549..ab08c20e7 100644 --- a/examples/espressif/esp/build.zig +++ b/examples/espressif/esp/build.zig @@ -24,17 +24,18 @@ pub fn build(b: *std.Build) void { .{ .name = "stepper_driver_dumb", .file = "src/stepper_driver_dumb.zig" }, .{ .name = "systimer", .file = "src/systimer.zig" }, .{ .name = "ws2812_blinky", .file = "src/ws2812_blinky.zig" }, + .{ .name = "scheduler", .file = "src/scheduler.zig" }, }; const targeted_examples: []const TargetedExample = &.{ - .{ - .target = .esp32_c3, - .example = .{ - .name = "wifi", - .file = "src/wifi.zig", - .features = .{ .lwip = true }, - }, - }, + // .{ + // .target = .esp32_c3, + // .example = .{ + // .name = "wifi", + // .file = "src/wifi.zig", + // .features = .{ .lwip = true }, + // }, + // }, }; for (examples) |example| { diff --git a/examples/espressif/esp/src/scheduler.zig b/examples/espressif/esp/src/scheduler.zig new file mode 100644 index 000000000..161803491 --- /dev/null +++ b/examples/espressif/esp/src/scheduler.zig @@ -0,0 +1,78 @@ +const std = @import("std"); +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; +const esp = microzig.hal; +const gpio = esp.gpio; +const systimer = esp.systimer; +const usb_serial_jtag = esp.usb_serial_jtag; + +pub const microzig_options: microzig.Options = .{ + .logFn = usb_serial_jtag.logger.log, + .interrupts = .{ + .interrupt1 = timer_interrupt, + }, + .log_level = .debug, +}; + +const alarm = systimer.alarm(0); + +fn timer_interrupt(_: *microzig.cpu.TrapFrame) linksection(".ram_text") callconv(.c) void { + std.log.info("timer interrupt!", .{}); + + alarm.clear_interrupt(); +} + +var scheduler: esp.Scheduler = undefined; +var mutex: esp.Scheduler.Mutex = .init; + +fn task1(_: ?*anyopaque) callconv(.c) void { + mutex.lock(&scheduler); + std.log.info("task 1", .{}); + scheduler.yield(.{}); + std.log.info("task 1 end", .{}); + mutex.unlock(&scheduler); +} + +pub fn main() !void { + std.log.info("hello world!", .{}); + + // unit0 is already enabled as it is used by `hal.time`. + alarm.set_unit(.unit0); + + // sets the period to one second. + alarm.set_period(@intCast(1_000_000 * systimer.ticks_per_us())); + + // to enable period mode you have to first clear the mode bit. + alarm.set_mode(.target); + alarm.set_mode(.period); + + alarm.set_interrupt_enabled(true); + alarm.set_enabled(true); + + microzig.cpu.interrupt.set_priority_threshold(.zero); + + microzig.cpu.interrupt.set_type(.interrupt1, .level); + microzig.cpu.interrupt.set_priority(.interrupt1, .highest); + microzig.cpu.interrupt.map(.systimer_target0, .interrupt1); + microzig.cpu.interrupt.enable(.interrupt1); + + esp.time.sleep_ms(1000); + + var heap = microzig.Allocator.init_with_heap(4096); + const allocator = heap.allocator(); + scheduler.init(allocator); + try scheduler.raw_alloc_spawn_with_options(task1, null, .{}); + + mutex.lock(&scheduler); + std.log.info("main", .{}); + scheduler.yield(.{}); + std.log.info("main revisited", .{}); + scheduler.yield(.{}); + std.log.info("main ending", .{}); + scheduler.yield(.{}); + mutex.unlock(&scheduler); + + while (true) { + microzig.cpu.wfi(); + } +} diff --git a/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld index 359acde8d..9ace43e17 100644 --- a/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld @@ -68,6 +68,13 @@ SECTIONS microzig_bss_end = .; } > DRAM + .heap (NOLOAD) : + { + microzig_heap_start = .; + . = ORIGIN(DRAM) + LENGTH(DRAM); + microzig_heap_end = .; + } > DRAM + microzig_data_load_start = ORIGIN(DROM) + _irom_size + _drom_size; PROVIDE(__global_pointer$ = microzig_data_start + 0x800); } diff --git a/port/espressif/esp/ld/esp32_c3/flashless_sections.ld b/port/espressif/esp/ld/esp32_c3/flashless_sections.ld index c8dd6e43f..b38ad1ea9 100644 --- a/port/espressif/esp/ld/esp32_c3/flashless_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/flashless_sections.ld @@ -49,5 +49,12 @@ SECTIONS microzig_bss_end = .; } > DRAM + .heap (NOLOAD) : + { + microzig_heap_start = .; + . = ORIGIN(DRAM) + LENGTH(DRAM); + microzig_heap_end = .; + } > DRAM + PROVIDE(__global_pointer$ = microzig_data_start + 0x800); } diff --git a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld index f8fdf3ca6..42f10a85e 100644 --- a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld @@ -99,5 +99,12 @@ SECTIONS microzig_bss_end = .; } > DRAM + .heap (NOLOAD) : + { + microzig_heap_start = .; + . = ORIGIN(DRAM) + LENGTH(DRAM); + microzig_heap_end = .; + } > DRAM + PROVIDE(__global_pointer$ = microzig_data_start + 0x800); } diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index c3725f053..cc5dd94cd 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -340,25 +340,6 @@ pub const TrapFrame = extern struct { a5: usize, a6: usize, a7: usize, - s0: usize, - s1: usize, - s2: usize, - s3: usize, - s4: usize, - s5: usize, - s6: usize, - s7: usize, - s8: usize, - s9: usize, - s10: usize, - s11: usize, - gp: usize, - tp: usize, - sp: usize, - pc: usize, - mstatus: usize, - mcause: usize, - mtval: usize, }; fn _vector_table() align(256) linksection(".ram_text") callconv(.naked) void { @@ -376,8 +357,7 @@ fn _vector_table() align(256) linksection(".ram_text") callconv(.naked) void { ); } - @export(&_update_priority, .{ .name = "_update_priority" }); - @export(&_restore_priority, .{ .name = "_restore_priority" }); + @export(&_handle_interrupt, .{ .name = "_handle_interrupt" }); } const interrupts_jump_asm = comptime blk: { @@ -398,162 +378,162 @@ fn _vector_table() align(256) linksection(".ram_text") callconv(.naked) void { \\ ++ interrupts_jump_asm ++ \\exception: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _exception_handler \\ j trap_common \\interrupt1: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt1_handler \\ j trap_common \\interrupt2: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt2_handler \\ j trap_common \\interrupt3: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt3_handler \\ j trap_common \\interrupt4: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt4_handler \\ j trap_common \\interrupt5: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt5_handler \\ j trap_common \\interrupt6: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt6_handler \\ j trap_common \\interrupt7: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt7_handler \\ j trap_common \\interrupt8: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt8_handler \\ j trap_common \\interrupt9: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt9_handler \\ j trap_common \\interrupt10: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt10_handler \\ j trap_common \\interrupt11: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt11_handler \\ j trap_common \\interrupt12: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt12_handler \\ j trap_common \\interrupt13: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt13_handler \\ j trap_common \\interrupt14: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt14_handler \\ j trap_common \\interrupt15: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt15_handler \\ j trap_common \\interrupt16: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt16_handler \\ j trap_common \\interrupt17: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt17_handler \\ j trap_common \\interrupt18: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt18_handler \\ j trap_common \\interrupt19: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt19_handler \\ j trap_common \\interrupt20: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt20_handler \\ j trap_common \\interrupt21: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt21_handler \\ j trap_common \\interrupt22: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt22_handler \\ j trap_common \\interrupt23: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt23_handler \\ j trap_common \\interrupt24: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt24_handler \\ j trap_common \\interrupt25: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt25_handler \\ j trap_common \\interrupt26: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt26_handler \\ j trap_common \\interrupt27: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt27_handler \\ j trap_common \\interrupt28: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt28_handler \\ j trap_common \\interrupt29: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt29_handler \\ j trap_common \\interrupt30: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt30_handler \\ j trap_common \\interrupt31: - \\ addi sp, sp, -35*4 + \\ addi sp, sp, -16*4 \\ sw ra, 0(sp) \\ la ra, _interrupt31_handler \\ j trap_common @@ -573,47 +553,10 @@ fn _vector_table() align(256) linksection(".ram_text") callconv(.naked) void { \\ sw a5, 13*4(sp) \\ sw a6, 14*4(sp) \\ sw a7, 15*4(sp) - \\ sw s0, 16*4(sp) - \\ sw s1, 17*4(sp) - \\ sw s2, 18*4(sp) - \\ sw s3, 19*4(sp) - \\ sw s4, 20*4(sp) - \\ sw s5, 21*4(sp) - \\ sw s6, 22*4(sp) - \\ sw s7, 23*4(sp) - \\ sw s8, 24*4(sp) - \\ sw s9, 25*4(sp) - \\ sw s10, 26*4(sp) - \\ sw s11, 27*4(sp) - \\ sw gp, 28*4(sp) - \\ sw tp, 29*4(sp) - \\ addi t1, sp, 35*4 # what sp was on entry to trap - \\ sw t1, 30*4(sp) - \\ csrrs t1, mepc, x0 - \\ sw t1, 31*4(sp) - \\ csrrs t1, mstatus, x0 - \\ sw t1, 32*4(sp) - \\ csrrs t1, mcause, x0 - \\ sw t1, 33*4(sp) - \\ csrrs t1, mtval, x0 - \\ sw t1, 34*4(sp) - \\ - \\ mv s0, ra # Save address of handler function - \\ - \\ jal ra, _update_priority - \\ mv s1, a0 # Save the return of _update_priority \\ \\ mv a0, sp # Pass a pointer to TrapFrame to the handler function - \\ jalr ra, s0 # Call the handler function - \\ - \\ mv a0, s1 # Pass stored priority to _restore_priority - \\ jal ra, _restore_priority - \\ - \\ lw t1, 31*4(sp) - \\ csrrw x0, mepc, t1 - \\ - \\ lw t1, 32*4(sp) - \\ csrrw x0, mstatus, t1 + \\ mv a1, ra # Save address of handler function + \\ jal ra, _handle_interrupt \\ \\ lw ra, 0*4(sp) \\ lw t0, 1*4(sp) @@ -631,21 +574,8 @@ fn _vector_table() align(256) linksection(".ram_text") callconv(.naked) void { \\ lw a5, 13*4(sp) \\ lw a6, 14*4(sp) \\ lw a7, 15*4(sp) - \\ lw s0, 16*4(sp) - \\ lw s1, 17*4(sp) - \\ lw s2, 18*4(sp) - \\ lw s3, 19*4(sp) - \\ lw s4, 20*4(sp) - \\ lw s5, 21*4(sp) - \\ lw s6, 22*4(sp) - \\ lw s7, 23*4(sp) - \\ lw s8, 24*4(sp) - \\ lw s9, 25*4(sp) - \\ lw s10, 26*4(sp) - \\ lw s11, 27*4(sp) - \\ lw gp, 28*4(sp) - \\ lw tp, 29*4(sp) - \\ lw sp, 30*4(sp) # This removes the frame we allocated from the stack + \\ + \\ addi sp, sp, 16*4 # This removes the frame we allocated from the stack \\ \\ mret ); @@ -658,17 +588,22 @@ fn unhandled(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { std.log.err("unhandled interrupt {} occurred!", .{mcause.code}); } else { const exception: Exception = @enumFromInt(mcause.code); - std.log.err("exception {s} occurred!", .{@tagName(exception)}); + std.log.err("unhandled exception {s} occurred at {x}!", .{@tagName(exception), csr.mepc.read_raw()}); + + switch (exception) { + .InstructionFault => std.log.err("faulting address: {x}", .{csr.mtval.read_raw()}), + .LoadFault => std.log.err("faulting address: {x}", .{csr.mtval.read_raw()}), + else => {}, + } } @panic("unhandled trap"); } -fn _update_priority() linksection(".ram_text") callconv(.c) u32 { - const mcause = csr.mcause.read(); - +fn _handle_interrupt(trap_frame: *TrapFrame, handler: InterruptHandler) linksection(".ram_text") callconv(.c) void { const prev_priority = interrupt.get_priority_threshold(); + const mcause = csr.mcause.read(); if (mcause.is_interrupt != 0) { // this is an interrupt (can also be exception in which case we don't enable interrupts). @@ -682,12 +617,10 @@ fn _update_priority() linksection(".ram_text") callconv(.c) u32 { } } - return @intFromEnum(prev_priority); -} + handler(trap_frame); -fn _restore_priority(priority_raw: u32) linksection(".ram_text") callconv(.c) void { interrupt.disable_interrupts(); - interrupt.set_priority_threshold(@enumFromInt(priority_raw)); + interrupt.set_priority_threshold(prev_priority); } pub const csr = struct { diff --git a/port/espressif/esp/src/hal.zig b/port/espressif/esp/src/hal.zig index f7c1b1b23..2e4b36287 100644 --- a/port/espressif/esp/src/hal.zig +++ b/port/espressif/esp/src/hal.zig @@ -3,6 +3,7 @@ const microzig = @import("microzig"); pub const esp_image = @import("esp_image"); +pub const Scheduler = @import("hal/Scheduler.zig"); pub const cache = @import("hal/cache.zig"); pub const clocks = @import("hal/clocks.zig"); pub const compatibility = @import("hal/compatibility.zig"); @@ -10,7 +11,7 @@ pub const drivers = @import("hal/drivers.zig"); pub const gpio = @import("hal/gpio.zig"); pub const efuse = @import("hal/efuse.zig"); pub const i2c = @import("hal/i2c.zig"); -pub const radio = @import("hal/radio.zig"); +// pub const radio = @import("hal/radio.zig"); pub const ledc = @import("hal/ledc.zig"); pub const rng = @import("hal/rng.zig"); pub const rom = @import("hal/rom.zig"); @@ -34,7 +35,7 @@ pub const HAL_Options = struct { secure_version: u32 = 0, version: []const u8 = "0.0.0", } = .{}, - radio: ?radio.Options = null, + // radio: ?radio.Options = null, }; /// Clock config applied by the default `init()` function of the hal. diff --git a/port/espressif/esp/src/hal/Scheduler.zig b/port/espressif/esp/src/hal/Scheduler.zig new file mode 100644 index 000000000..0a192eb68 --- /dev/null +++ b/port/espressif/esp/src/hal/Scheduler.zig @@ -0,0 +1,347 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const microzig = @import("microzig"); +const TrapFrame = microzig.cpu.TrapFrame; +const SYSTEM = microzig.chip.peripherals.SYSTEM; + +const Scheduler = @This(); + +// TODO: Custom wrapper handlers for interrupts that switch to an interrupt +// stack + +allocator: std.mem.Allocator = undefined, +ready_queue: ?*Task = null, +main_task: Task = undefined, +/// SAFETY: we don't need optionals because there will always be the main task +/// running. It can't be deleted. +current_task: *Task = undefined, + +pub const Task = struct { + // NOTE: I believe this ensures an alignment for the struct, useful because + // some bits for instance in the mutex state are mark whether it is locked + // or unlocked + _: void align(4) = {}, + context: Context, + async_closure: AsyncClosure, + stack: []u8, + queue_next: ?*Task = null, + + pub fn init(function: *const fn (param: ?*anyopaque) callconv(.c) void, param: ?*anyopaque, stack: []u8) Task { + return .{ + .context = .{ + .sp = stack.ptr[stack.len..], + .pc = AsyncClosure.call, + }, + .stack = stack, + .async_closure = .{ + .entry = function, + .param = param, + }, + }; + } +}; + +pub fn init(scheduler: *Scheduler, allocator: Allocator) void { + scheduler.allocator = allocator; + scheduler.main_task = .{ + .context = undefined, + .async_closure = undefined, + .stack = &.{}, + }; + scheduler.current_task = &scheduler.main_task; +} + +// TODO: implement ziggy spawn + +pub const SpawnOptions = struct { + stack_size: usize = 4096, +}; + +pub fn raw_alloc_spawn_with_options( + scheduler: *Scheduler, + function: *const fn (param: ?*anyopaque) callconv(.c) void, + param: ?*anyopaque, + options: SpawnOptions, +) !void { + // TODO: maybe find a way to only do one allocation. This is a bit tricky + // because we want to also support statically allocated tasks, maybe based + // on the not yet available @stackSize builtin + + const STACK_ALIGN: std.mem.Alignment = .@"16"; + const stack_size = std.mem.alignForward(usize, options.stack_size, STACK_ALIGN.toByteUnits()); + const stack = try scheduler.allocator.alignedAlloc(u8, STACK_ALIGN, stack_size); + errdefer scheduler.allocator.free(stack); + + const task = try scheduler.allocator.create(Task); + errdefer scheduler.allocator.destroy(task); + + task.* = .init(function, param, stack); + scheduler.schedule(task); +} + +pub fn schedule(scheduler: *Scheduler, task: *Task) void { + const first_task = scheduler.ready_queue; + +} + +const AsyncClosure = extern struct { + entry: *const fn (param: ?*anyopaque) callconv(.c) void, + param: ?*anyopaque, + + pub fn call(contexts: *const @FieldType(SwitchMessage, "contexts")) callconv(.c) noreturn { + const message: *const SwitchMessage = @fieldParentPtr("contexts", contexts); + message.handle(); + + const task: *Task = @fieldParentPtr("context", contexts.next); + task.async_closure.entry(task.async_closure.param); + while (true) message.scheduler.yield_with_action(.exit); + } +}; + +pub fn yield(scheduler: *Scheduler, options: struct { + task: ?*Task = null, + action: SwitchMessage.Action = .nothing, +}) void { + const cs = microzig.interrupt.enter_critical_section(); + + const node = options.task orelse scheduler.find_ready_task() orelse { + cs.leave(); + // TODO: maybe switch to idle task if not called in isr + return; + }; + const next_task: *Task = @fieldParentPtr("node", node); + + const message: SwitchMessage = .{ + .scheduler = scheduler, + .contexts = .{ + .prev = &scheduler.current_task.context, + .next = &next_task.context, + }, + .action = options.action, + .cs = cs, + }; + context_switch(&message).handle(); +} + +pub fn mutex_lock(scheduler: *Scheduler, prev_state: Mutex.State, mutex: *Mutex) void { + scheduler.yield(.{ .action = .{ .mutex_lock = .{ + .prev_state = prev_state, + .mutex = mutex, + } } }); +} + +pub fn mutex_unlock(scheduler: *Scheduler, _: Mutex.State, mutex: *Mutex) void { + // NOTE: only gets called if mutex is contented, very simplified + // implementation because single core (esp32c3 only for now) + + const waiting_task = blk: { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + const waiting_task: *Task = @ptrFromInt(@intFromEnum(mutex.state)); + mutex.state = @enumFromInt(@intFromPtr(waiting_task.queue_next)); + break :blk waiting_task; + }; + + scheduler.yield(.{ + .action = .nothing, + .task = waiting_task, + }); +} + +pub const SwitchMessage = struct { + scheduler: *Scheduler, + contexts: extern struct { + prev: *Context, + next: *Context, + }, + action: Action, + cs: microzig.interrupt.CriticalSection, + + pub const Action = union(enum) { + nothing, + mutex_lock: struct { + prev_state: Mutex.State, + mutex: *Mutex, + }, + exit, + }; + + pub fn handle(message: *const SwitchMessage) void { + defer message.cs.leave(); + + const scheduler = message.scheduler; + const prev_task: *Task = @fieldParentPtr("context", message.contexts.prev); + const next_task: *Task = @fieldParentPtr("context", message.contexts.next); + + scheduler.current_task = next_task; + + switch (message.action) { + .nothing => { + scheduler.schedule(prev_task); + }, + .mutex_lock => |state| { + if (state.prev_state == .unlocked) { + state.mutex.state = .locked_once; + prev_task.queue_next = null; + scheduler.schedule(prev_task); + } else { + const maybe_first_awaiter: ?*Task = @ptrFromInt(@intFromEnum(state.mutex.state)); + if (maybe_first_awaiter) |first_awaiter| { + var last_awaiter = first_awaiter; + while (true) last_awaiter = last_awaiter.queue_next orelse break; + last_awaiter.queue_next = prev_task; + } else { + state.mutex.state = @enumFromInt(@intFromPtr(prev_task)); + } + } + }, + .exit => { + scheduler.allocator.free(prev_task.stack); + scheduler.allocator.destroy(prev_task); + }, + } + } +}; + +fn find_ready_task(scheduler: *Scheduler) ?*Task { + if (scheduler.ready_queue) |next_task| { + scheduler.ready_queue = next_task.queue_next; + return next_task; + } + return null; +} + +pub const Context = extern struct { + pc: ?*const anyopaque, + sp: ?*anyopaque, +}; + +inline fn context_switch(message: *const SwitchMessage) *const SwitchMessage { + return @fieldParentPtr("contexts", asm volatile ( + \\lw t0, 0(a0) # prev context + \\lw t1, 4(a0) # next context + \\ + \\la t2, ret + \\sw t2, 0(t0) # save return pc + \\ + \\sw sp, 4(t0) # save prev stack pointer + \\ + \\lw t2, 0(t1) # load next pc + \\lw sp, 4(t1) # load next stack pointer + \\jr t2 # jump to next task + \\ret: + \\ + : [received_message] "={a0}" (-> *const @FieldType(SwitchMessage, "contexts")), + : [message_to_send] "{a0}" (&message.contexts), + : .{ + .x1 = true, // ra + .x3 = true, + .x4 = true, + .x5 = true, + .x6 = true, + .x7 = true, + .x8 = true, + .x9 = true, + .x10 = true, + .x11 = true, + .x12 = true, + .x13 = true, + .x14 = true, + .x15 = true, + .x16 = true, + .x17 = true, + .x18 = true, + .x19 = true, + .x20 = true, + .x21 = true, + .x22 = true, + .x23 = true, + .x24 = true, + .x25 = true, + .x26 = true, + .x27 = true, + .x28 = true, + .x29 = true, + .x30 = true, + .x31 = true, + .memory = true, + })); +} + +pub const Mutex = struct { + state: State, + + pub const State = enum(usize) { + locked_once = 0b00, + unlocked = 0b01, + contended = 0b10, + /// contended + _, + + pub fn isUnlocked(state: State) bool { + return @intFromEnum(state) & @intFromEnum(State.unlocked) == @intFromEnum(State.unlocked); + } + }; + + pub const init: Mutex = .{ .state = .unlocked }; + + pub fn tryLock(mutex: *Mutex) bool { + const prev_state: State = @enumFromInt(@atomicRmw( + usize, + @as(*usize, @ptrCast(&mutex.state)), + .And, + ~@intFromEnum(State.unlocked), + .acquire, + )); + return prev_state.isUnlocked(); + } + + pub fn lock(mutex: *Mutex, scheduler: *Scheduler) void { + const prev_state: State = @enumFromInt(@atomicRmw( + usize, + @as(*usize, @ptrCast(&mutex.state)), + .And, + ~@intFromEnum(State.unlocked), + .acquire, + )); + if (prev_state.isUnlocked()) { + @branchHint(.likely); + return; + } + scheduler.mutex_lock(prev_state, mutex); + } + + pub fn unlock(mutex: *Mutex, scheduler: *Scheduler) void { + const prev_state = @cmpxchgWeak(State, &mutex.state, .locked_once, .unlocked, .release, .acquire) orelse { + @branchHint(.likely); + return; + }; + std.debug.assert(prev_state != .unlocked); + return scheduler.mutex_unlock(prev_state, mutex); + } +}; + +pub const Condition = struct { + state: u64 = 0, + + pub fn wait(cond: *Condition, scheduler: *Scheduler, mutex: *Mutex) void { + return scheduler.condition_wait(cond, mutex); + } + + pub fn signal(cond: *Condition, scheduler: *Scheduler) void { + scheduler.condition_wake(cond, .one); + } + + pub fn broadcast(cond: *Condition, scheduler: *Scheduler) void { + scheduler.condition_wake(cond, .all); + } + + pub const Wake = enum { + /// Wake up only one thread. + one, + /// Wake up all threads. + all, + }; +}; diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 811aca692..89819d355 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -92,18 +92,18 @@ pub fn malloc(len: usize) callconv(.c) ?*anyopaque { log.debug("malloc {}", .{len}); - const buf = allocator.rawAlloc(@sizeOf(usize) + len, .@"4", @returnAddress()) orelse { + const buf = allocator.rawAlloc(8 + len, .@"8", @returnAddress()) orelse { log.warn("failed to allocate memory: {}", .{len}); return null; }; const alloc_len: *usize = @ptrCast(@alignCast(buf)); alloc_len.* = len; - return @ptrFromInt(@intFromPtr(buf) + @sizeOf(usize)); + return @ptrFromInt(@intFromPtr(buf) + 8); } pub fn calloc(number: usize, size: usize) callconv(.c) ?*anyopaque { - const total_size: usize = number * size; + const total_size: usize = number * size - 8; if (malloc(total_size)) |ptr| { @memset(@as([*]u8, @ptrCast(ptr))[0..total_size], 0); return ptr; From 9895b3dbfca9cd48ff9eb500559b747745277dbe Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Sat, 27 Dec 2025 18:55:56 +0200 Subject: [PATCH 12/43] Add scheduler and more --- examples/espressif/esp/src/scheduler.zig | 107 +-- .../esp/ld/esp32_c3/direct_boot_sections.ld | 1 + .../esp/ld/esp32_c3/flashless_sections.ld | 1 + .../esp/ld/esp32_c3/image_boot_sections.ld | 1 + port/espressif/esp/src/cpus/esp_riscv.zig | 312 +++---- port/espressif/esp/src/hal/Scheduler.zig | 785 ++++++++++++------ port/espressif/esp/src/hal/radio/timer.zig | 3 - port/espressif/esp/src/hal/systimer.zig | 12 +- 8 files changed, 743 insertions(+), 479 deletions(-) diff --git a/examples/espressif/esp/src/scheduler.zig b/examples/espressif/esp/src/scheduler.zig index 161803491..8bbed4ba6 100644 --- a/examples/espressif/esp/src/scheduler.zig +++ b/examples/espressif/esp/src/scheduler.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const log = std.log; const microzig = @import("microzig"); const peripherals = microzig.chip.peripherals; const esp = microzig.hal; @@ -9,70 +10,78 @@ const usb_serial_jtag = esp.usb_serial_jtag; pub const microzig_options: microzig.Options = .{ .logFn = usb_serial_jtag.logger.log, .interrupts = .{ - .interrupt1 = timer_interrupt, + .interrupt30 = .{ .c = esp.Scheduler.generic_interrupt_handler }, + .interrupt31 = .{ .naked = esp.Scheduler.isr_yield_handler }, }, .log_level = .debug, + .cpu = .{ + .interrupt_stack_size = 4096, + }, }; -const alarm = systimer.alarm(0); - -fn timer_interrupt(_: *microzig.cpu.TrapFrame) linksection(".ram_text") callconv(.c) void { - std.log.info("timer interrupt!", .{}); - - alarm.clear_interrupt(); -} - +var heap_buf: [10 * 1024]u8 = undefined; var scheduler: esp.Scheduler = undefined; -var mutex: esp.Scheduler.Mutex = .init; +var mutex: esp.Scheduler.Mutex = .{}; +// var sem: esp.Scheduler.Semaphore = .init(1); + +fn task1(_: ?*anyopaque) callconv(.c) noreturn { + var i: u32 = 0; + while (true) : (i += 1) { + // queue.putOneUncancelable(&scheduler, i); + { + mutex.lock(&scheduler); + defer mutex.unlock(&scheduler); + std.log.info("hello from task 1: {}", .{i}); + scheduler.sleep(1_000_000 * systimer.ticks_per_us()); + std.log.info("hello from task 1 still locking mutex", .{}); + } + scheduler.sleep(500_000 * systimer.ticks_per_us()); + } +} -fn task1(_: ?*anyopaque) callconv(.c) void { - mutex.lock(&scheduler); - std.log.info("task 1", .{}); - scheduler.yield(.{}); - std.log.info("task 1 end", .{}); - mutex.unlock(&scheduler); +fn task2(_: ?*anyopaque) callconv(.c) noreturn { + var i: u32 = 0; + while (true) : (i += 1) { + // queue.putOneUncancelable(&scheduler, i); + { + mutex.lock(&scheduler); + defer mutex.unlock(&scheduler); + std.log.info("hello from task 2: {}", .{i}); + scheduler.sleep(5_000_000 * systimer.ticks_per_us()); + std.log.info("hello from task 2 still locking mutex", .{}); + } + scheduler.sleep(5_000_000 * systimer.ticks_per_us()); + } } pub fn main() !void { - std.log.info("hello world!", .{}); - - // unit0 is already enabled as it is used by `hal.time`. - alarm.set_unit(.unit0); - - // sets the period to one second. - alarm.set_period(@intCast(1_000_000 * systimer.ticks_per_us())); - - // to enable period mode you have to first clear the mode bit. - alarm.set_mode(.target); - alarm.set_mode(.period); - - alarm.set_interrupt_enabled(true); - alarm.set_enabled(true); - - microzig.cpu.interrupt.set_priority_threshold(.zero); - - microzig.cpu.interrupt.set_type(.interrupt1, .level); - microzig.cpu.interrupt.set_priority(.interrupt1, .highest); - microzig.cpu.interrupt.map(.systimer_target0, .interrupt1); - microzig.cpu.interrupt.enable(.interrupt1); + var heap = microzig.Allocator.init_with_buffer(&heap_buf); + const allocator = heap.allocator(); + scheduler.init(allocator); esp.time.sleep_ms(1000); - var heap = microzig.Allocator.init_with_heap(4096); - const allocator = heap.allocator(); - scheduler.init(allocator); - try scheduler.raw_alloc_spawn_with_options(task1, null, .{}); + try scheduler.raw_alloc_spawn_with_options(task1, null, .{ + // .priority = .high, + }); + + try scheduler.raw_alloc_spawn_with_options(task2, null, .{ + // .priority = .high, + }); - mutex.lock(&scheduler); - std.log.info("main", .{}); - scheduler.yield(.{}); - std.log.info("main revisited", .{}); - scheduler.yield(.{}); - std.log.info("main ending", .{}); - scheduler.yield(.{}); - mutex.unlock(&scheduler); + // try sem.take(&scheduler, 500_000 * systimer.ticks_per_us()); + // log.info("got semaphore once", .{}); + // try sem.take(&scheduler, 2_000_000 * systimer.ticks_per_us()); while (true) { + scheduler.yield(.reschedule); microzig.cpu.wfi(); + // const value = queue.getOneUncancelable(&scheduler); + // std.log.info("received {}", .{value}); + // { + // mutex.lock(&scheduler); + // defer mutex.unlock(&scheduler); + // scheduler.sleep(500_000 * systimer.ticks_per_us()); + // } } } diff --git a/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld index 9ace43e17..4d3f32034 100644 --- a/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld @@ -27,6 +27,7 @@ SECTIONS { microzig_ram_text_start = .; KEEP(*(.ram_text)) + KEEP(*(.ram_vectors)) *(.rwtext*) diff --git a/port/espressif/esp/ld/esp32_c3/flashless_sections.ld b/port/espressif/esp/ld/esp32_c3/flashless_sections.ld index b38ad1ea9..987fd2991 100644 --- a/port/espressif/esp/ld/esp32_c3/flashless_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/flashless_sections.ld @@ -7,6 +7,7 @@ SECTIONS KEEP(*(microzig_ram_start)) *(.text*) KEEP(*(.ram_text)) + KEEP(*(.ram_vectors)) *(.rwtext*) diff --git a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld index 42f10a85e..432866b5e 100644 --- a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld @@ -55,6 +55,7 @@ SECTIONS .ram.text : { KEEP(*(.ram_text)) + KEEP(*(.ram_vectors)) /* TODO: in the case of memory protection there should be some alignment * and offset done here (NOLOAD) */ diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index cc5dd94cd..1df27c7cc 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -4,6 +4,15 @@ const microzig = @import("microzig"); const cpu_config = @import("cpu-config"); const riscv32_common = @import("riscv32-common"); +pub const CPU_Options = struct { + /// If null, interrupts will use same stack. Otherwise, the interrupt + /// handler will switch to a custom stack after pushing the TrapFrame. + /// Nested interrupts use the interrupt stack as well. This feature uses + /// mscratch to store the user's stack pointer. While not in an interrupt, + /// mscratch must be zero. + interrupt_stack_size: ?usize = null, +}; + pub const Exception = enum(u5) { InstructionFault = 0x1, IllegalInstruction = 0x2, @@ -48,7 +57,10 @@ pub const Interrupt = enum(u5) { interrupt31 = 31, }; -pub const InterruptHandler = *const fn (*TrapFrame) callconv(.c) void; +pub const InterruptHandler = union(enum) { + naked: *const fn () callconv(.naked) void, + c: *const fn (*TrapFrame) callconv(.c) void, +}; pub const InterruptOptions = microzig.utilities.GenerateInterruptOptions(&.{ .{ .InterruptEnum = enum { Exception }, .HandlerFn = InterruptHandler }, @@ -226,6 +238,38 @@ pub const interrupt = struct { const base: usize = @intFromPtr(&INTERRUPT_CORE0.MAC_INTR_MAP); return @ptrFromInt(base + @sizeOf(u32) * @as(usize, @intFromEnum(source))); } + + pub const SourceIterator = struct { + index: usize, + status_reg: u32, + + pub fn init() SourceIterator { + return .{ + .index = 0, + .status_reg = INTERRUPT_CORE0.INTR_STATUS_REG_0.raw, + }; + } + + pub fn next(iter: *SourceIterator) ?Source { + var leading_zeroes = @ctz(iter.status_reg); + if (leading_zeroes == @bitSizeOf(u32)) { + if (iter.index >= 32) { + return null; + } + iter.status_reg = INTERRUPT_CORE0.INTR_STATUS_REG_1.raw; + leading_zeroes = @ctz(iter.status_reg); + if (leading_zeroes == @bitSizeOf(u32)) { + return null; + } + iter.index = 32; + } + + iter.index += leading_zeroes; + iter.status_reg >>= @truncate(leading_zeroes + 1); + + return @enumFromInt(iter.index); + } + }; }; pub const nop = riscv32_common.nop; @@ -321,6 +365,10 @@ fn init_interrupts() void { .mode = .vectored, .base = @intCast(@intFromPtr(&_vector_table) >> 2), }); + + if (interrupt_stack_size != null) { + csr.mscratch.write_raw(0); + } } pub const TrapFrame = extern struct { @@ -342,201 +390,60 @@ pub const TrapFrame = extern struct { a7: usize, }; -fn _vector_table() align(256) linksection(".ram_text") callconv(.naked) void { - comptime { - // TODO: make a better default exception handler - @export( - microzig.options.interrupts.Exception orelse &unhandled, - .{ .name = "_exception_handler" }, - ); - - for (std.meta.fieldNames(Interrupt)) |field_name| { - @export( - @field(microzig.options.interrupts, field_name) orelse &unhandled, - .{ .name = std.fmt.comptimePrint("_{s}_handler", .{field_name}) }, - ); +const interrupt_stack_size = microzig.options.cpu.interrupt_stack_size; +pub var interrupt_stack: [interrupt_stack_size orelse 0]u8 align(16) = undefined; + +fn _vector_table() align(256) linksection(".ram_vectors") callconv(.naked) void { + const interrupt_jump_asm, const interrupt_c_stubs_asm = comptime blk: { + var interrupt_jump_asm: []const u8 = ""; + var interrupt_c_stubs_asm: []const u8 = ""; + + for (std.meta.fieldNames(InterruptOptions)) |field_name| { + const handler: InterruptHandler = @field(microzig.options.interrupts, field_name) orelse .{ .c = &unhandled }; + switch (handler) { + .naked => |naked_handler| { + @export(naked_handler, .{ + .name = std.fmt.comptimePrint("_{s}_handler_naked", .{field_name}), + }); + + interrupt_jump_asm = interrupt_jump_asm ++ std.fmt.comptimePrint( + \\.balign 4 + \\ j _{s}_handler_naked + \\ + , .{field_name}); + }, + .c => |c_handler| { + @export(c_handler, .{ + .name = std.fmt.comptimePrint("_{s}_handler_c", .{field_name}), + }); + + interrupt_jump_asm = interrupt_jump_asm ++ std.fmt.comptimePrint( + \\.balign 4 + \\ j _{s}_stub + \\ + , .{field_name}); + + interrupt_c_stubs_asm = interrupt_c_stubs_asm ++ std.fmt.comptimePrint( + \\_{[name]s}_stub: + \\ addi sp, sp, -16*4 + \\ sw ra, 0(sp) + \\ la ra, _{[name]s}_handler_c + \\ j trap_common + \\ + , .{ .name = field_name }); + }, + } } @export(&_handle_interrupt, .{ .name = "_handle_interrupt" }); - } - const interrupts_jump_asm = comptime blk: { - var s: []const u8 = &.{}; - for (1..32) |i| { - s = s ++ std.fmt.comptimePrint( - \\.balign 4 - \\ j interrupt{} - \\ - , .{i}); - } - break :blk s; + break :blk .{ interrupt_jump_asm, interrupt_c_stubs_asm }; }; // adapted from https://github.com/esp-rs/esp-hal/blob/main/esp-riscv-rt/src/lib.rs asm volatile ( - \\ j exception \\ - ++ interrupts_jump_asm ++ - \\exception: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _exception_handler - \\ j trap_common - \\interrupt1: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt1_handler - \\ j trap_common - \\interrupt2: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt2_handler - \\ j trap_common - \\interrupt3: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt3_handler - \\ j trap_common - \\interrupt4: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt4_handler - \\ j trap_common - \\interrupt5: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt5_handler - \\ j trap_common - \\interrupt6: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt6_handler - \\ j trap_common - \\interrupt7: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt7_handler - \\ j trap_common - \\interrupt8: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt8_handler - \\ j trap_common - \\interrupt9: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt9_handler - \\ j trap_common - \\interrupt10: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt10_handler - \\ j trap_common - \\interrupt11: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt11_handler - \\ j trap_common - \\interrupt12: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt12_handler - \\ j trap_common - \\interrupt13: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt13_handler - \\ j trap_common - \\interrupt14: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt14_handler - \\ j trap_common - \\interrupt15: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt15_handler - \\ j trap_common - \\interrupt16: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt16_handler - \\ j trap_common - \\interrupt17: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt17_handler - \\ j trap_common - \\interrupt18: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt18_handler - \\ j trap_common - \\interrupt19: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt19_handler - \\ j trap_common - \\interrupt20: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt20_handler - \\ j trap_common - \\interrupt21: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt21_handler - \\ j trap_common - \\interrupt22: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt22_handler - \\ j trap_common - \\interrupt23: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt23_handler - \\ j trap_common - \\interrupt24: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt24_handler - \\ j trap_common - \\interrupt25: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt25_handler - \\ j trap_common - \\interrupt26: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt26_handler - \\ j trap_common - \\interrupt27: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt27_handler - \\ j trap_common - \\interrupt28: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt28_handler - \\ j trap_common - \\interrupt29: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt29_handler - \\ j trap_common - \\interrupt30: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt30_handler - \\ j trap_common - \\interrupt31: - \\ addi sp, sp, -16*4 - \\ sw ra, 0(sp) - \\ la ra, _interrupt31_handler - \\ j trap_common + ++ interrupt_jump_asm ++ interrupt_c_stubs_asm ++ \\trap_common: \\ sw t0, 1*4(sp) \\ sw t1, 2*4(sp) @@ -556,7 +463,24 @@ fn _vector_table() align(256) linksection(".ram_text") callconv(.naked) void { \\ \\ mv a0, sp # Pass a pointer to TrapFrame to the handler function \\ mv a1, ra # Save address of handler function - \\ jal ra, _handle_interrupt + \\ + ++ (if (interrupt_stack_size != null) + // switch to interrupt stack if not nested + \\ csrr t0, mscratch + \\ bnez t0, 1f + \\ csrw mscratch, sp + \\ la sp, %[interrupt_stack_top] + \\ jal ra, _handle_interrupt + \\ csrrw sp, mscratch, x0 + \\ j 2f + \\1: + \\ jal ra, _handle_interrupt + \\2: + \\ + else + \\ jal ra, _handle_interrupt + \\ + ) ++ \\ \\ lw ra, 0*4(sp) \\ lw t0, 1*4(sp) @@ -578,6 +502,11 @@ fn _vector_table() align(256) linksection(".ram_text") callconv(.naked) void { \\ addi sp, sp, 16*4 # This removes the frame we allocated from the stack \\ \\ mret + + : + : [interrupt_stack_top] "i" (if (interrupt_stack_size != null) + interrupt_stack[interrupt_stack.len..].ptr + else {}), ); } @@ -588,7 +517,7 @@ fn unhandled(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { std.log.err("unhandled interrupt {} occurred!", .{mcause.code}); } else { const exception: Exception = @enumFromInt(mcause.code); - std.log.err("unhandled exception {s} occurred at {x}!", .{@tagName(exception), csr.mepc.read_raw()}); + std.log.err("unhandled exception {s} occurred at {x}!", .{ @tagName(exception), csr.mepc.read_raw() }); switch (exception) { .InstructionFault => std.log.err("faulting address: {x}", .{csr.mtval.read_raw()}), @@ -600,7 +529,10 @@ fn unhandled(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { @panic("unhandled trap"); } -fn _handle_interrupt(trap_frame: *TrapFrame, handler: InterruptHandler) linksection(".ram_text") callconv(.c) void { +fn _handle_interrupt( + trap_frame: *TrapFrame, + handler: *const fn (*TrapFrame) callconv(.c) void, +) linksection(".ram_text") callconv(.c) void { const prev_priority = interrupt.get_priority_threshold(); const mcause = csr.mcause.read(); diff --git a/port/espressif/esp/src/hal/Scheduler.zig b/port/espressif/esp/src/hal/Scheduler.zig index 0a192eb68..71bcdd08c 100644 --- a/port/espressif/esp/src/hal/Scheduler.zig +++ b/port/espressif/esp/src/hal/Scheduler.zig @@ -1,240 +1,265 @@ const std = @import("std"); +const assert = std.debug.assert; const Allocator = std.mem.Allocator; const microzig = @import("microzig"); const TrapFrame = microzig.cpu.TrapFrame; const SYSTEM = microzig.chip.peripherals.SYSTEM; +const time = microzig.drivers.time; + +const systimer = @import("systimer.zig"); +const get_time_since_boot = @import("time.zig").get_time_since_boot; + +// How it works? +// For simple task to task context switches, only necessary registers are +// saved. But if a higher priority task becomes available during the handling +// of an interrupt, a task switch is forced by saving the entire state of the +// task on the stack. What is interresting is that the two context switches are +// compatible. Voluntary yield can resume a task that was interrupted by force +// and vice versa. Because of the forced yield, tasks are required to have a +// minimum stack size available. + +const STACK_ALIGN: std.mem.Alignment = .@"16"; +const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 31 * @sizeOf(usize)); +const IDLE_STACK_SIZE = 512 + EXTRA_STACK_SIZE; + +const generic_interrupt: microzig.cpu.Interrupt = .interrupt30; +const yield_interrupt: microzig.cpu.Interrupt = .interrupt31; +const systimer_unit = systimer.unit(0); +const systimer_alarm = systimer.alarm(0); + +comptime { + if (microzig.options.cpu.interrupt_stack_size == null) + @compileError("Please enable interrupt stacks to use the scheduler"); +} const Scheduler = @This(); -// TODO: Custom wrapper handlers for interrupts that switch to an interrupt -// stack +var maybe_instance: ?*Scheduler = null; -allocator: std.mem.Allocator = undefined, -ready_queue: ?*Task = null, -main_task: Task = undefined, -/// SAFETY: we don't need optionals because there will always be the main task -/// running. It can't be deleted. -current_task: *Task = undefined, +gpa: Allocator, -pub const Task = struct { - // NOTE: I believe this ensures an alignment for the struct, useful because - // some bits for instance in the mutex state are mark whether it is locked - // or unlocked - _: void align(4) = {}, - context: Context, - async_closure: AsyncClosure, - stack: []u8, - queue_next: ?*Task = null, +ready_queue: Task.ReadyPriorityQueue = .{}, +timer_queue: std.DoublyLinkedList = .{}, +suspended_list: std.DoublyLinkedList = .{}, - pub fn init(function: *const fn (param: ?*anyopaque) callconv(.c) void, param: ?*anyopaque, stack: []u8) Task { - return .{ - .context = .{ - .sp = stack.ptr[stack.len..], - .pc = AsyncClosure.call, - }, - .stack = stack, - .async_closure = .{ - .entry = function, - .param = param, - }, - }; +main_task: Task, + +/// The task in the .running state +current_task: *Task, + +// TODO: configurable +pub const Priority = enum(u2) { + idle = 0, + low = 1, + mid = 2, + high = 3, + + pub fn is_greater(prio: Priority, other: Priority) bool { + return @intFromEnum(prio) > @intFromEnum(other); } }; -pub fn init(scheduler: *Scheduler, allocator: Allocator) void { - scheduler.allocator = allocator; - scheduler.main_task = .{ - .context = undefined, - .async_closure = undefined, - .stack = &.{}, +pub fn init(scheduler: *Scheduler, gpa: Allocator) void { + assert(maybe_instance == null); + + scheduler.* = .{ + .gpa = gpa, + .main_task = .{ + .context = undefined, + .priority = .idle, + .stack = &.{}, + }, + .current_task = &scheduler.main_task, }; - scheduler.current_task = &scheduler.main_task; + + maybe_instance = scheduler; + + microzig.cpu.interrupt.map(.from_cpu_intr0, yield_interrupt); + microzig.cpu.interrupt.set_type(yield_interrupt, .level); + microzig.cpu.interrupt.set_priority(yield_interrupt, .lowest); + microzig.cpu.interrupt.enable(yield_interrupt); + + microzig.cpu.interrupt.set_type(generic_interrupt, .level); + microzig.cpu.interrupt.set_priority(generic_interrupt, .highest); + microzig.cpu.interrupt.map(.systimer_target0, generic_interrupt); + microzig.cpu.interrupt.enable(generic_interrupt); + + // unit0 is already enabled as it is used by `hal.time`. + systimer_alarm.set_unit(systimer_unit); + systimer_alarm.set_mode(.target); + systimer_alarm.set_enabled(false); + systimer_alarm.set_interrupt_enabled(true); } -// TODO: implement ziggy spawn +fn task_entry() callconv(.naked) void { + asm volatile ( + \\lw a0, -4(sp) + \\lw a1, -8(sp) + \\jr a1 + \\ + ); +} pub const SpawnOptions = struct { + name: []const u8 = "task", stack_size: usize = 4096, + priority: Priority = .low, }; pub fn raw_alloc_spawn_with_options( scheduler: *Scheduler, - function: *const fn (param: ?*anyopaque) callconv(.c) void, + function: *const fn (param: ?*anyopaque) callconv(.c) noreturn, param: ?*anyopaque, options: SpawnOptions, ) !void { - // TODO: maybe find a way to only do one allocation. This is a bit tricky - // because we want to also support statically allocated tasks, maybe based - // on the not yet available @stackSize builtin + const alignment = comptime STACK_ALIGN.max(.of(Task)); - const STACK_ALIGN: std.mem.Alignment = .@"16"; - const stack_size = std.mem.alignForward(usize, options.stack_size, STACK_ALIGN.toByteUnits()); - const stack = try scheduler.allocator.alignedAlloc(u8, STACK_ALIGN, stack_size); - errdefer scheduler.allocator.free(stack); + const unaligned_allocation_size = @sizeOf(Task) + options.stack_size + EXTRA_STACK_SIZE; + const allocation_size = std.mem.alignForward(usize, unaligned_allocation_size, alignment.toByteUnits()); + const raw_alloc = try scheduler.gpa.alignedAlloc(u8, alignment, allocation_size); - const task = try scheduler.allocator.create(Task); - errdefer scheduler.allocator.destroy(task); + const task: *Task = @ptrCast(raw_alloc.ptr); + const stack: []u8 = raw_alloc[@sizeOf(Task)..]; + const stack_top = @intFromPtr(stack[stack.len..].ptr); - task.* = .init(function, param, stack); - scheduler.schedule(task); -} + const startup_state: *[2]u32 = @ptrFromInt(stack_top - 2 * @sizeOf(u32)); + startup_state[0] = @intFromPtr(function); + startup_state[1] = @intFromPtr(param); -pub fn schedule(scheduler: *Scheduler, task: *Task) void { - const first_task = scheduler.ready_queue; + task.* = .{ + .context = .{ + .sp = stack_top, + .pc = @intFromPtr(&task_entry), + .fp = 0, + }, + .stack = stack, + .priority = options.priority, + }; + scheduler.make_task_ready(task); } -const AsyncClosure = extern struct { - entry: *const fn (param: ?*anyopaque) callconv(.c) void, - param: ?*anyopaque, +pub fn make_task_ready(scheduler: *Scheduler, task: *Task) void { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); - pub fn call(contexts: *const @FieldType(SwitchMessage, "contexts")) callconv(.c) noreturn { - const message: *const SwitchMessage = @fieldParentPtr("contexts", contexts); - message.handle(); + scheduler.make_task_ready_from_cs(task); +} - const task: *Task = @fieldParentPtr("context", contexts.next); - task.async_closure.entry(task.async_closure.param); - while (true) message.scheduler.yield_with_action(.exit); +pub fn make_task_ready_from_cs(scheduler: *Scheduler, task: *Task) void { + switch (task.state) { + .running, .none => {}, + .ready => return, + .alarm_set => |_| { + scheduler.timer_queue.remove(&task.internal_node); + }, + .suspended => { + scheduler.suspended_list.remove(&task.internal_node); + }, } -}; -pub fn yield(scheduler: *Scheduler, options: struct { - task: ?*Task = null, - action: SwitchMessage.Action = .nothing, -}) void { - const cs = microzig.interrupt.enter_critical_section(); - - const node = options.task orelse scheduler.find_ready_task() orelse { - cs.leave(); - // TODO: maybe switch to idle task if not called in isr - return; - }; - const next_task: *Task = @fieldParentPtr("node", node); + // Remove the task from any external waiting list (eg: from a mutex). + if (task.waiting_queue) |waiting_queue| { + waiting_queue.inner.remove(&task.waiting_queue_node); + task.waiting_queue = null; + } - const message: SwitchMessage = .{ - .scheduler = scheduler, - .contexts = .{ - .prev = &scheduler.current_task.context, - .next = &next_task.context, - }, - .action = options.action, - .cs = cs, - }; - context_switch(&message).handle(); + task.state = .ready; + scheduler.ready_queue.append(task); } -pub fn mutex_lock(scheduler: *Scheduler, prev_state: Mutex.State, mutex: *Mutex) void { - scheduler.yield(.{ .action = .{ .mutex_lock = .{ - .prev_state = prev_state, - .mutex = mutex, - } } }); -} +pub fn change_task_priority_from_cs(scheduler: *Scheduler, task: *Task, new_priority: Priority) void { + task.priority = new_priority; -pub fn mutex_unlock(scheduler: *Scheduler, _: Mutex.State, mutex: *Mutex) void { - // NOTE: only gets called if mutex is contented, very simplified - // implementation because single core (esp32c3 only for now) + switch (task.state) { + .ready => { + scheduler.ready_queue.inner.remove(task); + scheduler.ready_queue.inner.append(task); + }, + else => {}, + } - const waiting_task = blk: { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); + if (task.waiting_queue) |waiting_queue| { + waiting_queue.inner.remove(&task.waiting_queue_node); + waiting_queue.append(&task.waiting_queue_node); + } +} - const waiting_task: *Task = @ptrFromInt(@intFromEnum(mutex.state)); - mutex.state = @enumFromInt(@intFromPtr(waiting_task.queue_next)); - break :blk waiting_task; - }; +pub const YieldAction = union(enum) { + reschedule, + wait: ?u52, +}; - scheduler.yield(.{ - .action = .nothing, - .task = waiting_task, - }); +pub inline fn yield(scheduler: *Scheduler, action: YieldAction) void { + const cs = microzig.interrupt.enter_critical_section(); + scheduler.yield_and_leave_cs(action, cs); } -pub const SwitchMessage = struct { +/// Must be called inside critical section. Calling leave on the critical +/// section becomes unnecessary. +pub inline fn yield_and_leave_cs( scheduler: *Scheduler, - contexts: extern struct { - prev: *Context, - next: *Context, - }, - action: Action, + action: YieldAction, cs: microzig.interrupt.CriticalSection, +) void { + const prev_context, const next_context = scheduler.yield_inner(action); + context_switch(prev_context, next_context); + if (!cs.enable_on_leave) { + microzig.cpu.interrupt.disable_interrupts(); + } +} - pub const Action = union(enum) { - nothing, - mutex_lock: struct { - prev_state: Mutex.State, - mutex: *Mutex, +fn yield_inner(scheduler: *Scheduler, action: YieldAction) struct { *Context, *Context } { + const prev_task = scheduler.current_task; + switch (action) { + .reschedule => { + scheduler.ready_queue.append(prev_task); + prev_task.state = .ready; }, - exit, - }; - - pub fn handle(message: *const SwitchMessage) void { - defer message.cs.leave(); - - const scheduler = message.scheduler; - const prev_task: *Task = @fieldParentPtr("context", message.contexts.prev); - const next_task: *Task = @fieldParentPtr("context", message.contexts.next); + .wait => |maybe_timeout| { + if (maybe_timeout) |timeout| { + scheduler.schedule_wake_at(prev_task, timeout); + } else { + prev_task.state = .suspended; + scheduler.suspended_list.append(&prev_task.internal_node); + } + }, + } - scheduler.current_task = next_task; + const next_task: *Task = scheduler.ready_queue.pop(null) orelse @panic("Idle task can't be waiting."); - switch (message.action) { - .nothing => { - scheduler.schedule(prev_task); - }, - .mutex_lock => |state| { - if (state.prev_state == .unlocked) { - state.mutex.state = .locked_once; - prev_task.queue_next = null; - scheduler.schedule(prev_task); - } else { - const maybe_first_awaiter: ?*Task = @ptrFromInt(@intFromEnum(state.mutex.state)); - if (maybe_first_awaiter) |first_awaiter| { - var last_awaiter = first_awaiter; - while (true) last_awaiter = last_awaiter.queue_next orelse break; - last_awaiter.queue_next = prev_task; - } else { - state.mutex.state = @enumFromInt(@intFromPtr(prev_task)); - } - } - }, - .exit => { - scheduler.allocator.free(prev_task.stack); - scheduler.allocator.destroy(prev_task); - }, - } - } -}; + scheduler.current_task = next_task; + next_task.state = .running; -fn find_ready_task(scheduler: *Scheduler) ?*Task { - if (scheduler.ready_queue) |next_task| { - scheduler.ready_queue = next_task.queue_next; - return next_task; - } - return null; + return .{ &prev_task.context, &next_task.context }; } -pub const Context = extern struct { - pc: ?*const anyopaque, - sp: ?*anyopaque, -}; +pub fn sleep(scheduler: *Scheduler, ticks: u52) void { + const now_ticks = systimer_unit.read(); + const timeout_ticks = now_ticks +% ticks; + scheduler.yield(.{ .wait = timeout_ticks }); +} -inline fn context_switch(message: *const SwitchMessage) *const SwitchMessage { - return @fieldParentPtr("contexts", asm volatile ( - \\lw t0, 0(a0) # prev context - \\lw t1, 4(a0) # next context +inline fn context_switch(prev_context: *Context, next_context: *Context) void { + asm volatile ( + \\la a2, 1f + \\sw a2, 0(a0) # save return pc + \\sw sp, 4(a0) # save prev stack pointer + \\sw s0, 8(a0) # save prev frame pointer \\ - \\la t2, ret - \\sw t2, 0(t0) # save return pc + \\lw a2, 0(a1) # load next pc + \\lw sp, 4(a1) # load next stack pointer + \\lw s0, 8(a1) # load next frame pointer \\ - \\sw sp, 4(t0) # save prev stack pointer + \\csrsi mstatus, 8 # enable interrupts \\ - \\lw t2, 0(t1) # load next pc - \\lw sp, 4(t1) # load next stack pointer - \\jr t2 # jump to next task - \\ret: + \\jr a2 # jump to next task + \\1: \\ - : [received_message] "={a0}" (-> *const @FieldType(SwitchMessage, "contexts")), - : [message_to_send] "{a0}" (&message.contexts), + : + : [prev_context] "{a0}" (prev_context), + [next_context] "{a1}" (next_context), : .{ .x1 = true, // ra .x3 = true, @@ -267,81 +292,379 @@ inline fn context_switch(message: *const SwitchMessage) *const SwitchMessage { .x30 = true, .x31 = true, .memory = true, - })); + }); } -pub const Mutex = struct { - state: State, +pub fn yield_from_isr(scheduler: *Scheduler) void { + if (scheduler.ready_queue.peek_top()) |top_ready_task| { + if (top_ready_task.priority.is_greater(scheduler.current_task.priority)) { + SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ + .CPU_INTR_FROM_CPU_0 = 1, + }); + } + } +} + +pub fn isr_yield_handler() linksection(".ram_vectors") callconv(.naked) void { + comptime { + assert(@sizeOf(Context) == 3 * @sizeOf(usize)); + } + + asm volatile ( + \\ + \\addi sp, sp, -31*4 + \\ + \\sw ra, 0*4(sp) + \\sw t0, 1*4(sp) + \\sw t1, 2*4(sp) + \\sw t2, 3*4(sp) + \\sw t3, 4*4(sp) + \\sw t4, 5*4(sp) + \\sw t5, 6*4(sp) + \\sw t6, 7*4(sp) + \\sw a0, 8*4(sp) + \\sw a1, 9*4(sp) + \\sw a2, 10*4(sp) + \\sw a3, 11*4(sp) + \\sw a4, 12*4(sp) + \\sw a5, 13*4(sp) + \\sw a6, 14*4(sp) + \\sw a7, 15*4(sp) + \\sw s1, 16*4(sp) + \\sw s2, 17*4(sp) + \\sw s3, 18*4(sp) + \\sw s4, 19*4(sp) + \\sw s5, 20*4(sp) + \\sw s6, 21*4(sp) + \\sw s7, 22*4(sp) + \\sw s8, 23*4(sp) + \\sw s9, 24*4(sp) + \\sw s10, 25*4(sp) + \\sw s11, 26*4(sp) + \\sw gp, 27*4(sp) + \\sw tp, 28*4(sp) + \\ + \\csrr a1, mepc + \\sw a1, 29*4(sp) + \\ + \\csrr a1, mstatus + \\sw a1, 30*4(sp) + \\ + // save sp for later + \\mv a2, sp + \\ + // use the interrupt stack in this call to minimize task stack size + // NOTE: mscratch doesn't need to be zeroed because this can't be + // interrupted by a higher priority interrupt + \\la sp, %[interrupt_stack_top] + \\ + // allocate `Context` struct and save context + \\addi sp, sp, -3*4 + \\la a1, 1f + \\sw a1, 0(sp) + \\sw a2, 4(sp) + \\sw s0, 8(sp) + \\ + // first parameter is a pointer to context + \\mv a0, sp + \\mv s1, a1 + \\jal %[schedule_in_isr] + \\ + // load next task context + \\lw a1, 0(sp) + \\lw a2, 4(sp) + \\lw s0, 8(sp) + // change sp to the new task + \\mv sp, a2 + \\ + // if the next task program counter is equal to 1f's location just jump + // to it (ie. the task was interrupted). Technically not required but + // works as an optimization. + \\beq a1, s1, 1f + \\ + // ensure interrupts get enabled after mret + \\li t0, 0x80 + \\csrs mstatus, t0 + \\ + // jump to new task + \\csrw mepc, a1 + \\mret + \\ + \\1: + \\ + \\lw t1, 30*4(sp) + \\csrw mstatus, t1 + \\ + \\lw t0, 29*4(sp) + \\csrw mepc, t0 + \\ + \\lw ra, 0*4(sp) + \\lw t0, 1*4(sp) + \\lw t1, 2*4(sp) + \\lw t2, 3*4(sp) + \\lw t3, 4*4(sp) + \\lw t4, 5*4(sp) + \\lw t5, 6*4(sp) + \\lw t6, 7*4(sp) + \\lw a0, 8*4(sp) + \\lw a1, 9*4(sp) + \\lw a2, 10*4(sp) + \\lw a3, 11*4(sp) + \\lw a4, 12*4(sp) + \\lw a5, 13*4(sp) + \\lw a6, 14*4(sp) + \\lw a7, 15*4(sp) + \\lw s1, 16*4(sp) + \\lw s2, 17*4(sp) + \\lw s3, 18*4(sp) + \\lw s4, 19*4(sp) + \\lw s5, 20*4(sp) + \\lw s6, 21*4(sp) + \\lw s7, 22*4(sp) + \\lw s8, 23*4(sp) + \\lw s9, 24*4(sp) + \\lw s10, 25*4(sp) + \\lw s11, 26*4(sp) + \\lw gp, 27*4(sp) + \\lw tp, 28*4(sp) + \\ + \\addi sp, sp, 31*4 + \\mret + : + : [schedule_in_isr] "i" (&schedule_in_isr), + [interrupt_stack_top] "i" (microzig.cpu.interrupt_stack[microzig.cpu.interrupt_stack.len..].ptr), + ); +} + +fn schedule_in_isr(context: *Context) linksection(".ram_vectors") callconv(.c) void { + const scheduler = maybe_instance orelse @panic("no active scheduler"); + + SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ + .CPU_INTR_FROM_CPU_0 = 0, + }); - pub const State = enum(usize) { - locked_once = 0b00, - unlocked = 0b01, - contended = 0b10, - /// contended - _, + const prev_task = scheduler.current_task; + const ready_task = scheduler.ready_queue.pop(scheduler.current_task.priority) orelse return; - pub fn isUnlocked(state: State) bool { - return @intFromEnum(state) & @intFromEnum(State.unlocked) == @intFromEnum(State.unlocked); + // swap contexts + prev_task.context = context.*; + context.* = ready_task.context; + + scheduler.ready_queue.append(prev_task); + prev_task.state = .ready; + + scheduler.current_task = ready_task; + ready_task.state = .running; +} + +/// Must be called from a critical section. +fn schedule_wake_at(scheduler: *Scheduler, sleeping_task: *Task, timeout: u52) void { + sleeping_task.state = .{ .alarm_set = timeout }; + + var maybe_node = scheduler.timer_queue.first; + while (maybe_node) |node| : (maybe_node = node.next) { + const task: *Task = @alignCast(@fieldParentPtr("internal_node", node)); + const wake_ticks = task.state.alarm_set; + if (timeout < wake_ticks or wake_ticks -% timeout < timeout) { + scheduler.timer_queue.insertBefore(&task.internal_node, &sleeping_task.internal_node); + break; } - }; + } else { + scheduler.timer_queue.append(&sleeping_task.internal_node); + } - pub const init: Mutex = .{ .state = .unlocked }; - - pub fn tryLock(mutex: *Mutex) bool { - const prev_state: State = @enumFromInt(@atomicRmw( - usize, - @as(*usize, @ptrCast(&mutex.state)), - .And, - ~@intFromEnum(State.unlocked), - .acquire, - )); - return prev_state.isUnlocked(); + // If we updated the first element of the list, it means that we have to + // reschedule the timer + if (scheduler.timer_queue.first == &sleeping_task.internal_node) { + systimer_alarm.set_target(timeout); + systimer_alarm.set_enabled(true); } +} - pub fn lock(mutex: *Mutex, scheduler: *Scheduler) void { - const prev_state: State = @enumFromInt(@atomicRmw( - usize, - @as(*usize, @ptrCast(&mutex.state)), - .And, - ~@intFromEnum(State.unlocked), - .acquire, - )); - if (prev_state.isUnlocked()) { - @branchHint(.likely); - return; +pub fn generic_interrupt_handler(_: *TrapFrame) callconv(.c) void { + const scheduler = maybe_instance orelse @panic("no active scheduler"); + + var iter: microzig.cpu.interrupt.SourceIterator = .init(); + while (iter.next()) |source| { + switch (source) { + .systimer_target0 => { + systimer_alarm.clear_interrupt(); + systimer_alarm.set_enabled(false); + + const now_ticks = systimer_unit.read(); + + var maybe_node = scheduler.timer_queue.first; + while (maybe_node) |node| : (maybe_node = node.next) { + const task: *Task = @alignCast(@fieldParentPtr("internal_node", node)); + const wake_ticks = task.state.alarm_set; + if (now_ticks < wake_ticks or wake_ticks -% now_ticks < now_ticks) { + break; + } + + scheduler.make_task_ready_from_cs(task); + } + + if (scheduler.timer_queue.first) |node| { + const task: *Task = @alignCast(@fieldParentPtr("internal_node", node)); + systimer_alarm.set_target(task.state.alarm_set); + systimer_alarm.set_enabled(true); + } + }, + else => {}, } - scheduler.mutex_lock(prev_state, mutex); } - pub fn unlock(mutex: *Mutex, scheduler: *Scheduler) void { - const prev_state = @cmpxchgWeak(State, &mutex.state, .locked_once, .unlocked, .release, .acquire) orelse { - @branchHint(.likely); - return; - }; - std.debug.assert(prev_state != .unlocked); - return scheduler.mutex_unlock(prev_state, mutex); + scheduler.yield_from_isr(); +} + +pub const Task = struct { + context: Context, + stack: []u8, + priority: Priority, + + /// What is the deal with this task right now? + state: State = .none, + + /// Node used for scheduler internal lists. + internal_node: std.DoublyLinkedList.Node = .{}, + + /// In which external waiting queue is this task in. + waiting_queue: ?*WaitingPriorityQueue = null, + /// Node used for external waiting queue. + waiting_queue_node: std.DoublyLinkedList.Node = .{}, + + pub const State = union(enum) { + none, + ready, + running, + alarm_set: u52, + suspended, + }; + + // TODO: Maybe swap with something more efficient. + pub const ReadyPriorityQueue = struct { + inner: std.DoublyLinkedList = .{}, + + pub fn peek_top(pq: *ReadyPriorityQueue) ?*Task { + if (pq.inner.first) |first_node| { + return @alignCast(@fieldParentPtr("internal_node", first_node)); + } else { + return null; + } + } + + pub fn pop(pq: *ReadyPriorityQueue, maybe_more_than_prio: ?Priority) ?*Task { + if (pq.peek_top()) |task| { + if (maybe_more_than_prio) |more_than_prio| { + if (!task.priority.is_greater(more_than_prio)) { + return null; + } + } + pq.inner.remove(&task.internal_node); + return task; + } + return null; + } + + pub fn append(pq: *ReadyPriorityQueue, new_task: *Task) void { + var maybe_node = pq.inner.first; + while (maybe_node) |node| : (maybe_node = node.next) { + const task: *Task = @alignCast(@fieldParentPtr("internal_node", node)); + if (new_task.priority.is_greater(task.priority)) { + pq.inner.insertBefore(node, &new_task.internal_node); + break; + } + } else { + pq.inner.append(&new_task.internal_node); + } + } + }; + + pub const WaitingPriorityQueue = struct { + inner: std.DoublyLinkedList = .{}, + + pub fn first(pq: *WaitingPriorityQueue) ?*Task { + if (pq.inner.first) |first_node| { + return @alignCast(@fieldParentPtr("waiting_queue_node", first_node)); + } else { + return null; + } + } + + pub fn append(pq: *WaitingPriorityQueue, new_task: *Task) void { + new_task.waiting_queue = pq; + + var maybe_node = pq.inner.first; + while (maybe_node) |node| : (maybe_node = node.next) { + const task: *Task = @alignCast(@fieldParentPtr("waiting_queue_node", node)); + if (new_task.priority.is_greater(task.priority)) { + pq.inner.insertBefore(node, &new_task.waiting_queue_node); + break; + } + } else { + pq.inner.append(&new_task.waiting_queue_node); + } + } + }; +}; + +pub const Context = extern struct { + pc: usize, + sp: usize, + fp: usize, + + pub fn format( + self: Context, + writer: *std.Io.Writer, + ) std.Io.Writer.Error!void { + try writer.print(".{{ .pc = 0x{x}, .sp = 0x{x}, .fp = 0x{x} }}", .{ self.pc, self.sp, self.fp }); } }; -pub const Condition = struct { - state: u64 = 0, +// TODO: implement priority inheritance +pub const Mutex = struct { + state: State = .unlocked, + waiting_queue: Task.WaitingPriorityQueue = .{}, + + pub const State = enum(u32) { + locked, + unlocked, + }; + + pub fn lock(mutex: *Mutex, scheduler: *Scheduler) void { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + while (mutex.state != .unlocked) { + const current_task = scheduler.current_task; + mutex.waiting_queue.append(current_task); + scheduler.yield(.{ .wait = null }); + } - pub fn wait(cond: *Condition, scheduler: *Scheduler, mutex: *Mutex) void { - return scheduler.condition_wait(cond, mutex); + mutex.state = .locked; } - pub fn signal(cond: *Condition, scheduler: *Scheduler) void { - scheduler.condition_wake(cond, .one); + pub fn unlock(mutex: *Mutex, scheduler: *Scheduler) void { + const cs = microzig.interrupt.enter_critical_section(); + defer scheduler.yield_and_leave_cs(.reschedule, cs); + + assert(mutex.state == .locked); + mutex.state = .unlocked; + + if (mutex.waiting_queue.first()) |waiting_task| { + scheduler.make_task_ready_from_cs(waiting_task); + } } +}; + +pub const Semaphore = struct { + value: u32, - pub fn broadcast(cond: *Condition, scheduler: *Scheduler) void { - scheduler.condition_wake(cond, .all); + pub fn init(initial_value: u32) Semaphore { + return .{ + .count = initial_value, + }; } - pub const Wake = enum { - /// Wake up only one thread. - one, - /// Wake up all threads. - all, - }; + // pub fn take() }; diff --git a/port/espressif/esp/src/hal/radio/timer.zig b/port/espressif/esp/src/hal/radio/timer.zig index 137071272..5ef5316c8 100644 --- a/port/espressif/esp/src/hal/radio/timer.zig +++ b/port/espressif/esp/src/hal/radio/timer.zig @@ -94,6 +94,3 @@ fn find_next_due(now: time.Absolute) ?*Timer { } return null; } - -// fn timer_task(_: ?*anyopaque) callconv(.c) noreturn { -// } diff --git a/port/espressif/esp/src/hal/systimer.zig b/port/espressif/esp/src/hal/systimer.zig index 7782e0b69..8c20d3414 100644 --- a/port/espressif/esp/src/hal/systimer.zig +++ b/port/espressif/esp/src/hal/systimer.zig @@ -4,7 +4,7 @@ const SYSTIMER = microzig.chip.peripherals.SYSTIMER; const system = @import("system.zig"); -pub fn ticks_per_us() u64 { +pub fn ticks_per_us() u52 { return 16; } @@ -69,7 +69,7 @@ pub const Unit = enum(u1) { } } - pub fn write(self: Unit, value: u64) void { + pub fn write(self: Unit, value: u52) void { const LOAD: @TypeOf(&SYSTIMER.UNIT0_LOAD) = switch (self) { .unit0 => &SYSTIMER.UNIT0_LOAD, .unit1 => @ptrCast(&SYSTIMER.UNIT1_LOAD), @@ -83,12 +83,12 @@ pub const Unit = enum(u1) { .unit1 => @ptrCast(&SYSTIMER.UNIT1_VALUE_HI), }; - LO.write(.{ .TIMER_UNIT0_LOAD_LO = value & 0xffff_ffff }); - HI.write(.{ .TIMER_UNIT0_LOAD_HI = value >> 32 }); + LO.write(.{ .TIMER_UNIT0_LOAD_LO = @truncate(value) }); + HI.write(.{ .TIMER_UNIT0_LOAD_HI = @truncate(value >> 32) }); LOAD.write(.{ .TIMER_UNIT0_LOAD = 1 }); } - pub fn read(self: Unit) u64 { + pub fn read(self: Unit) u52 { const OP: @TypeOf(&SYSTIMER.UNIT0_OP) = switch (self) { .unit0 => &SYSTIMER.UNIT0_OP, .unit1 => @ptrCast(&SYSTIMER.UNIT1_OP), @@ -115,7 +115,7 @@ pub const Unit = enum(u1) { const hi = HI.read().TIMER_UNIT0_VALUE_HI; lo_start = LO.read().TIMER_UNIT0_VALUE_LO; - if (lo_start == lo) return (@as(u64, hi) << 32) | lo; + if (lo_start == lo) return (@as(u52, hi) << 32) | lo; } } }; From c2805f733ea4603950416718260af3de3a310ce4 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Sun, 28 Dec 2025 12:11:37 +0200 Subject: [PATCH 13/43] Got wifi working and more --- examples/espressif/esp/build.zig | 16 +- examples/espressif/esp/src/scheduler.zig | 59 ++---- examples/espressif/esp/src/systimer.zig | 2 +- examples/espressif/esp/src/wifi.zig | 97 +++++----- .../esp/ld/esp32_c3/image_boot_sections.ld | 7 - port/espressif/esp/src/cpus/esp_riscv.zig | 4 + port/espressif/esp/src/hal.zig | 4 +- port/espressif/esp/src/hal/Scheduler.zig | 170 +++++++++++++++-- port/espressif/esp/src/hal/radio.zig | 83 ++------ .../esp/src/hal/radio/multitasking.zig | 99 ---------- port/espressif/esp/src/hal/radio/osi.zig | 178 ++++-------------- port/espressif/esp/src/hal/radio/timer.zig | 7 - port/espressif/esp/src/hal/radio/wifi.zig | 10 +- 13 files changed, 288 insertions(+), 448 deletions(-) delete mode 100644 port/espressif/esp/src/hal/radio/multitasking.zig diff --git a/examples/espressif/esp/build.zig b/examples/espressif/esp/build.zig index ab08c20e7..1a10bf4ec 100644 --- a/examples/espressif/esp/build.zig +++ b/examples/espressif/esp/build.zig @@ -28,14 +28,14 @@ pub fn build(b: *std.Build) void { }; const targeted_examples: []const TargetedExample = &.{ - // .{ - // .target = .esp32_c3, - // .example = .{ - // .name = "wifi", - // .file = "src/wifi.zig", - // .features = .{ .lwip = true }, - // }, - // }, + .{ + .target = .esp32_c3, + .example = .{ + .name = "wifi", + .file = "src/wifi.zig", + .features = .{ .lwip = true }, + }, + }, }; for (examples) |example| { diff --git a/examples/espressif/esp/src/scheduler.zig b/examples/espressif/esp/src/scheduler.zig index 8bbed4ba6..be013d551 100644 --- a/examples/espressif/esp/src/scheduler.zig +++ b/examples/espressif/esp/src/scheduler.zig @@ -22,38 +22,22 @@ pub const microzig_options: microzig.Options = .{ var heap_buf: [10 * 1024]u8 = undefined; var scheduler: esp.Scheduler = undefined; var mutex: esp.Scheduler.Mutex = .{}; -// var sem: esp.Scheduler.Semaphore = .init(1); fn task1(_: ?*anyopaque) callconv(.c) noreturn { var i: u32 = 0; while (true) : (i += 1) { - // queue.putOneUncancelable(&scheduler, i); - { - mutex.lock(&scheduler); - defer mutex.unlock(&scheduler); - std.log.info("hello from task 1: {}", .{i}); - scheduler.sleep(1_000_000 * systimer.ticks_per_us()); - std.log.info("hello from task 1 still locking mutex", .{}); - } + std.log.info("hello from task 1: {}", .{i}); + // { + // mutex.lock(&scheduler); + // defer mutex.unlock(&scheduler); + // std.log.info("hello from task 1: {}", .{i}); + // scheduler.sleep(1_000_000 * systimer.ticks_per_us()); + // std.log.info("hello from task 1 still locking mutex", .{}); + // } scheduler.sleep(500_000 * systimer.ticks_per_us()); } } -fn task2(_: ?*anyopaque) callconv(.c) noreturn { - var i: u32 = 0; - while (true) : (i += 1) { - // queue.putOneUncancelable(&scheduler, i); - { - mutex.lock(&scheduler); - defer mutex.unlock(&scheduler); - std.log.info("hello from task 2: {}", .{i}); - scheduler.sleep(5_000_000 * systimer.ticks_per_us()); - std.log.info("hello from task 2 still locking mutex", .{}); - } - scheduler.sleep(5_000_000 * systimer.ticks_per_us()); - } -} - pub fn main() !void { var heap = microzig.Allocator.init_with_buffer(&heap_buf); const allocator = heap.allocator(); @@ -61,27 +45,18 @@ pub fn main() !void { esp.time.sleep_ms(1000); - try scheduler.raw_alloc_spawn_with_options(task1, null, .{ - // .priority = .high, - }); - - try scheduler.raw_alloc_spawn_with_options(task2, null, .{ + _ = try scheduler.raw_alloc_spawn_with_options(task1, null, .{ // .priority = .high, }); - // try sem.take(&scheduler, 500_000 * systimer.ticks_per_us()); - // log.info("got semaphore once", .{}); - // try sem.take(&scheduler, 2_000_000 * systimer.ticks_per_us()); - while (true) { - scheduler.yield(.reschedule); - microzig.cpu.wfi(); - // const value = queue.getOneUncancelable(&scheduler); - // std.log.info("received {}", .{value}); - // { - // mutex.lock(&scheduler); - // defer mutex.unlock(&scheduler); - // scheduler.sleep(500_000 * systimer.ticks_per_us()); - // } + { + mutex.lock(&scheduler); + defer mutex.unlock(&scheduler); + std.log.info("hello from main", .{}); + scheduler.sleep(5_000_000 * systimer.ticks_per_us()); + std.log.info("hello from main still locking mutex", .{}); + } + scheduler.sleep(5_000_000 * systimer.ticks_per_us()); } } diff --git a/examples/espressif/esp/src/systimer.zig b/examples/espressif/esp/src/systimer.zig index 54a028e2f..ce1921f08 100644 --- a/examples/espressif/esp/src/systimer.zig +++ b/examples/espressif/esp/src/systimer.zig @@ -9,7 +9,7 @@ const usb_serial_jtag = hal.usb_serial_jtag; pub const microzig_options: microzig.Options = .{ .logFn = usb_serial_jtag.logger.log, .interrupts = .{ - .interrupt1 = timer_interrupt, + .interrupt1 = .{ .c = timer_interrupt }, }, }; diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index 7e6ffb319..c99e6be5f 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -3,6 +3,7 @@ const microzig = @import("microzig"); const SPSC_Queue = microzig.concurrency.SPSC_Queue; const interrupt = microzig.cpu.interrupt; const hal = microzig.hal; +const Scheduler = hal.Scheduler; const radio = hal.radio; const usb_serial_jtag = hal.usb_serial_jtag; @@ -30,47 +31,44 @@ pub const microzig_options: microzig.Options = .{ }, .logFn = usb_serial_jtag.logger.log, .interrupts = .{ - .interrupt1 = radio.interrupt_handlers.wifi_xxx, - .interrupt2 = radio.interrupt_handlers.timer, - .interrupt3 = radio.interrupt_handlers.yield, + .interrupt29 = .{ .c = radio.interrupt_handlers.wifi_xxx }, + .interrupt30 = .{ .c = Scheduler.generic_interrupt_handler }, + .interrupt31 = .{ .naked = Scheduler.isr_yield_handler }, + }, + .cpu = .{ + .interrupt_stack_size = 8192, }, .hal = .{ .radio = .{ - .wifi_interrupt = .interrupt1, - .timer_interrupt = .interrupt2, - .yield_interrupt = .interrupt3, - // .timer = .{ .systimer = .{ - // .unit = .unit0, - // .alarm = .alarm0, - // } }, + .wifi_interrupt = .interrupt29, }, }, }; var buffer: [50 * 1024]u8 = undefined; +var scheduler: Scheduler = undefined; pub fn main() !void { // var fba: std.heap.FixedBufferAllocator = .init(&buffer); // const allocator = fba.threadSafeAllocator(); var alloc = microzig.Allocator.init_with_buffer(&buffer); const allocator = alloc.allocator(); + scheduler.init(allocator); - microzig.cpu.interrupt.enable_interrupts(); - - try radio.init(allocator); + try radio.init(allocator, &scheduler); try radio.wifi.init(); - c.lwip_init(); - - var netif: c.netif = undefined; - _ = c.netif_add(&netif, @ptrCast(c.IP4_ADDR_ANY), @ptrCast(c.IP4_ADDR_ANY), @ptrCast(c.IP4_ADDR_ANY), null, netif_init, c.netif_input); - @memcpy(&netif.name, "e0"); - c.netif_create_ip6_linklocal_address(&netif, 1); - netif.ip6_autoconfig_enabled = 1; - c.netif_set_status_callback(&netif, netif_status_callback); - c.netif_set_default(&netif); - c.netif_set_up(&netif); - _ = c.dhcp_start(&netif); + // c.lwip_init(); + // + // var netif: c.netif = undefined; + // _ = c.netif_add(&netif, @ptrCast(c.IP4_ADDR_ANY), @ptrCast(c.IP4_ADDR_ANY), @ptrCast(c.IP4_ADDR_ANY), null, netif_init, c.netif_input); + // @memcpy(&netif.name, "e0"); + // c.netif_create_ip6_linklocal_address(&netif, 1); + // netif.ip6_autoconfig_enabled = 1; + // c.netif_set_status_callback(&netif, netif_status_callback); + // c.netif_set_default(&netif); + // c.netif_set_up(&netif); + // _ = c.dhcp_start(&netif); try radio.wifi.apply(.{ .sta = .{ @@ -82,7 +80,7 @@ pub fn main() !void { try radio.wifi.connect(); var connected: bool = false; - var last_mem_show = hal.time.get_time_since_boot(); + // var last_mem_show = hal.time.get_time_since_boot(); while (true) { radio.tick(); @@ -90,37 +88,39 @@ pub fn main() !void { const sta_state = radio.wifi.get_sta_state(); if (!connected and sta_state == .sta_connected) { std.log.info("link up", .{}); - c.netif_set_link_up(&netif); + // c.netif_set_link_up(&netif); connected = true; } else if (connected and sta_state == .sta_disconnected) { std.log.info("link down", .{}); - c.netif_set_link_down(&netif); + // c.netif_set_link_down(&netif); connected = false; } while (radio.wifi.recv_packet(.sta)) |packet| { defer packet.deinit(); - const maybe_pbuf: ?*c.struct_pbuf = c.pbuf_alloc(c.PBUF_RAW, @intCast(packet.data.len), c.PBUF_POOL); - if (maybe_pbuf) |pbuf| { - _ = c.pbuf_take(pbuf, packet.data.ptr, @intCast(packet.data.len)); - defer _ = c.pbuf_free(pbuf); - - if (c.netif_input(pbuf, &netif) != c.ERR_OK) { - std.log.warn("lwip netif input failed", .{}); - } - } + std.log.info("packet received", .{}); + + // const maybe_pbuf: ?*c.struct_pbuf = c.pbuf_alloc(c.PBUF_RAW, @intCast(packet.data.len), c.PBUF_POOL); + // if (maybe_pbuf) |pbuf| { + // _ = c.pbuf_take(pbuf, packet.data.ptr, @intCast(packet.data.len)); + // defer _ = c.pbuf_free(pbuf); + // + // if (c.netif_input(pbuf, &netif) != c.ERR_OK) { + // std.log.warn("lwip netif input failed", .{}); + // } + // } } - c.sys_check_timeouts(); + // c.sys_check_timeouts(); - const now = hal.time.get_time_since_boot(); - if (!now.diff(last_mem_show).less_than(.from_ms(1000))) { - const used_mem = 50 * 1024 - alloc.free_heap(); - std.log.info("used memory: {}K ({})", .{ used_mem / 1024, used_mem }); - last_mem_show = now; - } - hal.time.sleep_ms(10); + // const now = hal.time.get_time_since_boot(); + // if (!now.diff(last_mem_show).less_than(.from_ms(1000))) { + // const used_mem = 50 * 1024 - alloc.free_heap(); + // std.log.info("used memory: {}K ({})", .{ used_mem / 1024, used_mem }); + // last_mem_show = now; + // } + scheduler.sleep(10_000 * hal.systimer.ticks_per_us()); } // var ssid: [1:0]u8 = @splat(0); @@ -150,15 +150,6 @@ pub fn main() !void { // std.log.info("found {} aps", .{no}); } -var rx_queue: SPSC_Queue(*c.struct_pbuf, 10) = .empty; - -fn rx_callback(packet: radio.wifi.ReceivedPacket) void { - defer packet.deinit(); - - // std.log.info("receiving packet", .{}); - -} - fn netif_init(netif_c: [*c]c.struct_netif) callconv(.c) c.err_t { const netif: *c.struct_netif = netif_c; diff --git a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld index 432866b5e..28651d043 100644 --- a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld @@ -100,12 +100,5 @@ SECTIONS microzig_bss_end = .; } > DRAM - .heap (NOLOAD) : - { - microzig_heap_start = .; - . = ORIGIN(DRAM) + LENGTH(DRAM); - microzig_heap_end = .; - } > DRAM - PROVIDE(__global_pointer$ = microzig_data_start + 0x800); } diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index 1df27c7cc..a38fb347d 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -58,7 +58,11 @@ pub const Interrupt = enum(u5) { }; pub const InterruptHandler = union(enum) { + /// No state is saved. Do everything yourself. Interrupts are disabled + /// while it is executing. naked: *const fn () callconv(.naked) void, + /// State pushed on the stack. Handler can be interrupted by higher + /// priority interrupts. c: *const fn (*TrapFrame) callconv(.c) void, }; diff --git a/port/espressif/esp/src/hal.zig b/port/espressif/esp/src/hal.zig index 2e4b36287..fab329ce4 100644 --- a/port/espressif/esp/src/hal.zig +++ b/port/espressif/esp/src/hal.zig @@ -11,7 +11,7 @@ pub const drivers = @import("hal/drivers.zig"); pub const gpio = @import("hal/gpio.zig"); pub const efuse = @import("hal/efuse.zig"); pub const i2c = @import("hal/i2c.zig"); -// pub const radio = @import("hal/radio.zig"); +pub const radio = @import("hal/radio.zig"); pub const ledc = @import("hal/ledc.zig"); pub const rng = @import("hal/rng.zig"); pub const rom = @import("hal/rom.zig"); @@ -35,7 +35,7 @@ pub const HAL_Options = struct { secure_version: u32 = 0, version: []const u8 = "0.0.0", } = .{}, - // radio: ?radio.Options = null, + radio: ?radio.Options = null, }; /// Clock config applied by the default `init()` function of the hal. diff --git a/port/espressif/esp/src/hal/Scheduler.zig b/port/espressif/esp/src/hal/Scheduler.zig index 71bcdd08c..1f8db0ea7 100644 --- a/port/espressif/esp/src/hal/Scheduler.zig +++ b/port/espressif/esp/src/hal/Scheduler.zig @@ -19,11 +19,18 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; // and vice versa. Because of the forced yield, tasks are required to have a // minimum stack size available. +// TODO: add identifier to tasks +// TODO: stack usage report based on stack painting +// TODO: for other esp32 chips support SMP +// TODO: use @stackUpperBound when implemented + const STACK_ALIGN: std.mem.Alignment = .@"16"; const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 31 * @sizeOf(usize)); const IDLE_STACK_SIZE = 512 + EXTRA_STACK_SIZE; +// TODO: configurable const generic_interrupt: microzig.cpu.Interrupt = .interrupt30; +// TODO: configurable const yield_interrupt: microzig.cpu.Interrupt = .interrupt31; const systimer_unit = systimer.unit(0); const systimer_alarm = systimer.alarm(0); @@ -45,15 +52,19 @@ suspended_list: std.DoublyLinkedList = .{}, main_task: Task, -/// The task in the .running state +idle_stack: [IDLE_STACK_SIZE]u8 = undefined, +idle_task: Task, + +/// The task in .running state current_task: *Task, // TODO: configurable -pub const Priority = enum(u2) { +pub const Priority = enum(u8) { idle = 0, - low = 1, - mid = 2, - high = 3, + lowest = 1, + _, + + pub const highest: Priority = @enumFromInt(std.math.maxInt(@typeInfo(Priority).@"enum".tag_type)); pub fn is_greater(prio: Priority, other: Priority) bool { return @intFromEnum(prio) > @intFromEnum(other); @@ -67,12 +78,23 @@ pub fn init(scheduler: *Scheduler, gpa: Allocator) void { .gpa = gpa, .main_task = .{ .context = undefined, - .priority = .idle, + .priority = .lowest, .stack = &.{}, }, + .idle_task = .{ + .context = .{ + .pc = @intFromPtr(&idle), + .sp = @intFromPtr(scheduler.idle_stack[scheduler.idle_stack.len..].ptr), + .fp = 0, + }, + .stack = &scheduler.idle_stack, + .priority = .idle, + }, .current_task = &scheduler.main_task, }; + scheduler.make_task_ready(&scheduler.idle_task); + maybe_instance = scheduler; microzig.cpu.interrupt.map(.from_cpu_intr0, yield_interrupt); @@ -92,6 +114,14 @@ pub fn init(scheduler: *Scheduler, gpa: Allocator) void { systimer_alarm.set_interrupt_enabled(true); } +pub fn idle() callconv(.c) void { + const scheduler = maybe_instance orelse @panic("no active scheduler"); + while (true) { + scheduler.yield(.reschedule); + microzig.cpu.wfi(); + } +} + fn task_entry() callconv(.naked) void { asm volatile ( \\lw a0, -4(sp) @@ -102,9 +132,9 @@ fn task_entry() callconv(.naked) void { } pub const SpawnOptions = struct { - name: []const u8 = "task", stack_size: usize = 4096, - priority: Priority = .low, + // TODO: should we ban idle priority? + priority: Priority = .lowest, }; pub fn raw_alloc_spawn_with_options( @@ -112,7 +142,7 @@ pub fn raw_alloc_spawn_with_options( function: *const fn (param: ?*anyopaque) callconv(.c) noreturn, param: ?*anyopaque, options: SpawnOptions, -) !void { +) !*Task { const alignment = comptime STACK_ALIGN.max(.of(Task)); const unaligned_allocation_size = @sizeOf(Task) + options.stack_size + EXTRA_STACK_SIZE; @@ -138,6 +168,8 @@ pub fn raw_alloc_spawn_with_options( }; scheduler.make_task_ready(task); + + return task; } pub fn make_task_ready(scheduler: *Scheduler, task: *Task) void { @@ -235,8 +267,12 @@ fn yield_inner(scheduler: *Scheduler, action: YieldAction) struct { *Context, *C return .{ &prev_task.context, &next_task.context }; } +pub fn get_ticks(_: *Scheduler) u52 { + return systimer_unit.read(); +} + pub fn sleep(scheduler: *Scheduler, ticks: u52) void { - const now_ticks = systimer_unit.read(); + const now_ticks = scheduler.get_ticks(); const timeout_ticks = now_ticks +% ticks; scheduler.yield(.{ .wait = timeout_ticks }); } @@ -465,7 +501,7 @@ fn schedule_wake_at(scheduler: *Scheduler, sleeping_task: *Task, timeout: u52) v while (maybe_node) |node| : (maybe_node = node.next) { const task: *Task = @alignCast(@fieldParentPtr("internal_node", node)); const wake_ticks = task.state.alarm_set; - if (timeout < wake_ticks or wake_ticks -% timeout < timeout) { + if (is_a_before_b(timeout, wake_ticks)) { scheduler.timer_queue.insertBefore(&task.internal_node, &sleeping_task.internal_node); break; } @@ -491,13 +527,13 @@ pub fn generic_interrupt_handler(_: *TrapFrame) callconv(.c) void { systimer_alarm.clear_interrupt(); systimer_alarm.set_enabled(false); - const now_ticks = systimer_unit.read(); + const now_ticks = scheduler.get_ticks(); var maybe_node = scheduler.timer_queue.first; while (maybe_node) |node| : (maybe_node = node.next) { const task: *Task = @alignCast(@fieldParentPtr("internal_node", node)); const wake_ticks = task.state.alarm_set; - if (now_ticks < wake_ticks or wake_ticks -% now_ticks < now_ticks) { + if (is_a_before_b(now_ticks, wake_ticks)) { break; } @@ -533,6 +569,9 @@ pub const Task = struct { /// Node used for external waiting queue. waiting_queue_node: std.DoublyLinkedList.Node = .{}, + /// Task specific semaphore (required by the wifi driver) + semaphore: Semaphore = .init(0), + pub const State = union(enum) { none, ready, @@ -632,13 +671,22 @@ pub const Mutex = struct { }; pub fn lock(mutex: *Mutex, scheduler: *Scheduler) void { + lock_with_timeout(mutex, scheduler, null) catch unreachable; + } + + pub fn lock_with_timeout(mutex: *Mutex, scheduler: *Scheduler, maybe_timeout: ?u52) error{Timeout}!void { const cs = microzig.interrupt.enter_critical_section(); defer cs.leave(); while (mutex.state != .unlocked) { const current_task = scheduler.current_task; mutex.waiting_queue.append(current_task); - scheduler.yield(.{ .wait = null }); + scheduler.yield(.{ .wait = maybe_timeout }); + if (maybe_timeout) |timeout| { + if (!is_a_before_b(scheduler.get_ticks(), timeout)) { + return error.Timeout; + } + } } mutex.state = .locked; @@ -657,14 +705,104 @@ pub const Mutex = struct { } }; +pub const RecursiveMutex = struct { + value: u32 = 0, + owning_task: ?*Task = null, + waiting_queue: Task.WaitingPriorityQueue = .{}, + + pub fn lock(mutex: *RecursiveMutex, scheduler: *Scheduler) void { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + const current_task = scheduler.current_task; + + if (mutex.owning_task == current_task) { + mutex.value += 1; + return; + } + + while (mutex.owning_task != null) { + mutex.waiting_queue.append(current_task); + scheduler.yield(.{ .wait = null }); + } + + assert(mutex.value == 0); + mutex.value += 1; + mutex.owning_task = scheduler.current_task; + } + + pub fn unlock(mutex: *RecursiveMutex, scheduler: *Scheduler) bool { + const cs = microzig.interrupt.enter_critical_section(); + + assert(mutex.value > 0); + mutex.value -= 1; + if (mutex.value <= 0) { + defer scheduler.yield_and_leave_cs(.reschedule, cs); + + mutex.owning_task = null; + + if (mutex.waiting_queue.first()) |waiting_task| { + scheduler.make_task_ready_from_cs(waiting_task); + } + + return true; + } else { + cs.leave(); + return false; + } + } +}; + pub const Semaphore = struct { value: u32, + waiting_queue: Task.WaitingPriorityQueue = .{}, pub fn init(initial_value: u32) Semaphore { return .{ - .count = initial_value, + .value = initial_value, }; } - // pub fn take() + pub fn take(sem: *Semaphore, scheduler: *Scheduler) void { + sem.take_with_timeout(scheduler, null) catch unreachable; + } + + pub fn take_with_timeout(sem: *Semaphore, scheduler: *Scheduler, maybe_timeout: ?u52) error{Timeout}!void { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + while (sem.value <= 0) { + if (maybe_timeout) |timeout| { + if (!is_a_before_b(scheduler.get_ticks(), timeout)) { + return error.Timeout; + } + } + + const current_task = scheduler.current_task; + sem.waiting_queue.append(current_task); + scheduler.yield(.{ .wait = maybe_timeout }); + } + + sem.value -= 1; + } + + pub fn give(sem: *Semaphore, scheduler: *Scheduler) void { + const cs = microzig.interrupt.enter_critical_section(); + defer scheduler.yield_and_leave_cs(.reschedule, cs); + + sem.value += 1; + + if (sem.waiting_queue.first()) |waiting_task| { + scheduler.make_task_ready_from_cs(waiting_task); + } + } }; + +pub const TypeErasedQueue = struct { + buffer: []const u8, + +}; + +fn is_a_before_b(a: u52, b: u52) bool { + return a < b or b -% a < a; +} diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index a7a19eb03..f47fdff75 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -1,7 +1,7 @@ const std = @import("std"); -const log = std.log.scoped(.esp_radio); const Allocator = std.mem.Allocator; +const c = @import("esp-wifi-driver"); const microzig = @import("microzig"); const TrapFrame = microzig.cpu.TrapFrame; const time = microzig.drivers.time; @@ -12,27 +12,21 @@ const SYSTEM = peripherals.SYSTEM; const RTC_CNTL = peripherals.RTC_CNTL; const APB_CTRL = peripherals.APB_CTRL; -const c = @import("esp-wifi-driver"); - -pub const wifi = @import("radio/wifi.zig"); -pub const bluetooth = @import("radio/bluetooth.zig"); - const efuse = @import("efuse.zig"); +pub const bluetooth = @import("radio/bluetooth.zig"); const osi = @import("radio/osi.zig"); const timer = @import("radio/timer.zig"); -const multitasking = @import("radio/multitasking.zig"); +pub const wifi = @import("radio/wifi.zig"); +const Scheduler = @import("Scheduler.zig"); + +const log = std.log.scoped(.esp_radio); pub const Options = struct { wifi_interrupt: microzig.cpu.Interrupt, - timer_interrupt: microzig.cpu.Interrupt, - // TODO: we could probably do a context switch without this - yield_interrupt: microzig.cpu.Interrupt, - // TODO: support other timers and other systimer units - /// What alarm to use for preemption in systimer unit 0. - systimer_alarm: systimer.Alarm = .alarm0, wifi: wifi.Options = .{}, }; + pub const options = microzig.options.hal.radio orelse @compileError("Please specify options if you want to use radio."); @@ -41,7 +35,7 @@ pub const options = microzig.options.hal.radio orelse /// Radio uses the official esp drivers. You should enable interrupts /// after/before this. -pub fn init(allocator: Allocator) Allocator.Error!void { +pub fn init(allocator: Allocator, scheduler: *Scheduler) Allocator.Error!void { // TODO: check that clock frequency is higher or equal to 80mhz { @@ -52,18 +46,11 @@ pub fn init(allocator: Allocator) Allocator.Error!void { // phy_mem_init(); // only sets some global variable on esp32c3 osi.allocator = allocator; + osi.scheduler = scheduler; - // TODO: errdefer deinit - try multitasking.init(allocator); - - setup_timer_periodic_alarm(); setup_interrupts(); } - // multitasking.yield_task(); - - // try timer.init(allocator); - log.debug("initialization complete", .{}); // TODO: config @@ -74,9 +61,7 @@ pub fn init(allocator: Allocator) Allocator.Error!void { // TODO // should free everything -pub fn deinit() void { - -} +pub fn deinit() void {} pub fn tick() void { timer.tick(); @@ -153,41 +138,11 @@ fn enable_wifi_power_domain_and_init_clocks() void { fn setup_interrupts() void { // TODO: which interrupts are used should be configurable. - microzig.cpu.interrupt.set_priority_threshold(.zero); - microzig.cpu.interrupt.map(.wifi_mac, options.wifi_interrupt); microzig.cpu.interrupt.map(.wifi_pwr, options.wifi_interrupt); - microzig.cpu.interrupt.map(.systimer_target0, options.timer_interrupt); - microzig.cpu.interrupt.map(.from_cpu_intr0, options.yield_interrupt); - - inline for (&.{ options.wifi_interrupt, options.timer_interrupt, options.yield_interrupt }) |int| { - microzig.cpu.interrupt.set_type(int, .level); - microzig.cpu.interrupt.set_priority(int, .lowest); - } - inline for (&.{ options.timer_interrupt, options.yield_interrupt }) |int| { - microzig.cpu.interrupt.enable(int); - } -} - -// TODO: config (even other timers) -const preemt_interval: time.Duration = .from_ms(10); - -fn setup_timer_periodic_alarm() void { - const alarm = options.systimer_alarm; - - // unit0 is already enabled as it is used by `hal.time`. - alarm.set_unit(.unit0); - - // sets the period to one second. - alarm.set_period(@intCast(preemt_interval.to_us() * systimer.ticks_per_us())); - - // to enable period mode you have to first clear the mode bit. - alarm.set_mode(.target); - alarm.set_mode(.period); - - alarm.set_interrupt_enabled(true); - alarm.set_enabled(true); + microzig.cpu.interrupt.set_type(options.wifi_interrupt, .level); + microzig.cpu.interrupt.set_priority(options.wifi_interrupt, .highest); } pub const interrupt_handlers = struct { @@ -201,18 +156,4 @@ pub const interrupt_handlers = struct { handler.f(handler.arg); } - - pub fn timer(trap_frame: *TrapFrame) linksection(".ram_text") callconv(.c) void { - options.systimer_alarm.clear_interrupt(); - multitasking.switch_task(trap_frame); - } - - pub fn yield(trap_frame: *TrapFrame) linksection(".ram_text") callconv(.c) void { - // TODO: config - SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ - .CPU_INTR_FROM_CPU_0 = 0, - }); - - multitasking.switch_task(trap_frame); - } }; diff --git a/port/espressif/esp/src/hal/radio/multitasking.zig b/port/espressif/esp/src/hal/radio/multitasking.zig deleted file mode 100644 index b2e85925b..000000000 --- a/port/espressif/esp/src/hal/radio/multitasking.zig +++ /dev/null @@ -1,99 +0,0 @@ -const std = @import("std"); -const Allocator = std.mem.Allocator; - -const microzig = @import("microzig"); -const TrapFrame = microzig.cpu.TrapFrame; -const SYSTEM = microzig.chip.peripherals.SYSTEM; - -pub const Task = struct { - trap_frame: TrapFrame, - stack: []u8, - semaphore: u32 = 0, - next: *Task, - - pub fn create( - allocator: std.mem.Allocator, - entry: *const fn (param: ?*anyopaque) callconv(.c) noreturn, - param: ?*anyopaque, - stack_size: usize, - ) Allocator.Error!*Task { - const stack: []u8 = try allocator.alloc(u8, stack_size); - errdefer allocator.free(stack); - - const task: *Task = try allocator.create(Task); - errdefer allocator.destroy(task); - - const trap_frame: TrapFrame = blk: { - const stack_top_addr: usize = @intFromPtr(stack.ptr) + stack.len; - - var frame: TrapFrame = undefined; - @memset(std.mem.asBytes(&frame), 0); - - frame.pc = @intFromPtr(entry); - frame.a0 = @intFromPtr(param); - frame.sp = stack_top_addr - stack_top_addr % 16; - - break :blk frame; - }; - - task.* = .{ - .trap_frame = trap_frame, - .stack = stack, - .next = task, // loop back to this task - }; - - return task; - } - - pub fn destroy(task: *Task, allocator: Allocator) void { - allocator.free(task.stack); - allocator.destroy(task); - } -}; - -var main_task: *Task = undefined; -/// SAFETY: we don't need optionals because there will always be the main task -/// running. It can't be deleted. -pub var current_task: *Task = undefined; - -/// Must be called before the preemption timer interrupt is enabled. -pub fn init(allocator: Allocator) !void { - const task: *Task = try allocator.create(Task); - task.* = .{ - .trap_frame = undefined, - .stack = &.{}, - .next = task, // loop back to this task - }; - current_task = task; - main_task = task; -} - -pub fn schedule_task(task: *Task) void { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - task.next = current_task.next; - current_task.next = task; -} - -pub fn switch_task(trap_frame: *TrapFrame) void { - copy_context(¤t_task.trap_frame, trap_frame); - - // check if we need to delete any task - - current_task = current_task.next; - - copy_context(trap_frame, ¤t_task.trap_frame); -} - -fn copy_context(dst: *TrapFrame, src: *const TrapFrame) void { - const size = @offsetOf(TrapFrame, "mstatus"); - @memcpy(std.mem.asBytes(dst)[0..size], std.mem.asBytes(src)[0..size]); -} - -pub fn yield_task() void { - // TODO: config - SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ - .CPU_INTR_FROM_CPU_0 = 1, - }); -} diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 89819d355..16e365436 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -9,7 +9,8 @@ const peripherals = microzig.chip.peripherals; const APB_CTRL = peripherals.APB_CTRL; const hal = microzig.hal; -const multitasking = @import("multitasking.zig"); +const systimer = @import("../systimer.zig"); +const Scheduler = @import("../Scheduler.zig"); const timer = @import("timer.zig"); const wifi = @import("wifi.zig"); @@ -19,6 +20,7 @@ const c = @import("esp-wifi-driver"); const coex_enabled: bool = false; pub var allocator: std.mem.Allocator = undefined; +pub var scheduler: *Scheduler = undefined; pub var wifi_interrupt_handler: struct { f: *const fn (?*anyopaque) callconv(.c) void, @@ -86,10 +88,6 @@ pub fn __assert_func( } pub fn malloc(len: usize) callconv(.c) ?*anyopaque { - // Avoid multiple allocations at the same time as it causes a panic - microzig.cpu.interrupt.disable_interrupts(); - defer microzig.cpu.interrupt.enable_interrupts(); - log.debug("malloc {}", .{len}); const buf = allocator.rawAlloc(8 + len, .@"8", @returnAddress()) orelse { @@ -103,7 +101,7 @@ pub fn malloc(len: usize) callconv(.c) ?*anyopaque { } pub fn calloc(number: usize, size: usize) callconv(.c) ?*anyopaque { - const total_size: usize = number * size - 8; + const total_size: usize = number * size; if (malloc(total_size)) |ptr| { @memset(@as([*]u8, @ptrCast(ptr))[0..total_size], 0); return ptr; @@ -123,9 +121,9 @@ pub fn free(ptr: ?*anyopaque) callconv(.c) void { return; } - const buf_ptr: [*]u8 = @ptrFromInt(@intFromPtr(ptr) - @sizeOf(usize)); + const buf_ptr: [*]u8 = @ptrFromInt(@intFromPtr(ptr) - 8); const buf_len: *usize = @ptrCast(@alignCast(buf_ptr)); - allocator.rawFree(buf_ptr[0 .. @sizeOf(usize) + buf_len.*], .@"4", @returnAddress()); + allocator.rawFree(buf_ptr[0 .. @sizeOf(usize) + buf_len.*], .@"8", @returnAddress()); } pub fn puts(ptr: ?*anyopaque) callconv(.c) void { @@ -144,12 +142,12 @@ pub fn gettimeofday(tv: ?*c.timeval, _: ?*anyopaque) callconv(.c) i32 { } pub fn sleep(time_sec: c_uint) callconv(.c) c_int { - task_delay(time_sec * 1_000_000); + scheduler.sleep(time_sec * 1_000_000 * systimer.ticks_per_us()); return 0; } pub fn usleep(time_us: u32) callconv(.c) c_int { - task_delay(time_us); + scheduler.sleep(time_us * systimer.ticks_per_us()); return 0; } @@ -234,9 +232,6 @@ pub fn set_isr( switch (n) { 0, 1 => { - // don't need critical section because we enable the interrupt - // bellow. - wifi_interrupt_handler = .{ .f = @ptrCast(@alignCast(f)), .arg = arg, @@ -251,7 +246,7 @@ pub fn ints_on(mask: u32) callconv(.c) void { log.debug("ints_on {}", .{mask}); if (mask == 2) { - microzig.cpu.interrupt.enable(.interrupt1); + microzig.cpu.interrupt.enable(.interrupt29); } } @@ -260,7 +255,7 @@ pub fn ints_off(mask: u32) callconv(.c) void { log.debug("ints_off {}", .{mask}); if (mask == 2) { - microzig.cpu.interrupt.disable(.interrupt1); + microzig.cpu.interrupt.disable(.interrupt29); } } @@ -300,17 +295,17 @@ pub fn wifi_int_restore(mux_ptr: ?*anyopaque, state: u32) callconv(.c) void { pub fn task_yield_from_isr() callconv(.c) void { log.debug("task_yield_from_isr", .{}); - multitasking.yield_task(); + scheduler.yield_from_isr(); } pub fn semphr_create(max_value: u32, init_value: u32) callconv(.c) ?*anyopaque { log.debug("semphr_create {} {}", .{ max_value, init_value }); - const sem = allocator.create(u32) catch { + const sem = allocator.create(Scheduler.Semaphore) catch { log.warn("failed to allocate semaphore", .{}); return null; }; - sem.* = init_value; + sem.* = .init(init_value); log.debug(">>>> semaphore create: {*}", .{sem}); @@ -320,46 +315,17 @@ pub fn semphr_create(max_value: u32, init_value: u32) callconv(.c) ?*anyopaque { pub fn semphr_delete(ptr: ?*anyopaque) callconv(.c) void { log.debug("semphr_delete {?}", .{ptr}); - allocator.destroy(@as(*u32, @ptrCast(@alignCast(ptr)))); + allocator.destroy(@as(*Scheduler.Semaphore, @ptrCast(@alignCast(ptr)))); } pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { log.debug("semphr_take {?} {}", .{ ptr, tick }); - const forever = tick == c.OSI_FUNCS_TIME_BLOCKING; - const timeout = tick; - const start = hal.time.get_time_since_boot(); - - const sem: *u32 = @ptrCast(@alignCast(ptr)); - - while (true) { - const res: bool = blk: { - const cs = enter_critical_section(); - defer cs.leave(); - - microzig.cpu.fence(); - - const cnt = sem.*; - if (cnt > 0) { - sem.* = cnt - 1; - break :blk true; - } else { - break :blk false; - } - }; - - if (res) { - log.debug(">>>> return from semaphore take: {*}", .{sem}); - return 1; - } - - if (!forever and hal.time.get_time_since_boot().diff(start).to_us() > timeout) { - log.debug(">>>> return from semaphore take with timeout: {*}", .{sem}); - break; - } - - multitasking.yield_task(); - } + const sem: *Scheduler.Semaphore = @ptrCast(@alignCast(ptr)); + sem.take_with_timeout(scheduler, tick) catch { + log.debug(">>>> return from semaphore take with timeout: {*}", .{sem}); + return 1; + }; return 0; } @@ -367,26 +333,15 @@ pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { pub fn semphr_give(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("semphr_give {?}", .{ptr}); - const sem: *u32 = @ptrCast(@alignCast(ptr)); - - const cs = enter_critical_section(); - defer cs.leave(); - - const cnt = sem.*; - sem.* = cnt + 1; + const sem: *Scheduler.Semaphore = @ptrCast(@alignCast(ptr)); + sem.give(scheduler); return 1; } pub fn wifi_thread_semphr_get() callconv(.c) ?*anyopaque { - return @ptrCast(&multitasking.current_task.semaphore); + return &scheduler.current_task.semaphore; } -const Mutex = struct { - locking_task: usize, - count: u32, - recursive: bool, -}; - // TODO: idk if we're gonna need to implement this pub fn mutex_create() callconv(.c) ?*anyopaque { @panic("mutex_create: not implemented"); @@ -395,18 +350,11 @@ pub fn mutex_create() callconv(.c) ?*anyopaque { pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { log.debug("recursive_mutex_create", .{}); - const mutex = allocator.create(Mutex) catch { + const mutex = allocator.create(Scheduler.RecursiveMutex) catch { log.warn("failed to allocate recursive mutex", .{}); return null; }; - - mutex.* = .{ - .locking_task = 0xffff_ffff, - .count = 0, - .recursive = true, - }; - - microzig.cpu.fence(); + mutex.* = .{}; log.debug(">>>> mutex create: {*}", .{mutex}); @@ -416,60 +364,24 @@ pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { pub fn mutex_delete(ptr: ?*anyopaque) callconv(.c) void { log.debug("mutex_delete {?}", .{ptr}); - const mutex: *Mutex = @ptrCast(@alignCast(ptr)); + const mutex: *Scheduler.RecursiveMutex = @ptrCast(@alignCast(ptr)); allocator.destroy(mutex); } pub fn mutex_lock(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("mutex lock {?}", .{ptr}); - const mutex: *Mutex = @ptrCast(@alignCast(ptr)); - const cur_task_id: usize = @intFromPtr(multitasking.current_task); - - while (true) { - const mutex_locked = blk: { - const cs = enter_critical_section(); - defer cs.leave(); - - if (mutex.count == 0) { - mutex.locking_task = cur_task_id; - mutex.count += 1; - break :blk true; - } else if (mutex.locking_task == cur_task_id) { - mutex.count += 1; - break :blk true; - } else { - break :blk false; - } - }; - - microzig.cpu.fence(); - - if (mutex_locked) { - log.debug(">>>> return from mutex lock: {*}", .{mutex}); - return 1; - } + const mutex: *Scheduler.RecursiveMutex = @ptrCast(@alignCast(ptr)); + mutex.lock(scheduler); - multitasking.yield_task(); - } + return 1; } pub fn mutex_unlock(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("mutex unlock {?}", .{ptr}); - const mutex: *Mutex = @ptrCast(@alignCast(ptr)); - - const cs = enter_critical_section(); - defer cs.leave(); - - microzig.cpu.fence(); - - if (mutex.count > 0) { - mutex.count -= 1; - return 1; - } else { - return 0; - } + const mutex: *Scheduler.RecursiveMutex = @ptrCast(@alignCast(ptr)); + return @intFromBool(mutex.unlock(scheduler)); } // TODO: maybe use atomics? maybe it is spsc? @@ -620,7 +532,7 @@ pub fn queue_recv(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) return -1; } - multitasking.yield_task(); + scheduler.yield(.reschedule); } } @@ -661,19 +573,19 @@ fn task_create_common( core_id: u32, ) i32 { _ = name; // autofix - _ = prio; // autofix _ = core_id; // autofix - const task: *multitasking.Task = multitasking.Task.create( - allocator, + const task: *Scheduler.Task = scheduler.raw_alloc_spawn_with_options( @ptrCast(@alignCast(task_func)), param, - stack_depth, + .{ + .priority = @enumFromInt(prio), + .stack_size = stack_depth, + }, ) catch { log.warn("failed to create task", .{}); return 0; }; - multitasking.schedule_task(task); @as(*usize, @ptrCast(@alignCast(task_handle))).* = @intFromPtr(task); @@ -729,26 +641,19 @@ pub fn task_delete() callconv(.c) void { pub fn task_delay(tick: u32) callconv(.c) void { log.debug("task_delay {}", .{tick}); - const start = hal.time.get_time_since_boot(); - const delay: time.Duration = .from_us(tick); - - while (hal.time.get_time_since_boot().diff(start).less_than(delay)) { - multitasking.yield_task(); - } + scheduler.sleep(tick); } -// NOTE: we could probably use milliseconds directly. pub fn task_ms_to_tick(ms: u32) callconv(.c) i32 { - // NOTE: idk about bitcast here. Seems weird that it returns i32. - return @intCast(ms * 1_000); + return @intCast(ms * 1_000 * systimer.ticks_per_us()); } pub fn task_get_current_task() callconv(.c) ?*anyopaque { - return multitasking.current_task; + return scheduler.current_task; } pub fn task_get_max_priority() callconv(.c) i32 { - return -1; + return @intFromEnum(Scheduler.Priority.highest); } pub fn get_free_heap_size() callconv(.c) void { @@ -1078,8 +983,7 @@ pub fn wifi_rtc_disable_iso() callconv(.c) void { pub fn esp_timer_get_time() callconv(.c) i64 { log.debug("esp_timer_get_time", .{}); - // TODO: or bit cast? - return @intCast(hal.time.get_time_since_boot().to_us()); + return @intCast(scheduler.get_ticks()); } pub fn nvs_set_i8() callconv(.c) void { diff --git a/port/espressif/esp/src/hal/radio/timer.zig b/port/espressif/esp/src/hal/radio/timer.zig index 5ef5316c8..2d99ad473 100644 --- a/port/espressif/esp/src/hal/radio/timer.zig +++ b/port/espressif/esp/src/hal/radio/timer.zig @@ -5,8 +5,6 @@ const microzig = @import("microzig"); const time = microzig.drivers.time; const hal = microzig.hal; -const multitasking = @import("multitasking.zig"); - const c = @import("esp-wifi-driver"); pub const CallbackFn = *const fn (?*anyopaque) callconv(.c) void; @@ -22,11 +20,6 @@ pub const Timer = struct { var timer_list: std.SinglyLinkedList = .{}; -// pub fn init(allocator: Allocator) !void { -// const task = try multitasking.Task.create(allocator, timer_task, null, 8192); -// multitasking.schedule_task(task); -// } - pub fn add(allocator: Allocator, ets_timer: *c.ets_timer, callback: CallbackFn, arg: ?*anyopaque) !void { const timer = try allocator.create(Timer); timer.* = .{ diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index c51ab0ce7..1db1d0582 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -89,17 +89,17 @@ pub const Config = union(enum) { ssid: []const u8, /// Whether the SSID is hidden or visible. - ssid_hidden: bool, + ssid_hidden: bool = false, /// The channel the access point will operate on. Ignored in `ap_sta` /// mode. channel: u8, /// The secondary channel configuration. - secondary_channel: ?u8, + secondary_channel: ?u8 = null, /// Authentication config to be used by the access point. - auth: ?Auth, + auth: ?Auth = null, /// The maximum number of connections allowed on the access point. max_connections: u8, @@ -513,8 +513,8 @@ pub fn send_packet(iface: Interface, data: []const u8) (error{TooManyPacketsInFl log.warn("too many packets in flight", .{}); return error.TooManyPacketsInFlight; } - _ = @atomicRmw(usize, &packets_in_flight, .Add, 1, .acq_rel); - errdefer _ = @atomicRmw(usize, &packets_in_flight, .Sub, 1, .acq_rel); + _ = @atomicRmw(usize, &packets_in_flight, .Add, 1, .monotonic); + errdefer _ = @atomicRmw(usize, &packets_in_flight, .Sub, 1, .monotonic); try c_result(c.esp_wifi_internal_tx(@intFromEnum(iface), @ptrCast(@constCast(data.ptr)), @intCast(data.len))); } From d53d2617b0be3f722c76384329e35881cd573ad3 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Sun, 28 Dec 2025 22:50:50 +0200 Subject: [PATCH 14/43] Completely port osi to new scheduler --- examples/espressif/esp/src/scheduler.zig | 33 +- examples/espressif/esp/src/wifi.zig | 64 +-- port/espressif/esp/src/hal/Scheduler.zig | 663 ++++++++++++++++++----- port/espressif/esp/src/hal/radio.zig | 2 +- port/espressif/esp/src/hal/radio/osi.zig | 213 ++++---- 5 files changed, 664 insertions(+), 311 deletions(-) diff --git a/examples/espressif/esp/src/scheduler.zig b/examples/espressif/esp/src/scheduler.zig index be013d551..329636121 100644 --- a/examples/espressif/esp/src/scheduler.zig +++ b/examples/espressif/esp/src/scheduler.zig @@ -21,20 +21,19 @@ pub const microzig_options: microzig.Options = .{ var heap_buf: [10 * 1024]u8 = undefined; var scheduler: esp.Scheduler = undefined; -var mutex: esp.Scheduler.Mutex = .{}; +var queue: esp.Scheduler.Queue(u32) = .init(&.{}); fn task1(_: ?*anyopaque) callconv(.c) noreturn { - var i: u32 = 0; - while (true) : (i += 1) { - std.log.info("hello from task 1: {}", .{i}); - // { - // mutex.lock(&scheduler); - // defer mutex.unlock(&scheduler); - // std.log.info("hello from task 1: {}", .{i}); - // scheduler.sleep(1_000_000 * systimer.ticks_per_us()); - // std.log.info("hello from task 1 still locking mutex", .{}); - // } - scheduler.sleep(500_000 * systimer.ticks_per_us()); + for (0..5) |i| { + queue.put_one(&scheduler, i) catch { + std.log.err("failed to put item", .{}); + continue; + }; + scheduler.sleep(.from_ms(500)); + } + queue.close(&scheduler); + while (true) { + microzig.cpu.wfi(); } } @@ -50,13 +49,7 @@ pub fn main() !void { }); while (true) { - { - mutex.lock(&scheduler); - defer mutex.unlock(&scheduler); - std.log.info("hello from main", .{}); - scheduler.sleep(5_000_000 * systimer.ticks_per_us()); - std.log.info("hello from main still locking mutex", .{}); - } - scheduler.sleep(5_000_000 * systimer.ticks_per_us()); + const item = try queue.get_one(&scheduler); + std.log.info("got item: {}", .{item}); } } diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index c99e6be5f..ffe09d242 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -36,7 +36,7 @@ pub const microzig_options: microzig.Options = .{ .interrupt31 = .{ .naked = Scheduler.isr_yield_handler }, }, .cpu = .{ - .interrupt_stack_size = 8192, + .interrupt_stack_size = 4096, }, .hal = .{ .radio = .{ @@ -58,17 +58,17 @@ pub fn main() !void { try radio.init(allocator, &scheduler); try radio.wifi.init(); - // c.lwip_init(); - // - // var netif: c.netif = undefined; - // _ = c.netif_add(&netif, @ptrCast(c.IP4_ADDR_ANY), @ptrCast(c.IP4_ADDR_ANY), @ptrCast(c.IP4_ADDR_ANY), null, netif_init, c.netif_input); - // @memcpy(&netif.name, "e0"); - // c.netif_create_ip6_linklocal_address(&netif, 1); - // netif.ip6_autoconfig_enabled = 1; - // c.netif_set_status_callback(&netif, netif_status_callback); - // c.netif_set_default(&netif); - // c.netif_set_up(&netif); - // _ = c.dhcp_start(&netif); + c.lwip_init(); + + var netif: c.netif = undefined; + _ = c.netif_add(&netif, @ptrCast(c.IP4_ADDR_ANY), @ptrCast(c.IP4_ADDR_ANY), @ptrCast(c.IP4_ADDR_ANY), null, netif_init, c.netif_input); + @memcpy(&netif.name, "e0"); + c.netif_create_ip6_linklocal_address(&netif, 1); + netif.ip6_autoconfig_enabled = 1; + c.netif_set_status_callback(&netif, netif_status_callback); + c.netif_set_default(&netif); + c.netif_set_up(&netif); + _ = c.dhcp_start(&netif); try radio.wifi.apply(.{ .sta = .{ @@ -80,7 +80,7 @@ pub fn main() !void { try radio.wifi.connect(); var connected: bool = false; - // var last_mem_show = hal.time.get_time_since_boot(); + var last_mem_show = hal.time.get_time_since_boot(); while (true) { radio.tick(); @@ -88,11 +88,11 @@ pub fn main() !void { const sta_state = radio.wifi.get_sta_state(); if (!connected and sta_state == .sta_connected) { std.log.info("link up", .{}); - // c.netif_set_link_up(&netif); + c.netif_set_link_up(&netif); connected = true; } else if (connected and sta_state == .sta_disconnected) { std.log.info("link down", .{}); - // c.netif_set_link_down(&netif); + c.netif_set_link_down(&netif); connected = false; } @@ -101,26 +101,26 @@ pub fn main() !void { std.log.info("packet received", .{}); - // const maybe_pbuf: ?*c.struct_pbuf = c.pbuf_alloc(c.PBUF_RAW, @intCast(packet.data.len), c.PBUF_POOL); - // if (maybe_pbuf) |pbuf| { - // _ = c.pbuf_take(pbuf, packet.data.ptr, @intCast(packet.data.len)); - // defer _ = c.pbuf_free(pbuf); - // - // if (c.netif_input(pbuf, &netif) != c.ERR_OK) { - // std.log.warn("lwip netif input failed", .{}); - // } - // } + const maybe_pbuf: ?*c.struct_pbuf = c.pbuf_alloc(c.PBUF_RAW, @intCast(packet.data.len), c.PBUF_POOL); + if (maybe_pbuf) |pbuf| { + _ = c.pbuf_take(pbuf, packet.data.ptr, @intCast(packet.data.len)); + defer _ = c.pbuf_free(pbuf); + + if (c.netif_input(pbuf, &netif) != c.ERR_OK) { + std.log.warn("lwip netif input failed", .{}); + } + } } - // c.sys_check_timeouts(); + c.sys_check_timeouts(); - // const now = hal.time.get_time_since_boot(); - // if (!now.diff(last_mem_show).less_than(.from_ms(1000))) { - // const used_mem = 50 * 1024 - alloc.free_heap(); - // std.log.info("used memory: {}K ({})", .{ used_mem / 1024, used_mem }); - // last_mem_show = now; - // } - scheduler.sleep(10_000 * hal.systimer.ticks_per_us()); + const now = hal.time.get_time_since_boot(); + if (!now.diff(last_mem_show).less_than(.from_ms(1000))) { + const used_mem = 50 * 1024 - alloc.free_heap(); + std.log.info("used memory: {}K ({})", .{ used_mem / 1024, used_mem }); + last_mem_show = now; + } + scheduler.sleep(.from_ms(10)); } // var ssid: [1:0]u8 = @splat(0); diff --git a/port/espressif/esp/src/hal/Scheduler.zig b/port/espressif/esp/src/hal/Scheduler.zig index 1f8db0ea7..9639d5416 100644 --- a/port/espressif/esp/src/hal/Scheduler.zig +++ b/port/espressif/esp/src/hal/Scheduler.zig @@ -3,6 +3,8 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const microzig = @import("microzig"); +const CriticalSection = microzig.interrupt.CriticalSection; +const enter_critical_section = microzig.interrupt.enter_critical_section; const TrapFrame = microzig.cpu.TrapFrame; const SYSTEM = microzig.chip.peripherals.SYSTEM; const time = microzig.drivers.time; @@ -35,11 +37,6 @@ const yield_interrupt: microzig.cpu.Interrupt = .interrupt31; const systimer_unit = systimer.unit(0); const systimer_alarm = systimer.alarm(0); -comptime { - if (microzig.options.cpu.interrupt_stack_size == null) - @compileError("Please enable interrupt stacks to use the scheduler"); -} - const Scheduler = @This(); var maybe_instance: ?*Scheduler = null; @@ -72,6 +69,11 @@ pub const Priority = enum(u8) { }; pub fn init(scheduler: *Scheduler, gpa: Allocator) void { + comptime { + if (microzig.options.cpu.interrupt_stack_size == null) + @compileError("Please enable interrupt stacks to use the scheduler"); + } + assert(maybe_instance == null); scheduler.* = .{ @@ -103,7 +105,7 @@ pub fn init(scheduler: *Scheduler, gpa: Allocator) void { microzig.cpu.interrupt.enable(yield_interrupt); microzig.cpu.interrupt.set_type(generic_interrupt, .level); - microzig.cpu.interrupt.set_priority(generic_interrupt, .highest); + microzig.cpu.interrupt.set_priority(generic_interrupt, .lowest); microzig.cpu.interrupt.map(.systimer_target0, generic_interrupt); microzig.cpu.interrupt.enable(generic_interrupt); @@ -173,35 +175,29 @@ pub fn raw_alloc_spawn_with_options( } pub fn make_task_ready(scheduler: *Scheduler, task: *Task) void { - const cs = microzig.interrupt.enter_critical_section(); + const cs = enter_critical_section(); defer cs.leave(); - scheduler.make_task_ready_from_cs(task); + scheduler.make_task_ready_from_cs(task, cs); } -pub fn make_task_ready_from_cs(scheduler: *Scheduler, task: *Task) void { +pub fn make_task_ready_from_cs(scheduler: *Scheduler, task: *Task, _: CriticalSection) void { switch (task.state) { .running, .none => {}, .ready => return, .alarm_set => |_| { - scheduler.timer_queue.remove(&task.internal_node); + scheduler.timer_queue.remove(&task.node); }, .suspended => { - scheduler.suspended_list.remove(&task.internal_node); + scheduler.suspended_list.remove(&task.node); }, } - // Remove the task from any external waiting list (eg: from a mutex). - if (task.waiting_queue) |waiting_queue| { - waiting_queue.inner.remove(&task.waiting_queue_node); - task.waiting_queue = null; - } - task.state = .ready; scheduler.ready_queue.append(task); } -pub fn change_task_priority_from_cs(scheduler: *Scheduler, task: *Task, new_priority: Priority) void { +pub fn change_task_priority_from_cs(scheduler: *Scheduler, task: *Task, new_priority: Priority, _: CriticalSection) void { task.priority = new_priority; switch (task.state) { @@ -211,30 +207,21 @@ pub fn change_task_priority_from_cs(scheduler: *Scheduler, task: *Task, new_prio }, else => {}, } - - if (task.waiting_queue) |waiting_queue| { - waiting_queue.inner.remove(&task.waiting_queue_node); - waiting_queue.append(&task.waiting_queue_node); - } } pub const YieldAction = union(enum) { reschedule, - wait: ?u52, + wait: ?TimerTicks, }; pub inline fn yield(scheduler: *Scheduler, action: YieldAction) void { - const cs = microzig.interrupt.enter_critical_section(); + const cs = enter_critical_section(); scheduler.yield_and_leave_cs(action, cs); } /// Must be called inside critical section. Calling leave on the critical /// section becomes unnecessary. -pub inline fn yield_and_leave_cs( - scheduler: *Scheduler, - action: YieldAction, - cs: microzig.interrupt.CriticalSection, -) void { +pub inline fn yield_and_leave_cs(scheduler: *Scheduler, action: YieldAction, cs: CriticalSection) void { const prev_context, const next_context = scheduler.yield_inner(action); context_switch(prev_context, next_context); if (!cs.enable_on_leave) { @@ -254,7 +241,7 @@ fn yield_inner(scheduler: *Scheduler, action: YieldAction) struct { *Context, *C scheduler.schedule_wake_at(prev_task, timeout); } else { prev_task.state = .suspended; - scheduler.suspended_list.append(&prev_task.internal_node); + scheduler.suspended_list.append(&prev_task.node); } }, } @@ -267,14 +254,8 @@ fn yield_inner(scheduler: *Scheduler, action: YieldAction) struct { *Context, *C return .{ &prev_task.context, &next_task.context }; } -pub fn get_ticks(_: *Scheduler) u52 { - return systimer_unit.read(); -} - -pub fn sleep(scheduler: *Scheduler, ticks: u52) void { - const now_ticks = scheduler.get_ticks(); - const timeout_ticks = now_ticks +% ticks; - scheduler.yield(.{ .wait = timeout_ticks }); +pub fn sleep(scheduler: *Scheduler, duration: time.Duration) void { + scheduler.yield(.{ .wait = .after(duration) }); } inline fn context_switch(prev_context: *Context, next_context: *Context) void { @@ -331,14 +312,17 @@ inline fn context_switch(prev_context: *Context, next_context: *Context) void { }); } -pub fn yield_from_isr(scheduler: *Scheduler) void { - if (scheduler.ready_queue.peek_top()) |top_ready_task| { - if (top_ready_task.priority.is_greater(scheduler.current_task.priority)) { - SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ - .CPU_INTR_FROM_CPU_0 = 1, - }); - } - } +pub fn yield_from_isr() void { + SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ + .CPU_INTR_FROM_CPU_0 = 1, + }); +} + +pub fn is_a_higher_priority_task_ready(scheduler: *Scheduler) bool { + return if (scheduler.ready_queue.peek_top()) |top_ready_task| + top_ready_task.priority.is_greater(scheduler.current_task.priority) + else + false; } pub fn isr_yield_handler() linksection(".ram_vectors") callconv(.naked) void { @@ -494,25 +478,24 @@ fn schedule_in_isr(context: *Context) linksection(".ram_vectors") callconv(.c) v } /// Must be called from a critical section. -fn schedule_wake_at(scheduler: *Scheduler, sleeping_task: *Task, timeout: u52) void { - sleeping_task.state = .{ .alarm_set = timeout }; +fn schedule_wake_at(scheduler: *Scheduler, sleeping_task: *Task, ticks: TimerTicks) void { + sleeping_task.state = .{ .alarm_set = ticks }; var maybe_node = scheduler.timer_queue.first; while (maybe_node) |node| : (maybe_node = node.next) { - const task: *Task = @alignCast(@fieldParentPtr("internal_node", node)); - const wake_ticks = task.state.alarm_set; - if (is_a_before_b(timeout, wake_ticks)) { - scheduler.timer_queue.insertBefore(&task.internal_node, &sleeping_task.internal_node); + const task: *Task = @alignCast(@fieldParentPtr("node", node)); + if (ticks.is_before(task.state.alarm_set)) { + scheduler.timer_queue.insertBefore(&task.node, &sleeping_task.node); break; } } else { - scheduler.timer_queue.append(&sleeping_task.internal_node); + scheduler.timer_queue.append(&sleeping_task.node); } // If we updated the first element of the list, it means that we have to // reschedule the timer - if (scheduler.timer_queue.first == &sleeping_task.internal_node) { - systimer_alarm.set_target(timeout); + if (scheduler.timer_queue.first == &sleeping_task.node) { + systimer_alarm.set_target(@intFromEnum(ticks)); systimer_alarm.set_enabled(true); } } @@ -525,32 +508,32 @@ pub fn generic_interrupt_handler(_: *TrapFrame) callconv(.c) void { switch (source) { .systimer_target0 => { systimer_alarm.clear_interrupt(); - systimer_alarm.set_enabled(false); - const now_ticks = scheduler.get_ticks(); + const cs = enter_critical_section(); + defer cs.leave(); - var maybe_node = scheduler.timer_queue.first; - while (maybe_node) |node| : (maybe_node = node.next) { - const task: *Task = @alignCast(@fieldParentPtr("internal_node", node)); - const wake_ticks = task.state.alarm_set; - if (is_a_before_b(now_ticks, wake_ticks)) { + while (scheduler.timer_queue.first) |node| { + const task: *Task = @alignCast(@fieldParentPtr("node", node)); + if (!task.state.alarm_set.is_reached()) { break; } - - scheduler.make_task_ready_from_cs(task); + scheduler.make_task_ready_from_cs(task, cs); } if (scheduler.timer_queue.first) |node| { - const task: *Task = @alignCast(@fieldParentPtr("internal_node", node)); - systimer_alarm.set_target(task.state.alarm_set); + const task: *Task = @alignCast(@fieldParentPtr("node", node)); + systimer_alarm.set_target(@intFromEnum(task.state.alarm_set)); systimer_alarm.set_enabled(true); + } else { + systimer_alarm.set_enabled(false); } }, else => {}, } } - scheduler.yield_from_isr(); + if (scheduler.is_a_higher_priority_task_ready()) + yield_from_isr(); } pub const Task = struct { @@ -562,12 +545,7 @@ pub const Task = struct { state: State = .none, /// Node used for scheduler internal lists. - internal_node: std.DoublyLinkedList.Node = .{}, - - /// In which external waiting queue is this task in. - waiting_queue: ?*WaitingPriorityQueue = null, - /// Node used for external waiting queue. - waiting_queue_node: std.DoublyLinkedList.Node = .{}, + node: std.DoublyLinkedList.Node = .{}, /// Task specific semaphore (required by the wifi driver) semaphore: Semaphore = .init(0), @@ -576,7 +554,7 @@ pub const Task = struct { none, ready, running, - alarm_set: u52, + alarm_set: TimerTicks, suspended, }; @@ -586,7 +564,7 @@ pub const Task = struct { pub fn peek_top(pq: *ReadyPriorityQueue) ?*Task { if (pq.inner.first) |first_node| { - return @alignCast(@fieldParentPtr("internal_node", first_node)); + return @alignCast(@fieldParentPtr("node", first_node)); } else { return null; } @@ -599,7 +577,7 @@ pub const Task = struct { return null; } } - pq.inner.remove(&task.internal_node); + pq.inner.remove(&task.node); return task; } return null; @@ -608,40 +586,13 @@ pub const Task = struct { pub fn append(pq: *ReadyPriorityQueue, new_task: *Task) void { var maybe_node = pq.inner.first; while (maybe_node) |node| : (maybe_node = node.next) { - const task: *Task = @alignCast(@fieldParentPtr("internal_node", node)); + const task: *Task = @alignCast(@fieldParentPtr("node", node)); if (new_task.priority.is_greater(task.priority)) { - pq.inner.insertBefore(node, &new_task.internal_node); + pq.inner.insertBefore(node, &new_task.node); break; } } else { - pq.inner.append(&new_task.internal_node); - } - } - }; - - pub const WaitingPriorityQueue = struct { - inner: std.DoublyLinkedList = .{}, - - pub fn first(pq: *WaitingPriorityQueue) ?*Task { - if (pq.inner.first) |first_node| { - return @alignCast(@fieldParentPtr("waiting_queue_node", first_node)); - } else { - return null; - } - } - - pub fn append(pq: *WaitingPriorityQueue, new_task: *Task) void { - new_task.waiting_queue = pq; - - var maybe_node = pq.inner.first; - while (maybe_node) |node| : (maybe_node = node.next) { - const task: *Task = @alignCast(@fieldParentPtr("waiting_queue_node", node)); - if (new_task.priority.is_greater(task.priority)) { - pq.inner.insertBefore(node, &new_task.waiting_queue_node); - break; - } - } else { - pq.inner.append(&new_task.waiting_queue_node); + pq.inner.append(&new_task.node); } } }; @@ -660,10 +611,41 @@ pub const Context = extern struct { } }; +pub const WaitingList = struct { + inner: std.DoublyLinkedList = .{}, + + pub const Awaiter = struct { + task: *Task, + node: std.DoublyLinkedList.Node = .{}, + }; + + pub fn get_highest_priority(l: *WaitingList) ?*Task { + var maybe_node = l.inner.first; + var maybe_max_priority_awaiter: ?*Awaiter = null; + while (maybe_node) |node| : (maybe_node = node.next) { + const awaiter: *Awaiter = @fieldParentPtr("node", node); + if (maybe_max_priority_awaiter) |max_priority_awaiter| { + if (awaiter.task.priority.is_greater(max_priority_awaiter.task.priority)) { + maybe_max_priority_awaiter = awaiter; + } + } else { + maybe_max_priority_awaiter = awaiter; + } + } + + return if (maybe_max_priority_awaiter) |max_priority_awaiter| + max_priority_awaiter.task + else + null; + } +}; + +pub const TimeoutError = error{Timeout}; + // TODO: implement priority inheritance pub const Mutex = struct { state: State = .unlocked, - waiting_queue: Task.WaitingPriorityQueue = .{}, + awaiters: WaitingList = .{}, pub const State = enum(u32) { locked, @@ -674,33 +656,43 @@ pub const Mutex = struct { lock_with_timeout(mutex, scheduler, null) catch unreachable; } - pub fn lock_with_timeout(mutex: *Mutex, scheduler: *Scheduler, maybe_timeout: ?u52) error{Timeout}!void { - const cs = microzig.interrupt.enter_critical_section(); + pub fn lock_with_timeout(mutex: *Mutex, scheduler: *Scheduler, maybe_timeout: ?time.Duration) TimeoutError!void { + const cs = enter_critical_section(); defer cs.leave(); + const maybe_timeout_ticks: ?TimerTicks = if (maybe_timeout) |timeout| + .after(timeout) + else + null; + + var awaiter: WaitingList.Awaiter = .{ + .task = scheduler.current_task, + }; + mutex.awaiters.inner.append(&awaiter.node); + defer mutex.awaiters.inner.remove(&awaiter.node); + while (mutex.state != .unlocked) { - const current_task = scheduler.current_task; - mutex.waiting_queue.append(current_task); - scheduler.yield(.{ .wait = maybe_timeout }); - if (maybe_timeout) |timeout| { - if (!is_a_before_b(scheduler.get_ticks(), timeout)) { + if (maybe_timeout_ticks) |timeout_ticks| { + if (timeout_ticks.is_reached()) { return error.Timeout; } } + + scheduler.yield(.{ .wait = maybe_timeout_ticks }); } mutex.state = .locked; } pub fn unlock(mutex: *Mutex, scheduler: *Scheduler) void { - const cs = microzig.interrupt.enter_critical_section(); + const cs = enter_critical_section(); defer scheduler.yield_and_leave_cs(.reschedule, cs); assert(mutex.state == .locked); mutex.state = .unlocked; - if (mutex.waiting_queue.first()) |waiting_task| { - scheduler.make_task_ready_from_cs(waiting_task); + if (mutex.awaiters.get_highest_priority()) |task| { + scheduler.make_task_ready_from_cs(task, cs); } } }; @@ -708,10 +700,10 @@ pub const Mutex = struct { pub const RecursiveMutex = struct { value: u32 = 0, owning_task: ?*Task = null, - waiting_queue: Task.WaitingPriorityQueue = .{}, + awaiters: WaitingList = .{}, pub fn lock(mutex: *RecursiveMutex, scheduler: *Scheduler) void { - const cs = microzig.interrupt.enter_critical_section(); + const cs = enter_critical_section(); defer cs.leave(); const current_task = scheduler.current_task; @@ -721,8 +713,13 @@ pub const RecursiveMutex = struct { return; } + var awaiter: WaitingList.Awaiter = .{ + .task = current_task, + }; + mutex.awaiters.inner.append(&awaiter.node); + defer mutex.awaiters.inner.remove(&awaiter.node); + while (mutex.owning_task != null) { - mutex.waiting_queue.append(current_task); scheduler.yield(.{ .wait = null }); } @@ -732,7 +729,7 @@ pub const RecursiveMutex = struct { } pub fn unlock(mutex: *RecursiveMutex, scheduler: *Scheduler) bool { - const cs = microzig.interrupt.enter_critical_section(); + const cs = enter_critical_section(); assert(mutex.value > 0); mutex.value -= 1; @@ -741,8 +738,8 @@ pub const RecursiveMutex = struct { mutex.owning_task = null; - if (mutex.waiting_queue.first()) |waiting_task| { - scheduler.make_task_ready_from_cs(waiting_task); + if (mutex.awaiters.get_highest_priority()) |task| { + scheduler.make_task_ready_from_cs(task, cs); } return true; @@ -755,7 +752,7 @@ pub const RecursiveMutex = struct { pub const Semaphore = struct { value: u32, - waiting_queue: Task.WaitingPriorityQueue = .{}, + awaiters: WaitingList = .{}, pub fn init(initial_value: u32) Semaphore { return .{ @@ -767,42 +764,432 @@ pub const Semaphore = struct { sem.take_with_timeout(scheduler, null) catch unreachable; } - pub fn take_with_timeout(sem: *Semaphore, scheduler: *Scheduler, maybe_timeout: ?u52) error{Timeout}!void { - const cs = microzig.interrupt.enter_critical_section(); + pub fn take_with_timeout(sem: *Semaphore, scheduler: *Scheduler, maybe_timeout: ?time.Duration) TimeoutError!void { + const cs = enter_critical_section(); defer cs.leave(); + const maybe_timeout_ticks: ?TimerTicks = if (maybe_timeout) |timeout| + .after(timeout) + else + null; + + var awaiter: WaitingList.Awaiter = .{ + .task = scheduler.current_task, + }; + sem.awaiters.inner.append(&awaiter.node); + defer sem.awaiters.inner.remove(&awaiter.node); + while (sem.value <= 0) { - if (maybe_timeout) |timeout| { - if (!is_a_before_b(scheduler.get_ticks(), timeout)) { + if (maybe_timeout_ticks) |timeout_ticks| { + if (timeout_ticks.is_reached()) { return error.Timeout; } } - const current_task = scheduler.current_task; - sem.waiting_queue.append(current_task); - scheduler.yield(.{ .wait = maybe_timeout }); + scheduler.yield(.{ .wait = maybe_timeout_ticks }); } sem.value -= 1; } pub fn give(sem: *Semaphore, scheduler: *Scheduler) void { - const cs = microzig.interrupt.enter_critical_section(); + const cs = enter_critical_section(); defer scheduler.yield_and_leave_cs(.reschedule, cs); sem.value += 1; - if (sem.waiting_queue.first()) |waiting_task| { - scheduler.make_task_ready_from_cs(waiting_task); + if (sem.awaiters.get_highest_priority()) |task| { + scheduler.make_task_ready_from_cs(task, cs); } } }; +pub const QueueClosedError = error{Closed}; + pub const TypeErasedQueue = struct { - buffer: []const u8, + closed: bool, + + /// Ring buffer. This data is logically *after* queued getters. + buffer: []u8, + start: usize, + len: usize, + + putters: std.DoublyLinkedList, + getters: std.DoublyLinkedList, + + const Put = struct { + remaining: []const u8, + needed: usize, + task: *Task, + node: std.DoublyLinkedList.Node, + }; + + const Get = struct { + remaining: []u8, + needed: usize, + task: *Task, + node: std.DoublyLinkedList.Node, + }; + + pub fn init(buffer: []u8) TypeErasedQueue { + return .{ + .closed = false, + .buffer = buffer, + .start = 0, + .len = 0, + .putters = .{}, + .getters = .{}, + }; + } + + pub fn close(q: *TypeErasedQueue, scheduler: *Scheduler) void { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + q.closed = true; + + { + var it = q.getters.first; + while (it) |node| : (it = node.next) { + const getter: *Get = @alignCast(@fieldParentPtr("node", node)); + scheduler.make_task_ready_from_cs(getter.task, cs); + } + } + + { + var it = q.putters.first; + while (it) |node| : (it = node.next) { + const putter: *Put = @alignCast(@fieldParentPtr("node", node)); + scheduler.make_task_ready_from_cs(putter.task, cs); + } + } + } + + pub fn put(q: *TypeErasedQueue, scheduler: *Scheduler, elements: []const u8, min: usize) QueueClosedError!usize { + return q.put_with_timeout(scheduler, elements, min, null) catch |err| switch (err) { + error.Timeout => unreachable, + error.Closed => return error.Closed, + }; + } + + pub fn put_with_timeout( + q: *TypeErasedQueue, + scheduler: *Scheduler, + elements: []const u8, + min: usize, + maybe_timeout: ?time.Duration, + ) (QueueClosedError || TimeoutError)!usize { + assert(elements.len >= min); + if (elements.len == 0) return 0; + + const cs = enter_critical_section(); + defer cs.leave(); + + const n = try q.put_nonblocking(scheduler, elements, cs); + + // Don't block if we hit the target. + if (n >= min) return min; + const maybe_timeout_ticks: ?TimerTicks = if (maybe_timeout) |timeout| + .after(timeout) + else + null; + + var pending: Put = .{ + .remaining = elements[n..], + .needed = min - n, + .task = scheduler.current_task, + .node = .{}, + }; + q.putters.append(&pending.node); + defer if (pending.needed > 0) q.putters.remove(&pending.node); + + while (pending.needed > 0 and !q.closed) { + if (maybe_timeout_ticks) |timeout_ticks| { + if (timeout_ticks.is_reached()) { + return error.Timeout; + } + } + + scheduler.yield(.{ .wait = maybe_timeout_ticks }); + } + + if (pending.remaining.len == elements.len) { + // The queue was closed while we were waiting. We appended no elements. + assert(q.closed); + return error.Closed; + } + return elements.len - pending.remaining.len; + } + + pub fn put_from_isr(q: *TypeErasedQueue, scheduler: *Scheduler, elements: []const u8) QueueClosedError!usize { + const cs = enter_critical_section(); + defer cs.leave(); + + return q.put_nonblocking(scheduler, elements, cs); + } + + pub fn put_nonblocking(q: *TypeErasedQueue, scheduler: *Scheduler, elements: []const u8, cs: CriticalSection) QueueClosedError!usize { + // A closed queue cannot be added to, even if there is space in the buffer. + if (q.closed) return error.Closed; + + // Getters have first priority on the data, and only when the getters + // queue is empty do we start populating the buffer. + + // The number of elements we add immediately, before possibly blocking. + var n: usize = 0; + + while (q.getters.popFirst()) |getter_node| { + const getter: *Get = @alignCast(@fieldParentPtr("node", getter_node)); + const copy_len = @min(getter.remaining.len, elements.len - n); + assert(copy_len > 0); + @memcpy(getter.remaining[0..copy_len], elements[n..][0..copy_len]); + getter.remaining = getter.remaining[copy_len..]; + getter.needed -|= copy_len; + n += copy_len; + if (getter.needed == 0) { + scheduler.make_task_ready_from_cs(getter.task, cs); + } else { + assert(n == elements.len); // we didn't have enough elements for the getter + q.getters.prepend(getter_node); + } + if (n == elements.len) return elements.len; + } + + while (q.puttable_slice()) |slice| { + const copy_len = @min(slice.len, elements.len - n); + assert(copy_len > 0); + @memcpy(slice[0..copy_len], elements[n..][0..copy_len]); + q.len += copy_len; + n += copy_len; + if (n == elements.len) return elements.len; + } + + return n; + } + + fn puttable_slice(q: *const TypeErasedQueue) ?[]u8 { + const unwrapped_index = q.start + q.len; + const wrapped_index, const overflow = @subWithOverflow(unwrapped_index, q.buffer.len); + const slice = switch (overflow) { + 1 => q.buffer[unwrapped_index..], + 0 => q.buffer[wrapped_index..q.start], + }; + return if (slice.len > 0) slice else null; + } + + pub fn get(q: *TypeErasedQueue, scheduler: *Scheduler, buffer: []u8, min: usize) QueueClosedError!usize { + return q.put_with_timeout(scheduler, buffer, min, null) catch |err| switch (err) { + error.Timeout => unreachable, + error.Closed => return error.Closed, + }; + } + + pub fn get_with_timeout( + q: *TypeErasedQueue, + scheduler: *Scheduler, + buffer: []u8, + min: usize, + maybe_timeout: ?time.Duration, + ) (QueueClosedError || TimeoutError)!usize { + assert(buffer.len >= min); + if (buffer.len == 0) return 0; + + const cs = enter_critical_section(); + defer cs.leave(); + + // The ring buffer gets first priority, then data should come from any + // queued putters, then finally the ring buffer should be filled with + // data from putters so they can be resumed. + + // The number of elements we received immediately, before possibly blocking. + var n: usize = 0; + + while (q.gettable_slice()) |slice| { + const copy_len = @min(slice.len, buffer.len - n); + assert(copy_len > 0); + @memcpy(buffer[n..][0..copy_len], slice[0..copy_len]); + q.start += copy_len; + if (q.buffer.len - q.start == 0) q.start = 0; + q.len -= copy_len; + n += copy_len; + if (n == buffer.len) { + q.fill_ring_buffer_from_putters(scheduler); + return buffer.len; + } + } + + // Copy directly from putters into buffer. + while (q.putters.popFirst()) |putter_node| { + const putter: *Put = @alignCast(@fieldParentPtr("node", putter_node)); + const copy_len = @min(putter.remaining.len, buffer.len - n); + assert(copy_len > 0); + @memcpy(buffer[n..][0..copy_len], putter.remaining[0..copy_len]); + putter.remaining = putter.remaining[copy_len..]; + putter.needed -|= copy_len; + n += copy_len; + if (putter.needed == 0) { + scheduler.make_task_ready_from_cs(putter.task, cs); + } else { + assert(n == buffer.len); // we didn't have enough space for the putter + q.putters.prepend(putter_node); + } + if (n == buffer.len) { + q.fill_ring_buffer_from_putters(scheduler); + return buffer.len; + } + } + + // No need to call `fillRingBufferFromPutters` from this point onwards, + // because we emptied the ring buffer *and* the putter queue! + + // Don't block if we hit the target or if the queue is closed. Return how + // many elements we could get immediately, unless the queue was closed and + // empty, in which case report `error.Closed`. + if (n == 0 and q.closed) return error.Closed; + if (n >= min or q.closed) return n; + + const maybe_timeout_ticks: ?TimerTicks = if (maybe_timeout) |timeout| + .after(timeout) + else + null; + + var pending: Get = .{ + .remaining = buffer[n..], + .needed = min - n, + .task = scheduler.current_task, + .node = .{}, + }; + q.getters.append(&pending.node); + defer if (pending.needed > 0) q.getters.remove(&pending.node); + + while (pending.needed > 0 and !q.closed) { + if (maybe_timeout_ticks) |timeout_ticks| { + if (timeout_ticks.is_reached()) { + return error.Timeout; + } + } + + scheduler.yield(.{ .wait = maybe_timeout_ticks }); + } + + if (pending.remaining.len == buffer.len) { + // The queue was closed while we were waiting. We received no elements. + assert(q.closed); + return error.Closed; + } + + return buffer.len - pending.remaining.len; + } + + fn gettable_slice(q: *const TypeErasedQueue) ?[]const u8 { + const overlong_slice = q.buffer[q.start..]; + const slice = overlong_slice[0..@min(overlong_slice.len, q.len)]; + return if (slice.len > 0) slice else null; + } + + /// Called when there is nonzero space available in the ring buffer and + /// potentially putters waiting. The mutex is already held and the task is + /// to copy putter data to the ring buffer and signal any putters whose + /// buffers been fully copied. + fn fill_ring_buffer_from_putters(q: *TypeErasedQueue, scheduler: *Scheduler) void { + while (q.putters.popFirst()) |putter_node| { + const putter: *Put = @alignCast(@fieldParentPtr("node", putter_node)); + while (q.puttable_slice()) |slice| { + const copy_len = @min(slice.len, putter.remaining.len); + assert(copy_len > 0); + @memcpy(slice[0..copy_len], putter.remaining[0..copy_len]); + q.len += copy_len; + putter.remaining = putter.remaining[copy_len..]; + putter.needed -|= copy_len; + if (putter.needed == 0) { + scheduler.yield(.{ .wait = null }); + break; + } + } else { + q.putters.prepend(putter_node); + break; + } + } + } }; -fn is_a_before_b(a: u52, b: u52) bool { - return a < b or b -% a < a; +pub fn Queue(Elem: type) type { + return struct { + const Self = @This(); + + type_erased: TypeErasedQueue, + + pub fn init(buffer: []Elem) Self { + return .{ .type_erased = .init(@ptrCast(buffer)) }; + } + + pub fn close(q: *Self, scheduler: *Scheduler) void { + q.type_erased.close(scheduler); + } + + pub fn put(q: *Self, scheduler: *Scheduler, elements: []const Elem, min: usize) QueueClosedError!usize { + return @divExact(try q.type_erased.put(scheduler, @ptrCast(elements), min * @sizeOf(Elem)), @sizeOf(Elem)); + } + + pub fn put_all(q: *Self, scheduler: *Scheduler, elements: []const Elem) QueueClosedError!void { + const n = try q.put(scheduler, elements, elements.len); + if (n != elements.len) { + _ = try q.put(scheduler, elements[n..], elements.len - n); + unreachable; + } + } + + pub fn put_one(q: *Self, scheduler: *Scheduler, item: Elem) QueueClosedError!void { + assert(try q.put(scheduler, &.{item}, 1) == 1); + } + + pub fn put_from_isr(q: *Self, scheduler: *Scheduler, elements: []const Elem) QueueClosedError!usize { + return @divExact(try q.type_erased.put_from_isr(scheduler, @ptrCast(elements)), @sizeOf(Elem)); + } + + pub fn put_one_from_isr(q: *Self, scheduler: *Scheduler, item: Elem) QueueClosedError!bool { + return try q.type_erased.put_from_isr(scheduler, @ptrCast(&item)) == @sizeOf(Elem); + } + + pub fn get(q: *Self, scheduler: *Scheduler, buffer: []Elem, target: usize) QueueClosedError!usize { + return @divExact(try q.type_erased.get(scheduler, @ptrCast(buffer), target * @sizeOf(Elem)), @sizeOf(Elem)); + } + + pub fn get_one(q: *Self, scheduler: *Scheduler) QueueClosedError!Elem { + var buf: [1]Elem = undefined; + assert(try q.get(scheduler, &buf, 1) == 1); + return buf[0]; + } + + pub fn capacity(q: *const Self) usize { + return @divExact(q.type_erased.buffer.len, @sizeOf(Elem)); + } + }; } + +pub const TimerTicks = enum(u52) { + _, + + pub fn now() TimerTicks { + return @enumFromInt(systimer_unit.read()); + } + + pub fn after(duration: time.Duration) TimerTicks { + return TimerTicks.now().add_duration(duration); + } + + pub fn is_reached(ticks: TimerTicks) bool { + return ticks.is_before(.now()); + } + + pub fn is_before(a: TimerTicks, b: TimerTicks) bool { + const _a = @intFromEnum(a); + const _b = @intFromEnum(b); + return _a < _b or _b -% _a < _a; + } + + pub fn add_duration(ticks: TimerTicks, duration: time.Duration) TimerTicks { + return @enumFromInt(@intFromEnum(ticks) +% @as(u52, @intCast(duration.to_us())) * systimer.ticks_per_us()); + } +}; diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index f47fdff75..35b6ba1f2 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -142,7 +142,7 @@ fn setup_interrupts() void { microzig.cpu.interrupt.map(.wifi_pwr, options.wifi_interrupt); microzig.cpu.interrupt.set_type(options.wifi_interrupt, .level); - microzig.cpu.interrupt.set_priority(options.wifi_interrupt, .highest); + microzig.cpu.interrupt.set_priority(options.wifi_interrupt, .lowest); } pub const interrupt_handlers = struct { diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 16e365436..0d5572081 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -9,8 +9,9 @@ const peripherals = microzig.chip.peripherals; const APB_CTRL = peripherals.APB_CTRL; const hal = microzig.hal; -const systimer = @import("../systimer.zig"); const Scheduler = @import("../Scheduler.zig"); +const systimer = @import("../systimer.zig"); +const get_time_since_boot = @import("../time.zig").get_time_since_boot; const timer = @import("timer.zig"); const wifi = @import("wifi.zig"); @@ -133,7 +134,7 @@ pub fn puts(ptr: ?*anyopaque) callconv(.c) void { pub fn gettimeofday(tv: ?*c.timeval, _: ?*anyopaque) callconv(.c) i32 { if (tv) |time_val| { - const usec = hal.time.get_time_since_boot().to_us(); + const usec = get_time_since_boot().to_us(); time_val.tv_sec = usec / 1_000_000; time_val.tv_usec = @intCast(usec % 1_000_000); } @@ -142,12 +143,12 @@ pub fn gettimeofday(tv: ?*c.timeval, _: ?*anyopaque) callconv(.c) i32 { } pub fn sleep(time_sec: c_uint) callconv(.c) c_int { - scheduler.sleep(time_sec * 1_000_000 * systimer.ticks_per_us()); + scheduler.sleep(.from_us(time_sec * 1_000_000)); return 0; } pub fn usleep(time_us: u32) callconv(.c) c_int { - scheduler.sleep(time_us * systimer.ticks_per_us()); + scheduler.sleep(.from_us(time_us)); return 0; } @@ -295,7 +296,7 @@ pub fn wifi_int_restore(mux_ptr: ?*anyopaque, state: u32) callconv(.c) void { pub fn task_yield_from_isr() callconv(.c) void { log.debug("task_yield_from_isr", .{}); - scheduler.yield_from_isr(); + Scheduler.yield_from_isr(); } pub fn semphr_create(max_value: u32, init_value: u32) callconv(.c) ?*anyopaque { @@ -322,7 +323,11 @@ pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { log.debug("semphr_take {?} {}", .{ ptr, tick }); const sem: *Scheduler.Semaphore = @ptrCast(@alignCast(ptr)); - sem.take_with_timeout(scheduler, tick) catch { + const maybe_timeout: ?time.Duration = if (tick == c.OSI_FUNCS_TIME_BLOCKING) + .from_us(tick) + else + null; + sem.take_with_timeout(scheduler, maybe_timeout) catch { log.debug(">>>> return from semaphore take with timeout: {*}", .{sem}); return 1; }; @@ -384,61 +389,9 @@ pub fn mutex_unlock(ptr: ?*anyopaque) callconv(.c) i32 { return @intFromBool(mutex.unlock(scheduler)); } -// TODO: maybe use atomics? maybe it is spsc? -pub const Queue = struct { - len: usize = 0, - capacity: usize, - item_len: usize, - read_index: usize = 0, - write_index: usize = 0, - storage: []u8, - - pub fn create(capacity: usize, item_len: usize) error{OutOfMemory}!*Queue { - const queue = try allocator.create(Queue); - errdefer allocator.destroy(queue); - - queue.* = .{ - .capacity = capacity, - .item_len = item_len, - .storage = try allocator.alloc(u8, capacity * item_len), - }; - - return queue; - } - - pub fn destroy(self: *Queue) void { - allocator.free(self.storage); - allocator.destroy(self); - } - - pub fn get(self: Queue, index: usize) []u8 { - const item_start = self.item_len * index; - return self.storage[item_start..][0..self.item_len]; - } - - pub fn enqueue(self: *Queue, item: [*]const u8) error{QueueFull}!void { - if (self.len >= self.capacity) { - return error.QueueFull; - } - - const slot = self.get(self.write_index); - @memcpy(slot, item[0..self.item_len]); - log.debug(">>>> queue send: {any}", .{item[0..self.item_len]}); - self.write_index = (self.write_index + 1) % self.capacity; - self.len += 1; - } - - pub fn dequeue(self: *Queue, item: [*]u8) error{QueueEmpty}!void { - if (self.len == 0) { - return error.QueueEmpty; - } - - const slot = self.get(self.read_index); - @memcpy(item[0..self.item_len], slot); - log.debug(">>>> queue recv: {any}", .{item[0..self.item_len]}); - self.read_index = (self.read_index + 1) % self.capacity; - self.len -= 1; - } +pub const QueueWrapper = struct { + item_len: u32, + inner: Scheduler.TypeErasedQueue, }; pub fn queue_create(capacity: u32, item_len: u32) callconv(.c) ?*anyopaque { @@ -451,11 +404,22 @@ pub fn queue_create(capacity: u32, item_len: u32) callconv(.c) ?*anyopaque { break :blk .{ 3, 8 }; }; - const queue = Queue.create(new_cap, new_item_len) catch { + const buf: []u8 = allocator.alloc(u8, new_cap) catch { + log.warn("failed to allocate queue buffer", .{}); + return null; + }; + + const queue = allocator.create(QueueWrapper) catch { log.warn("failed to allocate queue", .{}); + allocator.free(buf); return null; }; + queue.* = .{ + .item_len = new_item_len, + .inner = .init(buf), + }; + log.debug(">>>> queue create: {*}", .{queue}); return queue; @@ -464,37 +428,46 @@ pub fn queue_create(capacity: u32, item_len: u32) callconv(.c) ?*anyopaque { pub fn queue_delete(ptr: ?*anyopaque) callconv(.c) void { log.debug("queue_delete {?}", .{ptr}); - const queue: *Queue = @ptrCast(@alignCast(ptr)); - queue.destroy(); -} - -// NOTE: here we ignore the timeout. The rust version doesn't use it. -fn queue_send_common(ptr: ?*anyopaque, item_ptr: ?*anyopaque) callconv(.c) i32 { - const queue: *Queue = @ptrCast(@alignCast(ptr)); - const item: [*]const u8 = @ptrCast(@alignCast(item_ptr)); - - const cs = enter_critical_section(); - defer cs.leave(); - - queue.enqueue(item) catch { - log.warn("failed to add item to queue", .{}); - return 0; - }; - - return 1; + const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); + allocator.free(queue.inner.buffer); + allocator.destroy(queue); } pub fn queue_send(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) callconv(.c) i32 { log.debug("queue_send {?} {?} {}", .{ ptr, item_ptr, block_time_tick }); - return queue_send_common(ptr, item_ptr); + const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); + const item: [*]const u8 = @ptrCast(@alignCast(item_ptr)); + + const maybe_timeout: ?time.Duration = if (block_time_tick == c.OSI_FUNCS_TIME_BLOCKING) + .from_us(block_time_tick) + else + null; + + return @intCast(@divExact(queue.inner.put_with_timeout( + scheduler, + item[0..queue.item_len], + queue.item_len, + maybe_timeout, + ) catch |err| switch (err) { + error.Closed => unreachable, + error.Timeout => return -1, + }, queue.item_len)); } pub fn queue_send_from_isr(ptr: ?*anyopaque, item_ptr: ?*anyopaque, _hptw: ?*anyopaque) callconv(.c) i32 { log.debug("queue_send_from_isr {?} {?} {?}", .{ ptr, item_ptr, _hptw }); - @as(*u32, @ptrCast(@alignCast(_hptw))).* = 1; - return queue_send_common(ptr, item_ptr); + const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); + const item: [*]const u8 = @ptrCast(@alignCast(item_ptr)); + const n = @divExact(queue.inner.put_from_isr( + scheduler, + item[0..queue.item_len], + ) catch unreachable, queue.item_len); + + @as(*u32, @ptrCast(@alignCast(_hptw))).* = @intFromBool(scheduler.is_a_higher_priority_task_ready()); + + return @intCast(n); } pub fn queue_send_to_back() callconv(.c) void { @@ -508,39 +481,30 @@ pub fn queue_send_to_front() callconv(.c) void { pub fn queue_recv(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) callconv(.c) i32 { log.debug("queue_recv {?} {?} {}", .{ ptr, item_ptr, block_time_tick }); - const forever = block_time_tick == c.OSI_FUNCS_TIME_BLOCKING; - const timeout = block_time_tick; - const start = hal.time.get_time_since_boot(); - - const queue: *Queue = @ptrCast(@alignCast(ptr)); + const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); const item: [*]u8 = @ptrCast(@alignCast(item_ptr)); - while (true) { - { - const cs = enter_critical_section(); - defer cs.leave(); - - if (queue.dequeue(item)) |_| { - log.debug(">>>> return from queue recv: {*}", .{queue}); - - return 1; - } else |_| {} - } + const maybe_timeout: ?time.Duration = if (block_time_tick == c.OSI_FUNCS_TIME_BLOCKING) + .from_us(block_time_tick) + else + null; - if (!forever and hal.time.get_time_since_boot().diff(start).to_us() > timeout) { - log.warn(">>>> return from queue recv from timeout: {*}", .{queue}); - return -1; - } - - scheduler.yield(.reschedule); - } + return @intCast(@divExact(queue.inner.get_with_timeout( + scheduler, + item[0..queue.item_len], + queue.item_len, + maybe_timeout, + ) catch |err| switch (err) { + error.Closed => unreachable, + error.Timeout => return -1, + }, queue.item_len)); } pub fn queue_msg_waiting(ptr: ?*anyopaque) callconv(.c) u32 { log.debug("queue_msg_waiting {?}", .{ptr}); - const queue: *Queue = @ptrCast(@alignCast(ptr)); - return queue.len; + const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); + return @divExact(queue.inner.len, queue.item_len); } pub fn event_group_create() callconv(.c) void { @@ -641,11 +605,11 @@ pub fn task_delete() callconv(.c) void { pub fn task_delay(tick: u32) callconv(.c) void { log.debug("task_delay {}", .{tick}); - scheduler.sleep(tick); + scheduler.sleep(.from_us(tick)); } pub fn task_ms_to_tick(ms: u32) callconv(.c) i32 { - return @intCast(ms * 1_000 * systimer.ticks_per_us()); + return @intCast(ms * 1_000); } pub fn task_get_current_task() callconv(.c) ?*anyopaque { @@ -946,7 +910,7 @@ pub fn timer_arm_us(ets_timer_ptr: ?*anyopaque, us: u32, repeat: bool) callconv( if (timer.find(ets_timer)) |tim| { const period: time.Duration = .from_us(us); - tim.deadline = .init_relative(hal.time.get_time_since_boot(), period); + tim.deadline = .init_relative(get_time_since_boot(), period); tim.periodic = if (repeat) period else null; } else { log.warn("timer not found based on ets_timer", .{}); @@ -983,7 +947,7 @@ pub fn wifi_rtc_disable_iso() callconv(.c) void { pub fn esp_timer_get_time() callconv(.c) i64 { log.debug("esp_timer_get_time", .{}); - return @intCast(scheduler.get_ticks()); + return @intCast(get_time_since_boot().to_us()); } pub fn nvs_set_i8() callconv(.c) void { @@ -1061,7 +1025,7 @@ pub fn log_writev(_: c_uint, _: [*c]const u8, fmt: [*c]const u8, va_list: c.va_l } pub fn log_timestamp() callconv(.c) u32 { - return @truncate(hal.time.get_time_since_boot().to_us() * 1_000); + return @truncate(get_time_since_boot().to_us() * 1_000); } pub const malloc_internal = malloc; @@ -1090,18 +1054,26 @@ pub fn wifi_realloc() callconv(.c) void { pub const wifi_calloc = calloc; pub const wifi_zalloc = zalloc_internal; -var wifi_queue_handle: ?*Queue = null; +var wifi_queue_handle: ?*QueueWrapper = null; +var wifi_queue: QueueWrapper = undefined; pub fn wifi_create_queue(capacity: c_int, item_len: c_int) callconv(.c) ?*anyopaque { log.debug("wifi_create_queue {} {}", .{ capacity, item_len }); std.debug.assert(wifi_queue_handle == null); - wifi_queue_handle = Queue.create(@intCast(capacity), @intCast(item_len)) catch { - log.warn("failed to allocate wifi queue", .{}); + + const buf: []u8 = allocator.alloc(u8, @intCast(capacity)) catch { + log.warn("failed to allocate queue buffer", .{}); return null; }; - log.debug(">>>> wifi queue create: {*}", .{wifi_queue_handle}); + wifi_queue = .{ + .inner = .init(buf), + .item_len = @intCast(item_len), + }; + wifi_queue_handle = &wifi_queue; + + log.debug(">>>> wifi queue create: {*}", .{&wifi_queue_handle}); return @ptrCast(&wifi_queue_handle); } @@ -1109,12 +1081,13 @@ pub fn wifi_create_queue(capacity: c_int, item_len: c_int) callconv(.c) ?*anyopa pub fn wifi_delete_queue(ptr: ?*anyopaque) callconv(.c) void { log.debug("wifi_delete_queue {?}", .{ptr}); - std.debug.assert(ptr == @as(?*anyopaque, @ptrCast(wifi_queue_handle))); + std.debug.assert(ptr == @as(?*anyopaque, @ptrCast(&wifi_queue_handle))); - const queue: *Queue = @ptrCast(@alignCast(ptr)); - queue.destroy(); + const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); + allocator.free(queue.inner.buffer); wifi_queue_handle = null; + wifi_queue = undefined; } pub fn coex_init() callconv(.c) c_int { From 2f959085fb82a756f923f79be20d72252d53b111 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Mon, 29 Dec 2025 10:50:08 +0200 Subject: [PATCH 15/43] Fix interrupt nesting --- port/espressif/esp/src/cpus/esp_riscv.zig | 31 ++++++++++++++++------- port/espressif/esp/src/hal/Scheduler.zig | 15 +++++------ port/espressif/esp/src/hal/radio.zig | 2 +- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index a38fb347d..d3769558e 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -537,26 +537,39 @@ fn _handle_interrupt( trap_frame: *TrapFrame, handler: *const fn (*TrapFrame) callconv(.c) void, ) linksection(".ram_text") callconv(.c) void { - const prev_priority = interrupt.get_priority_threshold(); - const mcause = csr.mcause.read(); + if (mcause.is_interrupt != 0) { - // this is an interrupt (can also be exception in which case we don't enable interrupts). + // interrupt const int: Interrupt = @enumFromInt(mcause.code); const priority = interrupt.get_priority(int); + // low priority interrupts can be preempted by higher priority interrupts if (@intFromEnum(priority) < 15) { - // allow higher priority interrupts to preempt this one + const mepc = csr.mepc.read_raw(); + const mstatus = csr.mstatus.read_raw(); + + const prev_thresh = interrupt.get_priority_threshold(); interrupt.set_priority_threshold(@enumFromInt(@intFromEnum(priority) + 1)); + interrupt.enable_interrupts(); - } - } - handler(trap_frame); + handler(trap_frame); - interrupt.disable_interrupts(); - interrupt.set_priority_threshold(prev_priority); + interrupt.disable_interrupts(); + + interrupt.set_priority_threshold(prev_thresh); + + csr.mepc.write_raw(mepc); + csr.mstatus.write_raw(mstatus); + } else { + handler(trap_frame); + } + } else { + // exception + handler(trap_frame); + } } pub const csr = struct { diff --git a/port/espressif/esp/src/hal/Scheduler.zig b/port/espressif/esp/src/hal/Scheduler.zig index 9639d5416..ec2ba41db 100644 --- a/port/espressif/esp/src/hal/Scheduler.zig +++ b/port/espressif/esp/src/hal/Scheduler.zig @@ -26,6 +26,11 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; // TODO: for other esp32 chips support SMP // TODO: use @stackUpperBound when implemented +comptime { + if (microzig.options.cpu.interrupt_stack_size == null) + @compileError("Please enable interrupt stacks to use the scheduler"); +} + const STACK_ALIGN: std.mem.Alignment = .@"16"; const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 31 * @sizeOf(usize)); const IDLE_STACK_SIZE = 512 + EXTRA_STACK_SIZE; @@ -69,11 +74,6 @@ pub const Priority = enum(u8) { }; pub fn init(scheduler: *Scheduler, gpa: Allocator) void { - comptime { - if (microzig.options.cpu.interrupt_stack_size == null) - @compileError("Please enable interrupt stacks to use the scheduler"); - } - assert(maybe_instance == null); scheduler.* = .{ @@ -509,15 +509,12 @@ pub fn generic_interrupt_handler(_: *TrapFrame) callconv(.c) void { .systimer_target0 => { systimer_alarm.clear_interrupt(); - const cs = enter_critical_section(); - defer cs.leave(); - while (scheduler.timer_queue.first) |node| { const task: *Task = @alignCast(@fieldParentPtr("node", node)); if (!task.state.alarm_set.is_reached()) { break; } - scheduler.make_task_ready_from_cs(task, cs); + scheduler.make_task_ready(task); } if (scheduler.timer_queue.first) |node| { diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index 35b6ba1f2..f47fdff75 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -142,7 +142,7 @@ fn setup_interrupts() void { microzig.cpu.interrupt.map(.wifi_pwr, options.wifi_interrupt); microzig.cpu.interrupt.set_type(options.wifi_interrupt, .level); - microzig.cpu.interrupt.set_priority(options.wifi_interrupt, .lowest); + microzig.cpu.interrupt.set_priority(options.wifi_interrupt, .highest); } pub const interrupt_handlers = struct { From 3af9b399b779487a6f1e4adb633486b809c29348 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Mon, 29 Dec 2025 14:50:32 +0200 Subject: [PATCH 16/43] Fix microzig.interrupt.Mutex --- core/src/allocator.zig | 32 ++++++++++++++++-------- core/src/interrupt.zig | 5 ++-- examples/espressif/esp/src/wifi.zig | 29 +++------------------ port/espressif/esp/src/hal/Scheduler.zig | 14 ++++++----- 4 files changed, 35 insertions(+), 45 deletions(-) diff --git a/core/src/allocator.zig b/core/src/allocator.zig index 91e9e66bc..3267deb8c 100644 --- a/core/src/allocator.zig +++ b/core/src/allocator.zig @@ -25,7 +25,7 @@ high_boundary: usize, fallback: ?*Alloc = null, /// A mutex used to protect access to the allocator. -mutex: microzig.interrupt.Mutex = .{}, +// mutex: microzig.interrupt.Mutex = .{}, /// Return a []u8 slice that contains the memory located between the /// microzig_heap_start and microzig_heap_end. This is the RAM that @@ -114,8 +114,10 @@ pub fn allocator(self: *Alloc) std.mem.Allocator { /// Returns the total amount of free memory in the heap. pub fn free_heap(self: *Alloc) usize { - self.mutex.lock(); - defer self.mutex.unlock(); + // self.mutex.lock(); + // defer self.mutex.unlock(); + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); var total: usize = 0; for (0..free_list_count) |i| { @@ -130,8 +132,10 @@ pub fn free_heap(self: *Alloc) usize { /// Returns the largest length that can be currently allocated. pub fn max_alloc_size(self: *Alloc) usize { - self.mutex.lock(); - defer self.mutex.unlock(); + // self.mutex.lock(); + // defer self.mutex.unlock(); + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); var maximum: usize = 0; for (0..free_list_count) |i| { @@ -164,8 +168,10 @@ const vtable: std.mem.Allocator.VTable = fn do_alloc(ptr: *anyopaque, len: usize, alignment: Alignment, pc: usize) ?[*]u8 { const self: *Alloc = @ptrCast(@alignCast(ptr)); - self.mutex.lock(); - defer self.mutex.unlock(); + // self.mutex.lock(); + // defer self.mutex.unlock(); + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); const needed = @max(len, @sizeOf(Chunk) - Chunk.header_size); @@ -268,8 +274,10 @@ fn do_alloc(ptr: *anyopaque, len: usize, alignment: Alignment, pc: usize) ?[*]u8 fn do_resize(ptr: *anyopaque, memory: []u8, _: Alignment, new_len: usize, _: usize) bool { const self: *Alloc = @ptrCast(@alignCast(ptr)); - self.mutex.lock(); - defer self.mutex.unlock(); + // self.mutex.lock(); + // defer self.mutex.unlock(); + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); var chunk = Chunk.from_data(memory, self); @@ -333,8 +341,10 @@ fn do_free(ptr: *anyopaque, memory: []u8, alignment: Alignment, pc: usize) void @panic("free - address is not in range"); } - self.mutex.lock(); - defer self.mutex.unlock(); + // self.mutex.lock(); + // defer self.mutex.unlock(); + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); Chunk.from_data(memory, self).combine_and_free(self); } diff --git a/core/src/interrupt.zig b/core/src/interrupt.zig index 09fffacd8..acec6de10 100644 --- a/core/src/interrupt.zig +++ b/core/src/interrupt.zig @@ -122,9 +122,10 @@ pub const CriticalSectionMutex = struct { /// Unlocks the mutex. pub fn unlock(self: *CriticalSectionMutex) void { - if (self.critical_section) |cs| { + const maybe_cs = self.critical_section; + self.critical_section = null; + if (maybe_cs) |cs| { cs.leave(); - self.critical_section = null; } } }; diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index ffe09d242..641ea3551 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -56,7 +56,10 @@ pub fn main() !void { scheduler.init(allocator); try radio.init(allocator, &scheduler); + defer radio.deinit(); + try radio.wifi.init(); + defer radio.wifi.deinit(); c.lwip_init(); @@ -122,32 +125,6 @@ pub fn main() !void { } scheduler.sleep(.from_ms(10)); } - - // var ssid: [1:0]u8 = @splat(0); - // var bssid: [1:0]u8 = @splat(0); - // - // var scan_config: c.wifi_scan_config_t = .{ - // .ssid = &ssid, - // .bssid = &bssid, - // .channel = 0, - // .show_hidden = true, - // .scan_type = c.WIFI_SCAN_TYPE_PASSIVE, - // .scan_time = .{ - // .active = .{ .min = 0, .max = 0 }, - // .passive = 2000, - // }, - // .home_chan_dwell_time = 0, - // .channel_bitmap = .{ - // .ghz_2_channels = 0, - // .ghz_5_channels = 0, - // }, - // }; - // try radio.wifi.c_result(c.esp_wifi_scan_start(&scan_config, true)); - // - // var no: u16 = undefined; - // try radio.wifi.c_result(c.esp_wifi_scan_get_ap_num(&no)); - - // std.log.info("found {} aps", .{no}); } fn netif_init(netif_c: [*c]c.struct_netif) callconv(.c) c.err_t { diff --git a/port/espressif/esp/src/hal/Scheduler.zig b/port/espressif/esp/src/hal/Scheduler.zig index ec2ba41db..da30d5a51 100644 --- a/port/espressif/esp/src/hal/Scheduler.zig +++ b/port/espressif/esp/src/hal/Scheduler.zig @@ -104,19 +104,21 @@ pub fn init(scheduler: *Scheduler, gpa: Allocator) void { microzig.cpu.interrupt.set_priority(yield_interrupt, .lowest); microzig.cpu.interrupt.enable(yield_interrupt); - microzig.cpu.interrupt.set_type(generic_interrupt, .level); - microzig.cpu.interrupt.set_priority(generic_interrupt, .lowest); - microzig.cpu.interrupt.map(.systimer_target0, generic_interrupt); - microzig.cpu.interrupt.enable(generic_interrupt); - // unit0 is already enabled as it is used by `hal.time`. systimer_alarm.set_unit(systimer_unit); systimer_alarm.set_mode(.target); systimer_alarm.set_enabled(false); systimer_alarm.set_interrupt_enabled(true); + + microzig.cpu.interrupt.map(.systimer_target0, generic_interrupt); + microzig.cpu.interrupt.set_type(generic_interrupt, .level); + microzig.cpu.interrupt.set_priority(generic_interrupt, .lowest); + microzig.cpu.interrupt.enable(generic_interrupt); } -pub fn idle() callconv(.c) void { +// TODO: deinit + +fn idle() callconv(.c) void { const scheduler = maybe_instance orelse @panic("no active scheduler"); while (true) { scheduler.yield(.reschedule); From 48efd95dbe18b51358fb28fc74bfd03dbb6eebc7 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Wed, 31 Dec 2025 11:50:35 +0200 Subject: [PATCH 17/43] Fix alignment issues and to small idle stack size --- core/src/allocator.zig | 32 +-- examples/espressif/esp/src/scheduler.zig | 15 +- port/espressif/esp/src/cpus/esp_riscv.zig | 4 +- port/espressif/esp/src/hal/Scheduler.zig | 241 +++++++++++++--------- port/espressif/esp/src/hal/radio/osi.zig | 2 +- 5 files changed, 171 insertions(+), 123 deletions(-) diff --git a/core/src/allocator.zig b/core/src/allocator.zig index 3267deb8c..91e9e66bc 100644 --- a/core/src/allocator.zig +++ b/core/src/allocator.zig @@ -25,7 +25,7 @@ high_boundary: usize, fallback: ?*Alloc = null, /// A mutex used to protect access to the allocator. -// mutex: microzig.interrupt.Mutex = .{}, +mutex: microzig.interrupt.Mutex = .{}, /// Return a []u8 slice that contains the memory located between the /// microzig_heap_start and microzig_heap_end. This is the RAM that @@ -114,10 +114,8 @@ pub fn allocator(self: *Alloc) std.mem.Allocator { /// Returns the total amount of free memory in the heap. pub fn free_heap(self: *Alloc) usize { - // self.mutex.lock(); - // defer self.mutex.unlock(); - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); + self.mutex.lock(); + defer self.mutex.unlock(); var total: usize = 0; for (0..free_list_count) |i| { @@ -132,10 +130,8 @@ pub fn free_heap(self: *Alloc) usize { /// Returns the largest length that can be currently allocated. pub fn max_alloc_size(self: *Alloc) usize { - // self.mutex.lock(); - // defer self.mutex.unlock(); - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); + self.mutex.lock(); + defer self.mutex.unlock(); var maximum: usize = 0; for (0..free_list_count) |i| { @@ -168,10 +164,8 @@ const vtable: std.mem.Allocator.VTable = fn do_alloc(ptr: *anyopaque, len: usize, alignment: Alignment, pc: usize) ?[*]u8 { const self: *Alloc = @ptrCast(@alignCast(ptr)); - // self.mutex.lock(); - // defer self.mutex.unlock(); - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); + self.mutex.lock(); + defer self.mutex.unlock(); const needed = @max(len, @sizeOf(Chunk) - Chunk.header_size); @@ -274,10 +268,8 @@ fn do_alloc(ptr: *anyopaque, len: usize, alignment: Alignment, pc: usize) ?[*]u8 fn do_resize(ptr: *anyopaque, memory: []u8, _: Alignment, new_len: usize, _: usize) bool { const self: *Alloc = @ptrCast(@alignCast(ptr)); - // self.mutex.lock(); - // defer self.mutex.unlock(); - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); + self.mutex.lock(); + defer self.mutex.unlock(); var chunk = Chunk.from_data(memory, self); @@ -341,10 +333,8 @@ fn do_free(ptr: *anyopaque, memory: []u8, alignment: Alignment, pc: usize) void @panic("free - address is not in range"); } - // self.mutex.lock(); - // defer self.mutex.unlock(); - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); + self.mutex.lock(); + defer self.mutex.unlock(); Chunk.from_data(memory, self).combine_and_free(self); } diff --git a/examples/espressif/esp/src/scheduler.zig b/examples/espressif/esp/src/scheduler.zig index 329636121..bade995cb 100644 --- a/examples/espressif/esp/src/scheduler.zig +++ b/examples/espressif/esp/src/scheduler.zig @@ -20,18 +20,16 @@ pub const microzig_options: microzig.Options = .{ }; var heap_buf: [10 * 1024]u8 = undefined; -var scheduler: esp.Scheduler = undefined; -var queue: esp.Scheduler.Queue(u32) = .init(&.{}); -fn task1(_: ?*anyopaque) callconv(.c) noreturn { +fn task1(scheduler: *esp.Scheduler, q: *esp.Scheduler.Queue(u32)) noreturn { for (0..5) |i| { - queue.put_one(&scheduler, i) catch { + q.put_one(scheduler, i) catch { std.log.err("failed to put item", .{}); continue; }; scheduler.sleep(.from_ms(500)); } - queue.close(&scheduler); + q.close(scheduler); while (true) { microzig.cpu.wfi(); } @@ -40,13 +38,14 @@ fn task1(_: ?*anyopaque) callconv(.c) noreturn { pub fn main() !void { var heap = microzig.Allocator.init_with_buffer(&heap_buf); const allocator = heap.allocator(); + + var scheduler: esp.Scheduler = undefined; scheduler.init(allocator); esp.time.sleep_ms(1000); - _ = try scheduler.raw_alloc_spawn_with_options(task1, null, .{ - // .priority = .high, - }); + var queue: esp.Scheduler.Queue(u32) = .init(&.{}); + _ = try scheduler.spawn(task1, .{&scheduler, &queue}, .{}); while (true) { const item = try queue.get_one(&scheduler); diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index d3769558e..bdb1aaa26 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -243,6 +243,8 @@ pub const interrupt = struct { return @ptrFromInt(base + @sizeOf(u32) * @as(usize, @intFromEnum(source))); } + // TODO: implement some sort of masking so as to only check sources you + // care about pub const SourceIterator = struct { index: usize, status_reg: u32, @@ -395,7 +397,7 @@ pub const TrapFrame = extern struct { }; const interrupt_stack_size = microzig.options.cpu.interrupt_stack_size; -pub var interrupt_stack: [interrupt_stack_size orelse 0]u8 align(16) = undefined; +pub var interrupt_stack: [std.mem.alignForward(usize, interrupt_stack_size orelse 0, 16)]u8 align(16) = undefined; fn _vector_table() align(256) linksection(".ram_vectors") callconv(.naked) void { const interrupt_jump_asm, const interrupt_c_stubs_asm = comptime blk: { diff --git a/port/espressif/esp/src/hal/Scheduler.zig b/port/espressif/esp/src/hal/Scheduler.zig index da30d5a51..4ddb2472f 100644 --- a/port/espressif/esp/src/hal/Scheduler.zig +++ b/port/espressif/esp/src/hal/Scheduler.zig @@ -23,7 +23,9 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; // TODO: add identifier to tasks // TODO: stack usage report based on stack painting -// TODO: for other esp32 chips support SMP +// TODO: for other esp32 chips with multicore support SMP +// TODO: make a cancelation system +// TODO: implement std.Io // TODO: use @stackUpperBound when implemented comptime { @@ -33,7 +35,6 @@ comptime { const STACK_ALIGN: std.mem.Alignment = .@"16"; const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 31 * @sizeOf(usize)); -const IDLE_STACK_SIZE = 512 + EXTRA_STACK_SIZE; // TODO: configurable const generic_interrupt: microzig.cpu.Interrupt = .interrupt30; @@ -51,10 +52,12 @@ gpa: Allocator, ready_queue: Task.ReadyPriorityQueue = .{}, timer_queue: std.DoublyLinkedList = .{}, suspended_list: std.DoublyLinkedList = .{}, +scheduled_for_deletion_list: std.DoublyLinkedList = .{}, main_task: Task, -idle_stack: [IDLE_STACK_SIZE]u8 = undefined, +// Idle only requires the stack space used by the yield interrupt +idle_stack: [std.mem.alignForward(usize, EXTRA_STACK_SIZE, STACK_ALIGN.toByteUnits())]u8 align(STACK_ALIGN.toByteUnits()) = undefined, idle_task: Task, /// The task in .running state @@ -95,7 +98,7 @@ pub fn init(scheduler: *Scheduler, gpa: Allocator) void { .current_task = &scheduler.main_task, }; - scheduler.make_task_ready(&scheduler.idle_task); + scheduler.make_task_ready(&scheduler.idle_task, .{}); maybe_instance = scheduler; @@ -118,12 +121,12 @@ pub fn init(scheduler: *Scheduler, gpa: Allocator) void { // TODO: deinit -fn idle() callconv(.c) void { - const scheduler = maybe_instance orelse @panic("no active scheduler"); - while (true) { - scheduler.yield(.reschedule); - microzig.cpu.wfi(); - } +fn idle() callconv(.naked) void { + asm volatile ( + \\1: + \\wfi + \\j 1b + ); } fn task_entry() callconv(.naked) void { @@ -135,13 +138,38 @@ fn task_entry() callconv(.naked) void { ); } +pub fn spawn( + scheduler: *Scheduler, + function: anytype, + args: std.meta.ArgsTuple(@TypeOf(function)), + options: SpawnOptions, +) !*Task { + if (@typeInfo(@TypeOf(function)).@"fn".return_type.? != noreturn) + @compileError("tasks must not return"); + + const Args = @TypeOf(args); + const TypeErased = struct { + fn start(context: ?*anyopaque) callconv(.c) noreturn { + const args_casted: *const Args = @ptrCast(@alignCast(context)); + + @call(.auto, function, args_casted.*); + + const sched = maybe_instance orelse @panic("no active scheduler"); + sched.yield(.delete); + } + }; + + // SAFETY: @constCast is safe to use since the task entry only dereferences the ptr + return scheduler.raw_spawn_with_options(TypeErased.start, @ptrCast(@constCast(&args)), options); +} + pub const SpawnOptions = struct { stack_size: usize = 4096, // TODO: should we ban idle priority? priority: Priority = .lowest, }; -pub fn raw_alloc_spawn_with_options( +pub fn raw_spawn_with_options( scheduler: *Scheduler, function: *const fn (param: ?*anyopaque) callconv(.c) noreturn, param: ?*anyopaque, @@ -171,49 +199,63 @@ pub fn raw_alloc_spawn_with_options( .priority = options.priority, }; - scheduler.make_task_ready(task); + scheduler.make_task_ready(task, .{}); return task; } -pub fn make_task_ready(scheduler: *Scheduler, task: *Task) void { +pub fn make_task_ready( + scheduler: *Scheduler, + task: *Task, + ready_flags: Task.ReadyFlags, +) void { const cs = enter_critical_section(); defer cs.leave(); - scheduler.make_task_ready_from_cs(task, cs); + scheduler.make_task_ready_from_cs(task, ready_flags, cs); } -pub fn make_task_ready_from_cs(scheduler: *Scheduler, task: *Task, _: CriticalSection) void { +pub fn make_task_ready_from_cs( + scheduler: *Scheduler, + task: *Task, + ready_flags: Task.ReadyFlags, + _: CriticalSection, +) void { switch (task.state) { - .running, .none => {}, - .ready => return, + .none => {}, + .ready => { + task.state = .{ .ready = ready_flags }; + return; + }, .alarm_set => |_| { scheduler.timer_queue.remove(&task.node); }, .suspended => { scheduler.suspended_list.remove(&task.node); }, + inline else => |_, tag| @panic(std.fmt.comptimePrint("{t} -> ready transition invalid", .{tag})), } - task.state = .ready; + task.state = .{ .ready = ready_flags }; scheduler.ready_queue.append(task); } -pub fn change_task_priority_from_cs(scheduler: *Scheduler, task: *Task, new_priority: Priority, _: CriticalSection) void { - task.priority = new_priority; - - switch (task.state) { - .ready => { - scheduler.ready_queue.inner.remove(task); - scheduler.ready_queue.inner.append(task); - }, - else => {}, - } -} +// pub fn change_task_priority_from_cs(scheduler: *Scheduler, task: *Task, new_priority: Priority, _: CriticalSection) void { +// task.priority = new_priority; +// +// switch (task.state) { +// .ready => { +// scheduler.ready_queue.inner.remove(task); +// scheduler.ready_queue.inner.append(task); +// }, +// else => {}, +// } +// } pub const YieldAction = union(enum) { reschedule, wait: ?TimerTicks, + delete, }; pub inline fn yield(scheduler: *Scheduler, action: YieldAction) void { @@ -224,40 +266,57 @@ pub inline fn yield(scheduler: *Scheduler, action: YieldAction) void { /// Must be called inside critical section. Calling leave on the critical /// section becomes unnecessary. pub inline fn yield_and_leave_cs(scheduler: *Scheduler, action: YieldAction, cs: CriticalSection) void { - const prev_context, const next_context = scheduler.yield_inner(action); - context_switch(prev_context, next_context); - if (!cs.enable_on_leave) { + defer if (!cs.enable_on_leave) { microzig.cpu.interrupt.disable_interrupts(); - } + }; + const current_task, const next_task = scheduler.yield_inner(action); + context_switch(¤t_task.context, &next_task.context); } -fn yield_inner(scheduler: *Scheduler, action: YieldAction) struct { *Context, *Context } { - const prev_task = scheduler.current_task; +fn yield_inner(scheduler: *Scheduler, action: YieldAction) struct { *Task, *Task } { + const current_task = scheduler.current_task; switch (action) { .reschedule => { - scheduler.ready_queue.append(prev_task); - prev_task.state = .ready; + current_task.state = .{ .ready = .{} }; + scheduler.ready_queue.append(current_task); }, .wait => |maybe_timeout| { + assert(current_task != &scheduler.idle_task); + if (maybe_timeout) |timeout| { - scheduler.schedule_wake_at(prev_task, timeout); + scheduler.schedule_wake_at(current_task, timeout); } else { - prev_task.state = .suspended; - scheduler.suspended_list.append(&prev_task.node); + current_task.state = .suspended; + scheduler.suspended_list.append(¤t_task.node); } }, + .delete => { + assert(current_task != &scheduler.idle_task and current_task != &scheduler.main_task); + + current_task.state = .scheduled_for_deletion; + scheduler.scheduled_for_deletion_list.append(¤t_task.node); + }, } - const next_task: *Task = scheduler.ready_queue.pop(null) orelse @panic("Idle task can't be waiting."); + const next_task: *Task = scheduler.ready_queue.pop(null) orelse @panic("No task ready to run!"); + const ready_flags = next_task.state.ready; + next_task.state = .{ .running = ready_flags }; scheduler.current_task = next_task; - next_task.state = .running; - return .{ &prev_task.context, &next_task.context }; + return .{ current_task, next_task }; } +pub const TimeoutError = error{Timeout}; + +// pub fn check_timeout_from_cs(scheduler: *Scheduler, _: CriticalSection) TimeoutError!void { +// if (scheduler.current_task.state.running.timeout) +// return error.Timeout; +// } + pub fn sleep(scheduler: *Scheduler, duration: time.Duration) void { scheduler.yield(.{ .wait = .after(duration) }); + assert(scheduler.current_task.state.running.timeout); } inline fn context_switch(prev_context: *Context, next_context: *Context) void { @@ -381,7 +440,7 @@ pub fn isr_yield_handler() linksection(".ram_vectors") callconv(.naked) void { \\la sp, %[interrupt_stack_top] \\ // allocate `Context` struct and save context - \\addi sp, sp, -3*4 + \\addi sp, sp, -16 \\la a1, 1f \\sw a1, 0(sp) \\sw a2, 4(sp) @@ -465,18 +524,25 @@ fn schedule_in_isr(context: *Context) linksection(".ram_vectors") callconv(.c) v .CPU_INTR_FROM_CPU_0 = 0, }); - const prev_task = scheduler.current_task; + const current_task = scheduler.current_task; const ready_task = scheduler.ready_queue.pop(scheduler.current_task.priority) orelse return; // swap contexts - prev_task.context = context.*; + current_task.context = context.*; context.* = ready_task.context; - scheduler.ready_queue.append(prev_task); - prev_task.state = .ready; + // keep the state until the next yield + { + const ready_flags = current_task.state.running; + current_task.state = .{ .ready = ready_flags }; + } + scheduler.ready_queue.append(current_task); + { + const ready_flags = ready_task.state.ready; + ready_task.state = .{ .running = ready_flags }; + } scheduler.current_task = ready_task; - ready_task.state = .running; } /// Must be called from a critical section. @@ -509,6 +575,9 @@ pub fn generic_interrupt_handler(_: *TrapFrame) callconv(.c) void { while (iter.next()) |source| { switch (source) { .systimer_target0 => { + const cs = enter_critical_section(); + defer cs.leave(); + systimer_alarm.clear_interrupt(); while (scheduler.timer_queue.first) |node| { @@ -516,7 +585,7 @@ pub fn generic_interrupt_handler(_: *TrapFrame) callconv(.c) void { if (!task.state.alarm_set.is_reached()) { break; } - scheduler.make_task_ready(task); + scheduler.make_task_ready_from_cs(task, .{ .timeout = true }, cs); } if (scheduler.timer_queue.first) |node| { @@ -531,8 +600,9 @@ pub fn generic_interrupt_handler(_: *TrapFrame) callconv(.c) void { } } - if (scheduler.is_a_higher_priority_task_ready()) + if (scheduler.is_a_higher_priority_task_ready()) { yield_from_isr(); + } } pub const Task = struct { @@ -551,10 +621,15 @@ pub const Task = struct { pub const State = union(enum) { none, - ready, - running, + ready: ReadyFlags, + running: ReadyFlags, alarm_set: TimerTicks, suspended, + scheduled_for_deletion, + }; + + pub const ReadyFlags = packed struct(u1) { + timeout: bool = false, }; // TODO: Maybe swap with something more efficient. @@ -639,8 +714,6 @@ pub const WaitingList = struct { } }; -pub const TimeoutError = error{Timeout}; - // TODO: implement priority inheritance pub const Mutex = struct { state: State = .unlocked, @@ -671,13 +744,9 @@ pub const Mutex = struct { defer mutex.awaiters.inner.remove(&awaiter.node); while (mutex.state != .unlocked) { - if (maybe_timeout_ticks) |timeout_ticks| { - if (timeout_ticks.is_reached()) { - return error.Timeout; - } - } - scheduler.yield(.{ .wait = maybe_timeout_ticks }); + if (scheduler.current_task.state.running.timeout) + return error.Timeout; } mutex.state = .locked; @@ -691,7 +760,7 @@ pub const Mutex = struct { mutex.state = .unlocked; if (mutex.awaiters.get_highest_priority()) |task| { - scheduler.make_task_ready_from_cs(task, cs); + scheduler.make_task_ready_from_cs(task, .{}, cs); } } }; @@ -738,7 +807,7 @@ pub const RecursiveMutex = struct { mutex.owning_task = null; if (mutex.awaiters.get_highest_priority()) |task| { - scheduler.make_task_ready_from_cs(task, cs); + scheduler.make_task_ready_from_cs(task, .{}, cs); } return true; @@ -779,13 +848,9 @@ pub const Semaphore = struct { defer sem.awaiters.inner.remove(&awaiter.node); while (sem.value <= 0) { - if (maybe_timeout_ticks) |timeout_ticks| { - if (timeout_ticks.is_reached()) { - return error.Timeout; - } - } - scheduler.yield(.{ .wait = maybe_timeout_ticks }); + if (scheduler.current_task.state.running.timeout) + return error.Timeout; } sem.value -= 1; @@ -798,7 +863,7 @@ pub const Semaphore = struct { sem.value += 1; if (sem.awaiters.get_highest_priority()) |task| { - scheduler.make_task_ready_from_cs(task, cs); + scheduler.make_task_ready_from_cs(task, .{}, cs); } } }; @@ -843,7 +908,7 @@ pub const TypeErasedQueue = struct { pub fn close(q: *TypeErasedQueue, scheduler: *Scheduler) void { const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); + defer scheduler.yield_and_leave_cs(.reschedule, cs); q.closed = true; @@ -851,7 +916,7 @@ pub const TypeErasedQueue = struct { var it = q.getters.first; while (it) |node| : (it = node.next) { const getter: *Get = @alignCast(@fieldParentPtr("node", node)); - scheduler.make_task_ready_from_cs(getter.task, cs); + scheduler.make_task_ready_from_cs(getter.task, .{}, cs); } } @@ -859,7 +924,7 @@ pub const TypeErasedQueue = struct { var it = q.putters.first; while (it) |node| : (it = node.next) { const putter: *Put = @alignCast(@fieldParentPtr("node", node)); - scheduler.make_task_ready_from_cs(putter.task, cs); + scheduler.make_task_ready_from_cs(putter.task, .{}, cs); } } } @@ -904,13 +969,9 @@ pub const TypeErasedQueue = struct { defer if (pending.needed > 0) q.putters.remove(&pending.node); while (pending.needed > 0 and !q.closed) { - if (maybe_timeout_ticks) |timeout_ticks| { - if (timeout_ticks.is_reached()) { - return error.Timeout; - } - } - scheduler.yield(.{ .wait = maybe_timeout_ticks }); + if (scheduler.current_task.state.running.timeout) + return error.Timeout; } if (pending.remaining.len == elements.len) { @@ -947,7 +1008,7 @@ pub const TypeErasedQueue = struct { getter.needed -|= copy_len; n += copy_len; if (getter.needed == 0) { - scheduler.make_task_ready_from_cs(getter.task, cs); + scheduler.make_task_ready_from_cs(getter.task, .{}, cs); } else { assert(n == elements.len); // we didn't have enough elements for the getter q.getters.prepend(getter_node); @@ -978,7 +1039,7 @@ pub const TypeErasedQueue = struct { } pub fn get(q: *TypeErasedQueue, scheduler: *Scheduler, buffer: []u8, min: usize) QueueClosedError!usize { - return q.put_with_timeout(scheduler, buffer, min, null) catch |err| switch (err) { + return q.get_with_timeout(scheduler, buffer, min, null) catch |err| switch (err) { error.Timeout => unreachable, error.Closed => return error.Closed, }; @@ -1013,7 +1074,7 @@ pub const TypeErasedQueue = struct { q.len -= copy_len; n += copy_len; if (n == buffer.len) { - q.fill_ring_buffer_from_putters(scheduler); + q.fill_ring_buffer_from_putters(scheduler, cs); return buffer.len; } } @@ -1028,13 +1089,13 @@ pub const TypeErasedQueue = struct { putter.needed -|= copy_len; n += copy_len; if (putter.needed == 0) { - scheduler.make_task_ready_from_cs(putter.task, cs); + scheduler.make_task_ready_from_cs(putter.task, .{}, cs); } else { assert(n == buffer.len); // we didn't have enough space for the putter q.putters.prepend(putter_node); } if (n == buffer.len) { - q.fill_ring_buffer_from_putters(scheduler); + q.fill_ring_buffer_from_putters(scheduler, cs); return buffer.len; } } @@ -1063,13 +1124,9 @@ pub const TypeErasedQueue = struct { defer if (pending.needed > 0) q.getters.remove(&pending.node); while (pending.needed > 0 and !q.closed) { - if (maybe_timeout_ticks) |timeout_ticks| { - if (timeout_ticks.is_reached()) { - return error.Timeout; - } - } - scheduler.yield(.{ .wait = maybe_timeout_ticks }); + if (scheduler.current_task.state.running.timeout) + return error.Timeout; } if (pending.remaining.len == buffer.len) { @@ -1091,7 +1148,7 @@ pub const TypeErasedQueue = struct { /// potentially putters waiting. The mutex is already held and the task is /// to copy putter data to the ring buffer and signal any putters whose /// buffers been fully copied. - fn fill_ring_buffer_from_putters(q: *TypeErasedQueue, scheduler: *Scheduler) void { + fn fill_ring_buffer_from_putters(q: *TypeErasedQueue, scheduler: *Scheduler, cs: CriticalSection) void { while (q.putters.popFirst()) |putter_node| { const putter: *Put = @alignCast(@fieldParentPtr("node", putter_node)); while (q.puttable_slice()) |slice| { @@ -1102,7 +1159,7 @@ pub const TypeErasedQueue = struct { putter.remaining = putter.remaining[copy_len..]; putter.needed -|= copy_len; if (putter.needed == 0) { - scheduler.yield(.{ .wait = null }); + scheduler.make_task_ready_from_cs(putter.task, .{}, cs); break; } } else { diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 0d5572081..1224acfc8 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -539,7 +539,7 @@ fn task_create_common( _ = name; // autofix _ = core_id; // autofix - const task: *Scheduler.Task = scheduler.raw_alloc_spawn_with_options( + const task: *Scheduler.Task = scheduler.raw_spawn_with_options( @ptrCast(@alignCast(task_func)), param, .{ From 93aeb91918bf9130dc26111644453562c4b621e7 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Fri, 2 Jan 2026 15:50:35 +0200 Subject: [PATCH 18/43] More Scheduler work --- examples/espressif/esp/src/scheduler.zig | 20 +- examples/espressif/esp/src/wifi.zig | 1 - port/espressif/esp/src/hal/Scheduler.zig | 685 +++++++++------------- port/espressif/esp/src/hal/radio.zig | 18 +- port/espressif/esp/src/hal/radio/osi.zig | 95 +-- port/espressif/esp/src/hal/radio/wifi.zig | 2 +- 6 files changed, 345 insertions(+), 476 deletions(-) diff --git a/examples/espressif/esp/src/scheduler.zig b/examples/espressif/esp/src/scheduler.zig index bade995cb..14486f846 100644 --- a/examples/espressif/esp/src/scheduler.zig +++ b/examples/espressif/esp/src/scheduler.zig @@ -20,35 +20,35 @@ pub const microzig_options: microzig.Options = .{ }; var heap_buf: [10 * 1024]u8 = undefined; +var scheduler: esp.Scheduler = undefined; +var buffer: [1]u32 = undefined; +var queue: esp.Scheduler.Queue(u32) = .init(&buffer); -fn task1(scheduler: *esp.Scheduler, q: *esp.Scheduler.Queue(u32)) noreturn { +fn task1() void { for (0..5) |i| { - q.put_one(scheduler, i) catch { + queue.put_one(&scheduler, i) catch { std.log.err("failed to put item", .{}); continue; }; scheduler.sleep(.from_ms(500)); } - q.close(scheduler); - while (true) { - microzig.cpu.wfi(); - } + scheduler.yield(.delete); } pub fn main() !void { var heap = microzig.Allocator.init_with_buffer(&heap_buf); const allocator = heap.allocator(); - var scheduler: esp.Scheduler = undefined; scheduler.init(allocator); esp.time.sleep_ms(1000); - var queue: esp.Scheduler.Queue(u32) = .init(&.{}); - _ = try scheduler.spawn(task1, .{&scheduler, &queue}, .{}); + _ = try scheduler.spawn(task1, .{}, .{ + .stack_size = 8000, + }); while (true) { - const item = try queue.get_one(&scheduler); + const item = try queue.get_one(&scheduler, .from_ms(1000)); std.log.info("got item: {}", .{item}); } } diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index 641ea3551..7bf80305a 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -123,7 +123,6 @@ pub fn main() !void { std.log.info("used memory: {}K ({})", .{ used_mem / 1024, used_mem }); last_mem_show = now; } - scheduler.sleep(.from_ms(10)); } } diff --git a/port/espressif/esp/src/hal/Scheduler.zig b/port/espressif/esp/src/hal/Scheduler.zig index 4ddb2472f..81f02996c 100644 --- a/port/espressif/esp/src/hal/Scheduler.zig +++ b/port/espressif/esp/src/hal/Scheduler.zig @@ -21,12 +21,17 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; // and vice versa. Because of the forced yield, tasks are required to have a // minimum stack size available. -// TODO: add identifier to tasks +// TODO: add identifier names to tasks // TODO: stack usage report based on stack painting +// TODO: implement task garbage collection // TODO: for other esp32 chips with multicore support SMP // TODO: make a cancelation system // TODO: implement std.Io // TODO: use @stackUpperBound when implemented +// TODO: implement priority inheritance for respective sync primitives +// TODO: better handling if timeout is in the past or very short +// NOTE: don't use anonymous structs for reinitializing state unions with +// previous union state to avoid compiler bug (0.15.2) comptime { if (microzig.options.cpu.interrupt_stack_size == null) @@ -49,9 +54,8 @@ var maybe_instance: ?*Scheduler = null; gpa: Allocator, -ready_queue: Task.ReadyPriorityQueue = .{}, +ready_queue: ReadyPriorityQueue = .{}, timer_queue: std.DoublyLinkedList = .{}, -suspended_list: std.DoublyLinkedList = .{}, scheduled_for_deletion_list: std.DoublyLinkedList = .{}, main_task: Task, @@ -98,7 +102,7 @@ pub fn init(scheduler: *Scheduler, gpa: Allocator) void { .current_task = &scheduler.main_task, }; - scheduler.make_task_ready(&scheduler.idle_task, .{}); + scheduler.ready(&scheduler.idle_task, .{}); maybe_instance = scheduler; @@ -144,9 +148,6 @@ pub fn spawn( args: std.meta.ArgsTuple(@TypeOf(function)), options: SpawnOptions, ) !*Task { - if (@typeInfo(@TypeOf(function)).@"fn".return_type.? != noreturn) - @compileError("tasks must not return"); - const Args = @TypeOf(args); const TypeErased = struct { fn start(context: ?*anyopaque) callconv(.c) noreturn { @@ -156,6 +157,7 @@ pub fn spawn( const sched = maybe_instance orelse @panic("no active scheduler"); sched.yield(.delete); + unreachable; } }; @@ -199,62 +201,20 @@ pub fn raw_spawn_with_options( .priority = options.priority, }; - scheduler.make_task_ready(task, .{}); - - return task; -} - -pub fn make_task_ready( - scheduler: *Scheduler, - task: *Task, - ready_flags: Task.ReadyFlags, -) void { const cs = enter_critical_section(); defer cs.leave(); - scheduler.make_task_ready_from_cs(task, ready_flags, cs); -} - -pub fn make_task_ready_from_cs( - scheduler: *Scheduler, - task: *Task, - ready_flags: Task.ReadyFlags, - _: CriticalSection, -) void { - switch (task.state) { - .none => {}, - .ready => { - task.state = .{ .ready = ready_flags }; - return; - }, - .alarm_set => |_| { - scheduler.timer_queue.remove(&task.node); - }, - .suspended => { - scheduler.suspended_list.remove(&task.node); - }, - inline else => |_, tag| @panic(std.fmt.comptimePrint("{t} -> ready transition invalid", .{tag})), - } + scheduler.ready(task, .{}); - task.state = .{ .ready = ready_flags }; - scheduler.ready_queue.append(task); + return task; } -// pub fn change_task_priority_from_cs(scheduler: *Scheduler, task: *Task, new_priority: Priority, _: CriticalSection) void { -// task.priority = new_priority; -// -// switch (task.state) { -// .ready => { -// scheduler.ready_queue.inner.remove(task); -// scheduler.ready_queue.inner.append(task); -// }, -// else => {}, -// } -// } - pub const YieldAction = union(enum) { reschedule, - wait: ?TimerTicks, + wait: struct { + timeout: ?TimerTicks = null, + wait_queue: ?*PriorityWaitQueue = null, + }, delete, }; @@ -278,16 +238,21 @@ fn yield_inner(scheduler: *Scheduler, action: YieldAction) struct { *Task, *Task switch (action) { .reschedule => { current_task.state = .{ .ready = .{} }; - scheduler.ready_queue.append(current_task); + scheduler.ready_queue.put(current_task); }, - .wait => |maybe_timeout| { + .wait => |wait_action| { assert(current_task != &scheduler.idle_task); - if (maybe_timeout) |timeout| { + if (wait_action.wait_queue) |wait_queue| { + assert(current_task.wait_queue == null); + wait_queue.put(current_task); + current_task.wait_queue = wait_queue; + } + + if (wait_action.timeout) |timeout| { scheduler.schedule_wake_at(current_task, timeout); } else { current_task.state = .suspended; - scheduler.suspended_list.append(¤t_task.node); } }, .delete => { @@ -300,22 +265,41 @@ fn yield_inner(scheduler: *Scheduler, action: YieldAction) struct { *Task, *Task const next_task: *Task = scheduler.ready_queue.pop(null) orelse @panic("No task ready to run!"); - const ready_flags = next_task.state.ready; - next_task.state = .{ .running = ready_flags }; + next_task.state = Task.State{ .running = next_task.state.ready }; scheduler.current_task = next_task; return .{ current_task, next_task }; } -pub const TimeoutError = error{Timeout}; +pub fn wake_from_wait_queue(scheduler: *Scheduler, wait_queue: *PriorityWaitQueue, how_many: enum(usize) { + one = 1, + all = std.math.maxInt(usize), + _, +}) void { + var remaining: usize = @intFromEnum(how_many); + if (remaining == 0) return; + while (wait_queue.pop()) |task| : (remaining -= 1) { + if (remaining == 0) break; + + assert(task.wait_queue == wait_queue); + task.wait_queue = null; + + switch (task.state) { + .alarm_set => |_| scheduler.timer_queue.remove(&task.node), + .suspended => {}, + else => @panic("invalid state for waiting task"), + } -// pub fn check_timeout_from_cs(scheduler: *Scheduler, _: CriticalSection) TimeoutError!void { -// if (scheduler.current_task.state.running.timeout) -// return error.Timeout; -// } + scheduler.ready(task, .{}); + } +} + +pub const TimeoutError = error{Timeout}; pub fn sleep(scheduler: *Scheduler, duration: time.Duration) void { - scheduler.yield(.{ .wait = .after(duration) }); + scheduler.yield(.{ .wait = .{ + .timeout = .after(duration), + } }); assert(scheduler.current_task.state.running.timeout); } @@ -532,16 +516,10 @@ fn schedule_in_isr(context: *Context) linksection(".ram_vectors") callconv(.c) v context.* = ready_task.context; // keep the state until the next yield - { - const ready_flags = current_task.state.running; - current_task.state = .{ .ready = ready_flags }; - } - scheduler.ready_queue.append(current_task); + current_task.state = Task.State{ .ready = current_task.state.running }; + scheduler.ready_queue.put(current_task); - { - const ready_flags = ready_task.state.ready; - ready_task.state = .{ .running = ready_flags }; - } + ready_task.state = Task.State{ .running = ready_task.state.ready }; scheduler.current_task = ready_task; } @@ -565,6 +543,9 @@ fn schedule_wake_at(scheduler: *Scheduler, sleeping_task: *Task, ticks: TimerTic if (scheduler.timer_queue.first == &sleeping_task.node) { systimer_alarm.set_target(@intFromEnum(ticks)); systimer_alarm.set_enabled(true); + + if (ticks.is_reached()) + scheduler.sweep_timer_queue_for_timeouts(); } } @@ -580,21 +561,7 @@ pub fn generic_interrupt_handler(_: *TrapFrame) callconv(.c) void { systimer_alarm.clear_interrupt(); - while (scheduler.timer_queue.first) |node| { - const task: *Task = @alignCast(@fieldParentPtr("node", node)); - if (!task.state.alarm_set.is_reached()) { - break; - } - scheduler.make_task_ready_from_cs(task, .{ .timeout = true }, cs); - } - - if (scheduler.timer_queue.first) |node| { - const task: *Task = @alignCast(@fieldParentPtr("node", node)); - systimer_alarm.set_target(@intFromEnum(task.state.alarm_set)); - systimer_alarm.set_enabled(true); - } else { - systimer_alarm.set_enabled(false); - } + scheduler.sweep_timer_queue_for_timeouts(); }, else => {}, } @@ -605,6 +572,55 @@ pub fn generic_interrupt_handler(_: *TrapFrame) callconv(.c) void { } } +fn sweep_timer_queue_for_timeouts(scheduler: *Scheduler) void { + while (scheduler.timer_queue.popFirst()) |node| { + const task: *Task = @alignCast(@fieldParentPtr("node", node)); + if (!task.state.alarm_set.is_reached()) { + scheduler.timer_queue.prepend(&task.node); + break; + } + + if (task.wait_queue) |wait_queue| { + wait_queue.remove(task); + task.wait_queue = null; + } + + scheduler.ready(task, .{ .timeout = true }); + } + + if (scheduler.timer_queue.first) |node| { + const task: *Task = @alignCast(@fieldParentPtr("node", node)); + systimer_alarm.set_target(@intFromEnum(task.state.alarm_set)); + systimer_alarm.set_enabled(true); + } else { + systimer_alarm.set_enabled(false); + } +} + +fn ready(scheduler: *Scheduler, task: *Task, flags: Task.ReadyFlags) void { + assert(task.state != .ready); + task.state = .{ .ready = flags }; + scheduler.ready_queue.put(task); +} + +// fn maybe_inherit_priority_section(scheduler: *Scheduler, task: *Task, priority: Priority) void { +// if (!priority.is_greater(task.priority)) return; +// +// task.priority = priority; +// +// switch (task.state) { +// .ready => { +// scheduler.ready_queue.inner.remove(task); +// scheduler.ready_queue.put(task); +// }, +// else => {}, +// } +// +// if (task.active_wait_list) |wait_list| { +// wait_list.list.update_priority(wait_list.awaiter); +// } +// } + pub const Task = struct { context: Context, stack: []u8, @@ -619,6 +635,12 @@ pub const Task = struct { /// Task specific semaphore (required by the wifi driver) semaphore: Semaphore = .init(0), + /// Node used for external wait queues. + wait_queue_node: std.DoublyLinkedList.Node = .{}, + + /// The wait queue in which this task is currently placed. + wait_queue: ?*PriorityWaitQueue = null, + pub const State = union(enum) { none, ready: ReadyFlags, @@ -632,44 +654,83 @@ pub const Task = struct { timeout: bool = false, }; - // TODO: Maybe swap with something more efficient. - pub const ReadyPriorityQueue = struct { - inner: std.DoublyLinkedList = .{}, + pub const WaitQueueEntry = struct { + queue: *std.DoublyLinkedList, + node: std.DoublyLinkedList = .{}, + }; +}; - pub fn peek_top(pq: *ReadyPriorityQueue) ?*Task { - if (pq.inner.first) |first_node| { - return @alignCast(@fieldParentPtr("node", first_node)); - } else { - return null; - } +// TODO: Maybe swap with something more efficient. +pub const ReadyPriorityQueue = struct { + inner: std.DoublyLinkedList = .{}, + + pub fn peek_top(pq: *ReadyPriorityQueue) ?*Task { + if (pq.inner.first) |first_node| { + return @alignCast(@fieldParentPtr("node", first_node)); + } else { + return null; } + } - pub fn pop(pq: *ReadyPriorityQueue, maybe_more_than_prio: ?Priority) ?*Task { - if (pq.peek_top()) |task| { - if (maybe_more_than_prio) |more_than_prio| { - if (!task.priority.is_greater(more_than_prio)) { - return null; - } + pub fn pop(pq: *ReadyPriorityQueue, maybe_more_than_prio: ?Priority) ?*Task { + if (pq.peek_top()) |task| { + if (maybe_more_than_prio) |more_than_prio| { + if (!task.priority.is_greater(more_than_prio)) { + return null; } - pq.inner.remove(&task.node); - return task; } + pq.inner.remove(&task.node); + return task; + } + return null; + } + + pub fn put(pq: *ReadyPriorityQueue, new_task: *Task) void { + var maybe_node = pq.inner.first; + while (maybe_node) |node| : (maybe_node = node.next) { + const task: *Task = @alignCast(@fieldParentPtr("node", node)); + if (new_task.priority.is_greater(task.priority)) { + pq.inner.insertBefore(node, &new_task.node); + break; + } + } else { + pq.inner.append(&new_task.node); + } + } +}; + +pub const PriorityWaitQueue = struct { + list: std.DoublyLinkedList = .{}, + + pub fn pop(q: *PriorityWaitQueue) ?*Task { + if (q.list.popFirst()) |first_node| { + return @alignCast(@fieldParentPtr("wait_queue_node", first_node)); + } else { return null; } + } - pub fn append(pq: *ReadyPriorityQueue, new_task: *Task) void { - var maybe_node = pq.inner.first; - while (maybe_node) |node| : (maybe_node = node.next) { - const task: *Task = @alignCast(@fieldParentPtr("node", node)); - if (new_task.priority.is_greater(task.priority)) { - pq.inner.insertBefore(node, &new_task.node); - break; - } - } else { - pq.inner.append(&new_task.node); + pub fn put(q: *PriorityWaitQueue, task: *Task) void { + var it = q.list.first; + while (it) |current_node| : (it = current_node.next) { + const current_task: *Task = @alignCast(@fieldParentPtr("wait_queue_node", current_node)); + if (task.priority.is_greater(current_task.priority)) { + q.list.insertBefore(¤t_task.wait_queue_node, &task.wait_queue_node); + break; } + } else { + q.list.append(&task.wait_queue_node); } - }; + } + + pub fn remove(q: *PriorityWaitQueue, task: *Task) void { + q.list.remove(&task.wait_queue_node); + } + + pub fn update_priority(q: *PriorityWaitQueue, task: *Task) void { + q.remove(&task.wait_queue_node); + q.put(task); + } }; pub const Context = extern struct { @@ -685,39 +746,9 @@ pub const Context = extern struct { } }; -pub const WaitingList = struct { - inner: std.DoublyLinkedList = .{}, - - pub const Awaiter = struct { - task: *Task, - node: std.DoublyLinkedList.Node = .{}, - }; - - pub fn get_highest_priority(l: *WaitingList) ?*Task { - var maybe_node = l.inner.first; - var maybe_max_priority_awaiter: ?*Awaiter = null; - while (maybe_node) |node| : (maybe_node = node.next) { - const awaiter: *Awaiter = @fieldParentPtr("node", node); - if (maybe_max_priority_awaiter) |max_priority_awaiter| { - if (awaiter.task.priority.is_greater(max_priority_awaiter.task.priority)) { - maybe_max_priority_awaiter = awaiter; - } - } else { - maybe_max_priority_awaiter = awaiter; - } - } - - return if (maybe_max_priority_awaiter) |max_priority_awaiter| - max_priority_awaiter.task - else - null; - } -}; - -// TODO: implement priority inheritance pub const Mutex = struct { state: State = .unlocked, - awaiters: WaitingList = .{}, + wait_queue: PriorityWaitQueue = .{}, pub const State = enum(u32) { locked, @@ -737,14 +768,11 @@ pub const Mutex = struct { else null; - var awaiter: WaitingList.Awaiter = .{ - .task = scheduler.current_task, - }; - mutex.awaiters.inner.append(&awaiter.node); - defer mutex.awaiters.inner.remove(&awaiter.node); - while (mutex.state != .unlocked) { - scheduler.yield(.{ .wait = maybe_timeout_ticks }); + scheduler.yield(.{ .wait = .{ + .timeout = maybe_timeout_ticks, + .wait_queue = &mutex.wait_queue, + } }); if (scheduler.current_task.state.running.timeout) return error.Timeout; } @@ -759,16 +787,14 @@ pub const Mutex = struct { assert(mutex.state == .locked); mutex.state = .unlocked; - if (mutex.awaiters.get_highest_priority()) |task| { - scheduler.make_task_ready_from_cs(task, .{}, cs); - } + scheduler.wake_from_wait_queue(&mutex.wait_queue, .one); } }; pub const RecursiveMutex = struct { value: u32 = 0, owning_task: ?*Task = null, - awaiters: WaitingList = .{}, + wait_queue: PriorityWaitQueue = .{}, pub fn lock(mutex: *RecursiveMutex, scheduler: *Scheduler) void { const cs = enter_critical_section(); @@ -781,19 +807,21 @@ pub const RecursiveMutex = struct { return; } - var awaiter: WaitingList.Awaiter = .{ - .task = current_task, - }; - mutex.awaiters.inner.append(&awaiter.node); - defer mutex.awaiters.inner.remove(&awaiter.node); - + // if (mutex.owning_task) |owning_task| { + // // if (current_task.priority.is_greater(owning_task.priority)) { + // // scheduler.change_task_priority_from_cs(owning_task, current_task.priority, cs); + // // } + // while (mutex.owning_task != null) { - scheduler.yield(.{ .wait = null }); + scheduler.yield(.{ .wait = .{ + .wait_queue = &mutex.wait_queue, + } }); } + // } assert(mutex.value == 0); mutex.value += 1; - mutex.owning_task = scheduler.current_task; + mutex.owning_task = current_task; } pub fn unlock(mutex: *RecursiveMutex, scheduler: *Scheduler) bool { @@ -805,10 +833,7 @@ pub const RecursiveMutex = struct { defer scheduler.yield_and_leave_cs(.reschedule, cs); mutex.owning_task = null; - - if (mutex.awaiters.get_highest_priority()) |task| { - scheduler.make_task_ready_from_cs(task, .{}, cs); - } + scheduler.wake_from_wait_queue(&mutex.wait_queue, .one); return true; } else { @@ -820,7 +845,7 @@ pub const RecursiveMutex = struct { pub const Semaphore = struct { value: u32, - awaiters: WaitingList = .{}, + wait_queue: PriorityWaitQueue = .{}, pub fn init(initial_value: u32) Semaphore { return .{ @@ -841,14 +866,11 @@ pub const Semaphore = struct { else null; - var awaiter: WaitingList.Awaiter = .{ - .task = scheduler.current_task, - }; - sem.awaiters.inner.append(&awaiter.node); - defer sem.awaiters.inner.remove(&awaiter.node); - while (sem.value <= 0) { - scheduler.yield(.{ .wait = maybe_timeout_ticks }); + scheduler.yield(.{ .wait = .{ + .timeout = maybe_timeout_ticks, + .wait_queue = &sem.wait_queue, + } }); if (scheduler.current_task.state.running.timeout) return error.Timeout; } @@ -862,42 +884,21 @@ pub const Semaphore = struct { sem.value += 1; - if (sem.awaiters.get_highest_priority()) |task| { - scheduler.make_task_ready_from_cs(task, .{}, cs); - } + scheduler.wake_from_wait_queue(&sem.wait_queue, .one); } }; -pub const QueueClosedError = error{Closed}; - pub const TypeErasedQueue = struct { - closed: bool, - - /// Ring buffer. This data is logically *after* queued getters. buffer: []u8, start: usize, len: usize, - putters: std.DoublyLinkedList, - getters: std.DoublyLinkedList, - - const Put = struct { - remaining: []const u8, - needed: usize, - task: *Task, - node: std.DoublyLinkedList.Node, - }; - - const Get = struct { - remaining: []u8, - needed: usize, - task: *Task, - node: std.DoublyLinkedList.Node, - }; + putters: PriorityWaitQueue, + getters: PriorityWaitQueue, pub fn init(buffer: []u8) TypeErasedQueue { + assert(buffer.len != 0); // buffer len must be greater than 0 return .{ - .closed = false, .buffer = buffer, .start = 0, .len = 0, @@ -906,125 +907,57 @@ pub const TypeErasedQueue = struct { }; } - pub fn close(q: *TypeErasedQueue, scheduler: *Scheduler) void { - const cs = microzig.interrupt.enter_critical_section(); - defer scheduler.yield_and_leave_cs(.reschedule, cs); - - q.closed = true; - - { - var it = q.getters.first; - while (it) |node| : (it = node.next) { - const getter: *Get = @alignCast(@fieldParentPtr("node", node)); - scheduler.make_task_ready_from_cs(getter.task, .{}, cs); - } - } - - { - var it = q.putters.first; - while (it) |node| : (it = node.next) { - const putter: *Put = @alignCast(@fieldParentPtr("node", node)); - scheduler.make_task_ready_from_cs(putter.task, .{}, cs); - } - } - } - - pub fn put(q: *TypeErasedQueue, scheduler: *Scheduler, elements: []const u8, min: usize) QueueClosedError!usize { - return q.put_with_timeout(scheduler, elements, min, null) catch |err| switch (err) { - error.Timeout => unreachable, - error.Closed => return error.Closed, - }; - } - - pub fn put_with_timeout( + pub fn put( q: *TypeErasedQueue, scheduler: *Scheduler, elements: []const u8, min: usize, maybe_timeout: ?time.Duration, - ) (QueueClosedError || TimeoutError)!usize { + ) usize { assert(elements.len >= min); if (elements.len == 0) return 0; const cs = enter_critical_section(); - defer cs.leave(); - - const n = try q.put_nonblocking(scheduler, elements, cs); - - // Don't block if we hit the target. - if (n >= min) return min; + defer scheduler.yield_and_leave_cs(.reschedule, cs); const maybe_timeout_ticks: ?TimerTicks = if (maybe_timeout) |timeout| .after(timeout) else null; - var pending: Put = .{ - .remaining = elements[n..], - .needed = min - n, - .task = scheduler.current_task, - .node = .{}, - }; - q.putters.append(&pending.node); - defer if (pending.needed > 0) q.putters.remove(&pending.node); + var n: usize = 0; - while (pending.needed > 0 and !q.closed) { - scheduler.yield(.{ .wait = maybe_timeout_ticks }); - if (scheduler.current_task.state.running.timeout) - return error.Timeout; - } + while (true) { + n += q.put_non_blocking_from_cs(scheduler, elements[n..]); + if (n >= min) return n; - if (pending.remaining.len == elements.len) { - // The queue was closed while we were waiting. We appended no elements. - assert(q.closed); - return error.Closed; + scheduler.yield(.{ .wait = .{ + .timeout = maybe_timeout_ticks, + .wait_queue = &q.putters, + } }); + if (scheduler.current_task.state.running.timeout) + return n; } - return elements.len - pending.remaining.len; } - pub fn put_from_isr(q: *TypeErasedQueue, scheduler: *Scheduler, elements: []const u8) QueueClosedError!usize { + pub fn put_non_blocking(q: *TypeErasedQueue, scheduler: *Scheduler, elements: []const u8) usize { const cs = enter_critical_section(); defer cs.leave(); - return q.put_nonblocking(scheduler, elements, cs); + return q.put_non_blocking_from_cs(scheduler, elements); } - pub fn put_nonblocking(q: *TypeErasedQueue, scheduler: *Scheduler, elements: []const u8, cs: CriticalSection) QueueClosedError!usize { - // A closed queue cannot be added to, even if there is space in the buffer. - if (q.closed) return error.Closed; - - // Getters have first priority on the data, and only when the getters - // queue is empty do we start populating the buffer. - - // The number of elements we add immediately, before possibly blocking. + fn put_non_blocking_from_cs(q: *TypeErasedQueue, scheduler: *Scheduler, elements: []const u8) usize { var n: usize = 0; - - while (q.getters.popFirst()) |getter_node| { - const getter: *Get = @alignCast(@fieldParentPtr("node", getter_node)); - const copy_len = @min(getter.remaining.len, elements.len - n); - assert(copy_len > 0); - @memcpy(getter.remaining[0..copy_len], elements[n..][0..copy_len]); - getter.remaining = getter.remaining[copy_len..]; - getter.needed -|= copy_len; - n += copy_len; - if (getter.needed == 0) { - scheduler.make_task_ready_from_cs(getter.task, .{}, cs); - } else { - assert(n == elements.len); // we didn't have enough elements for the getter - q.getters.prepend(getter_node); - } - if (n == elements.len) return elements.len; - } - while (q.puttable_slice()) |slice| { const copy_len = @min(slice.len, elements.len - n); assert(copy_len > 0); @memcpy(slice[0..copy_len], elements[n..][0..copy_len]); q.len += copy_len; n += copy_len; - if (n == elements.len) return elements.len; + if (n == elements.len) break; } - + if (n > 0) scheduler.wake_from_wait_queue(&q.getters, .one); return n; } @@ -1038,33 +971,48 @@ pub const TypeErasedQueue = struct { return if (slice.len > 0) slice else null; } - pub fn get(q: *TypeErasedQueue, scheduler: *Scheduler, buffer: []u8, min: usize) QueueClosedError!usize { - return q.get_with_timeout(scheduler, buffer, min, null) catch |err| switch (err) { - error.Timeout => unreachable, - error.Closed => return error.Closed, - }; - } - - pub fn get_with_timeout( + pub fn get( q: *TypeErasedQueue, scheduler: *Scheduler, buffer: []u8, min: usize, maybe_timeout: ?time.Duration, - ) (QueueClosedError || TimeoutError)!usize { + ) usize { assert(buffer.len >= min); if (buffer.len == 0) return 0; const cs = enter_critical_section(); - defer cs.leave(); + defer scheduler.yield_and_leave_cs(.reschedule, cs); - // The ring buffer gets first priority, then data should come from any - // queued putters, then finally the ring buffer should be filled with - // data from putters so they can be resumed. + const maybe_timeout_ticks: ?TimerTicks = if (maybe_timeout) |timeout| + .after(timeout) + else + null; - // The number of elements we received immediately, before possibly blocking. var n: usize = 0; + while (true) { + n += q.get_non_blocking_from_cs(scheduler, buffer[n..]); + if (n >= min) return n; + + scheduler.yield(.{ .wait = .{ + .timeout = maybe_timeout_ticks, + .wait_queue = &q.getters, + } }); + if (scheduler.current_task.state.running.timeout) + return n; + } + } + + pub fn get_non_blocking(q: *TypeErasedQueue, scheduler: *Scheduler, buffer: []u8) usize { + const cs = enter_critical_section(); + defer cs.leave(); + + return q.get_non_blocking_from_cs(scheduler, buffer); + } + + fn get_non_blocking_from_cs(q: *TypeErasedQueue, scheduler: *Scheduler, buffer: []u8) usize { + var n: usize = 0; while (q.gettable_slice()) |slice| { const copy_len = @min(slice.len, buffer.len - n); assert(copy_len > 0); @@ -1073,69 +1021,10 @@ pub const TypeErasedQueue = struct { if (q.buffer.len - q.start == 0) q.start = 0; q.len -= copy_len; n += copy_len; - if (n == buffer.len) { - q.fill_ring_buffer_from_putters(scheduler, cs); - return buffer.len; - } - } - - // Copy directly from putters into buffer. - while (q.putters.popFirst()) |putter_node| { - const putter: *Put = @alignCast(@fieldParentPtr("node", putter_node)); - const copy_len = @min(putter.remaining.len, buffer.len - n); - assert(copy_len > 0); - @memcpy(buffer[n..][0..copy_len], putter.remaining[0..copy_len]); - putter.remaining = putter.remaining[copy_len..]; - putter.needed -|= copy_len; - n += copy_len; - if (putter.needed == 0) { - scheduler.make_task_ready_from_cs(putter.task, .{}, cs); - } else { - assert(n == buffer.len); // we didn't have enough space for the putter - q.putters.prepend(putter_node); - } - if (n == buffer.len) { - q.fill_ring_buffer_from_putters(scheduler, cs); - return buffer.len; - } + if (n == buffer.len) break; } - - // No need to call `fillRingBufferFromPutters` from this point onwards, - // because we emptied the ring buffer *and* the putter queue! - - // Don't block if we hit the target or if the queue is closed. Return how - // many elements we could get immediately, unless the queue was closed and - // empty, in which case report `error.Closed`. - if (n == 0 and q.closed) return error.Closed; - if (n >= min or q.closed) return n; - - const maybe_timeout_ticks: ?TimerTicks = if (maybe_timeout) |timeout| - .after(timeout) - else - null; - - var pending: Get = .{ - .remaining = buffer[n..], - .needed = min - n, - .task = scheduler.current_task, - .node = .{}, - }; - q.getters.append(&pending.node); - defer if (pending.needed > 0) q.getters.remove(&pending.node); - - while (pending.needed > 0 and !q.closed) { - scheduler.yield(.{ .wait = maybe_timeout_ticks }); - if (scheduler.current_task.state.running.timeout) - return error.Timeout; - } - - if (pending.remaining.len == buffer.len) { - // The queue was closed while we were waiting. We received no elements. - assert(q.closed); - return error.Closed; - } - - return buffer.len - pending.remaining.len; + if (n > 0) scheduler.wake_from_wait_queue(&q.putters, .one); + return n; } fn gettable_slice(q: *const TypeErasedQueue) ?[]const u8 { @@ -1143,31 +1032,6 @@ pub const TypeErasedQueue = struct { const slice = overlong_slice[0..@min(overlong_slice.len, q.len)]; return if (slice.len > 0) slice else null; } - - /// Called when there is nonzero space available in the ring buffer and - /// potentially putters waiting. The mutex is already held and the task is - /// to copy putter data to the ring buffer and signal any putters whose - /// buffers been fully copied. - fn fill_ring_buffer_from_putters(q: *TypeErasedQueue, scheduler: *Scheduler, cs: CriticalSection) void { - while (q.putters.popFirst()) |putter_node| { - const putter: *Put = @alignCast(@fieldParentPtr("node", putter_node)); - while (q.puttable_slice()) |slice| { - const copy_len = @min(slice.len, putter.remaining.len); - assert(copy_len > 0); - @memcpy(slice[0..copy_len], putter.remaining[0..copy_len]); - q.len += copy_len; - putter.remaining = putter.remaining[copy_len..]; - putter.needed -|= copy_len; - if (putter.needed == 0) { - scheduler.make_task_ready_from_cs(putter.task, .{}, cs); - break; - } - } else { - q.putters.prepend(putter_node); - break; - } - } - } }; pub fn Queue(Elem: type) type { @@ -1184,37 +1048,36 @@ pub fn Queue(Elem: type) type { q.type_erased.close(scheduler); } - pub fn put(q: *Self, scheduler: *Scheduler, elements: []const Elem, min: usize) QueueClosedError!usize { - return @divExact(try q.type_erased.put(scheduler, @ptrCast(elements), min * @sizeOf(Elem)), @sizeOf(Elem)); + pub fn put(q: *Self, scheduler: *Scheduler, elements: []const Elem, min: usize, timeout: ?time.Duration) usize { + return @divExact(q.type_erased.put(scheduler, @ptrCast(elements), min * @sizeOf(Elem), timeout), @sizeOf(Elem)); } - pub fn put_all(q: *Self, scheduler: *Scheduler, elements: []const Elem) QueueClosedError!void { - const n = try q.put(scheduler, elements, elements.len); - if (n != elements.len) { - _ = try q.put(scheduler, elements[n..], elements.len - n); - unreachable; - } + pub fn put_all(q: *Self, scheduler: *Scheduler, elements: []const Elem, timeout: ?time.Duration) TimeoutError!void { + if (q.put(scheduler, elements, elements.len, timeout) != elements.len) + return error.Timeout; } - pub fn put_one(q: *Self, scheduler: *Scheduler, item: Elem) QueueClosedError!void { - assert(try q.put(scheduler, &.{item}, 1) == 1); + pub fn put_one(q: *Self, scheduler: *Scheduler, item: Elem) TimeoutError!void { + if (q.put(scheduler, &.{item}, 1, null) != 1) + return error.Timeout; } - pub fn put_from_isr(q: *Self, scheduler: *Scheduler, elements: []const Elem) QueueClosedError!usize { - return @divExact(try q.type_erased.put_from_isr(scheduler, @ptrCast(elements)), @sizeOf(Elem)); + pub fn put_non_blocking(q: *Self, scheduler: *Scheduler, elements: []const Elem) usize { + return @divExact(q.type_erased.put_non_blocking(scheduler, @ptrCast(elements)), @sizeOf(Elem)); } - pub fn put_one_from_isr(q: *Self, scheduler: *Scheduler, item: Elem) QueueClosedError!bool { - return try q.type_erased.put_from_isr(scheduler, @ptrCast(&item)) == @sizeOf(Elem); + pub fn put_one_non_blocking(q: *Self, scheduler: *Scheduler, item: Elem) bool { + return q.put_non_blocking(scheduler, @ptrCast(&item)) == 1; } - pub fn get(q: *Self, scheduler: *Scheduler, buffer: []Elem, target: usize) QueueClosedError!usize { - return @divExact(try q.type_erased.get(scheduler, @ptrCast(buffer), target * @sizeOf(Elem)), @sizeOf(Elem)); + pub fn get(q: *Self, scheduler: *Scheduler, buffer: []Elem, target: usize, timeout: ?time.Duration) usize { + return @divExact(q.type_erased.get(scheduler, @ptrCast(buffer), target * @sizeOf(Elem), timeout), @sizeOf(Elem)); } - pub fn get_one(q: *Self, scheduler: *Scheduler) QueueClosedError!Elem { + pub fn get_one(q: *Self, scheduler: *Scheduler, timeout: ?time.Duration) TimeoutError!Elem { var buf: [1]Elem = undefined; - assert(try q.get(scheduler, &buf, 1) == 1); + if (q.get(scheduler, &buf, 1, timeout) != 1) + return error.Timeout; return buf[0]; } diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index f47fdff75..256c7a066 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -147,13 +147,15 @@ fn setup_interrupts() void { pub const interrupt_handlers = struct { pub fn wifi_xxx(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { - const handler = osi.wifi_interrupt_handler; - - log.debug("interrupt WIFI_xxx {} {?}", .{ - handler.f, - handler.arg, - }); - - handler.f(handler.arg); + if (osi.wifi_interrupt_handler) |handler| { + log.debug("interrupt WIFI_xxx {} {?}", .{ + handler.f, + handler.arg, + }); + + handler.f(handler.arg); + } else { + // should be unreachable + } } }; diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 1224acfc8..d551bee72 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -23,7 +23,7 @@ const coex_enabled: bool = false; pub var allocator: std.mem.Allocator = undefined; pub var scheduler: *Scheduler = undefined; -pub var wifi_interrupt_handler: struct { +pub var wifi_interrupt_handler: ?struct { f: *const fn (?*anyopaque) callconv(.c) void, arg: ?*anyopaque, } = undefined; @@ -233,30 +233,32 @@ pub fn set_isr( switch (n) { 0, 1 => { - wifi_interrupt_handler = .{ - .f = @ptrCast(@alignCast(f)), + wifi_interrupt_handler = if (f) |handler| .{ + .f = @ptrCast(@alignCast(handler)), .arg = arg, - }; + } else null; }, else => @panic("invalid interrupt number"), } } -// TODO pub fn ints_on(mask: u32) callconv(.c) void { log.debug("ints_on {}", .{mask}); if (mask == 2) { microzig.cpu.interrupt.enable(.interrupt29); + } else { + @panic("ints_on: not implemented"); } } -// TODO pub fn ints_off(mask: u32) callconv(.c) void { log.debug("ints_off {}", .{mask}); if (mask == 2) { microzig.cpu.interrupt.disable(.interrupt29); + } else { + @panic("ints_off: not implemented"); } } @@ -347,7 +349,6 @@ pub fn wifi_thread_semphr_get() callconv(.c) ?*anyopaque { return &scheduler.current_task.semaphore; } -// TODO: idk if we're gonna need to implement this pub fn mutex_create() callconv(.c) ?*anyopaque { @panic("mutex_create: not implemented"); } @@ -404,7 +405,7 @@ pub fn queue_create(capacity: u32, item_len: u32) callconv(.c) ?*anyopaque { break :blk .{ 3, 8 }; }; - const buf: []u8 = allocator.alloc(u8, new_cap) catch { + const buf: []u8 = allocator.alloc(u8, new_cap * item_len) catch { log.warn("failed to allocate queue buffer", .{}); return null; }; @@ -439,20 +440,20 @@ pub fn queue_send(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); const item: [*]const u8 = @ptrCast(@alignCast(item_ptr)); - const maybe_timeout: ?time.Duration = if (block_time_tick == c.OSI_FUNCS_TIME_BLOCKING) - .from_us(block_time_tick) - else - null; - - return @intCast(@divExact(queue.inner.put_with_timeout( - scheduler, - item[0..queue.item_len], - queue.item_len, - maybe_timeout, - ) catch |err| switch (err) { - error.Closed => unreachable, - error.Timeout => return -1, - }, queue.item_len)); + const size = switch (block_time_tick) { + 0 => queue.inner.put_non_blocking(scheduler, item[0..queue.item_len]), + else => queue.inner.put( + scheduler, + item[0..queue.item_len], + 1, + if (block_time_tick == c.OSI_FUNCS_TIME_BLOCKING) + .from_us(block_time_tick) + else + null, + ), + }; + if (size == 0) return -1; + return 1; } pub fn queue_send_from_isr(ptr: ?*anyopaque, item_ptr: ?*anyopaque, _hptw: ?*anyopaque) callconv(.c) i32 { @@ -460,10 +461,7 @@ pub fn queue_send_from_isr(ptr: ?*anyopaque, item_ptr: ?*anyopaque, _hptw: ?*any const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); const item: [*]const u8 = @ptrCast(@alignCast(item_ptr)); - const n = @divExact(queue.inner.put_from_isr( - scheduler, - item[0..queue.item_len], - ) catch unreachable, queue.item_len); + const n = @divExact(queue.inner.put_non_blocking(scheduler, item[0..queue.item_len]), queue.item_len); @as(*u32, @ptrCast(@alignCast(_hptw))).* = @intFromBool(scheduler.is_a_higher_priority_task_ready()); @@ -484,20 +482,23 @@ pub fn queue_recv(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); const item: [*]u8 = @ptrCast(@alignCast(item_ptr)); - const maybe_timeout: ?time.Duration = if (block_time_tick == c.OSI_FUNCS_TIME_BLOCKING) - .from_us(block_time_tick) - else - null; - - return @intCast(@divExact(queue.inner.get_with_timeout( - scheduler, - item[0..queue.item_len], - queue.item_len, - maybe_timeout, - ) catch |err| switch (err) { - error.Closed => unreachable, - error.Timeout => return -1, - }, queue.item_len)); + const size = switch (block_time_tick) { + 0 => queue.inner.get_non_blocking( + scheduler, + item[0..queue.item_len], + ), + else => queue.inner.get( + scheduler, + item[0..queue.item_len], + queue.item_len, + if (block_time_tick == c.OSI_FUNCS_TIME_BLOCKING) + .from_us(block_time_tick) + else + null, + ), + }; + if (size == 0) return -1; + return 1; } pub fn queue_msg_waiting(ptr: ?*anyopaque) callconv(.c) u32 { @@ -598,8 +599,12 @@ pub fn task_create( return task_create_common(task_func, name, stack_depth, param, prio, task_handle, 0); } -pub fn task_delete() callconv(.c) void { - @panic("task_delete: not implemented"); +pub fn task_delete(handle: ?*anyopaque) callconv(.c) void { + log.debug("task_delete {?}", .{handle}); + if (handle != null) { + @panic("task_delete(non-null): not implemented"); + } + scheduler.yield(.delete); } pub fn task_delay(tick: u32) callconv(.c) void { @@ -1062,7 +1067,7 @@ pub fn wifi_create_queue(capacity: c_int, item_len: c_int) callconv(.c) ?*anyopa std.debug.assert(wifi_queue_handle == null); - const buf: []u8 = allocator.alloc(u8, @intCast(capacity)) catch { + const buf: []u8 = allocator.alloc(u8, @intCast(capacity * item_len)) catch { log.warn("failed to allocate queue buffer", .{}); return null; }; @@ -1083,8 +1088,8 @@ pub fn wifi_delete_queue(ptr: ?*anyopaque) callconv(.c) void { std.debug.assert(ptr == @as(?*anyopaque, @ptrCast(&wifi_queue_handle))); - const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); - allocator.free(queue.inner.buffer); + const queue: *?*QueueWrapper = @ptrCast(@alignCast(ptr)); + allocator.free(queue.*.?.inner.buffer); wifi_queue_handle = null; wifi_queue = undefined; diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index 1db1d0582..aca8a91c1 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -666,7 +666,7 @@ export var g_wifi_osi_funcs: c.wifi_osi_funcs_t = .{ ._event_group_wait_bits = @ptrCast(&osi.event_group_wait_bits), ._task_create_pinned_to_core = osi.task_create_pinned_to_core, ._task_create = osi.task_create, - ._task_delete = @ptrCast(&osi.task_delete), + ._task_delete = osi.task_delete, ._task_delay = osi.task_delay, ._task_ms_to_tick = osi.task_ms_to_tick, ._task_get_current_task = osi.task_get_current_task, From 0cb0872d4b85ca39831ff058ab68d5fbb5ac46a4 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Sat, 3 Jan 2026 11:13:12 +0200 Subject: [PATCH 19/43] Fix spawn parameters and update to latest esp wifi --- examples/espressif/esp/src/scheduler.zig | 17 ++- modules/riscv32-common/src/riscv32_common.zig | 1 - port/espressif/esp/build.zig | 132 +++++++++--------- port/espressif/esp/build.zig.zon | 4 +- .../esp/ld/esp32_c3/image_boot_sections.ld | 7 +- port/espressif/esp/src/hal/Scheduler.zig | 103 +++++--------- .../hal/radio/libc_dummy_include/sys/lock.h | 0 port/espressif/esp/src/hal/radio/osi.zig | 44 ++++-- port/espressif/esp/src/hal/radio/wifi.zig | 8 +- 9 files changed, 162 insertions(+), 154 deletions(-) create mode 100644 port/espressif/esp/src/hal/radio/libc_dummy_include/sys/lock.h diff --git a/examples/espressif/esp/src/scheduler.zig b/examples/espressif/esp/src/scheduler.zig index 14486f846..b0f9f4449 100644 --- a/examples/espressif/esp/src/scheduler.zig +++ b/examples/espressif/esp/src/scheduler.zig @@ -20,30 +20,33 @@ pub const microzig_options: microzig.Options = .{ }; var heap_buf: [10 * 1024]u8 = undefined; -var scheduler: esp.Scheduler = undefined; -var buffer: [1]u32 = undefined; -var queue: esp.Scheduler.Queue(u32) = .init(&buffer); -fn task1() void { +fn task1(scheduler: *esp.Scheduler, queue: *esp.Scheduler.Queue(u32)) void { for (0..5) |i| { - queue.put_one(&scheduler, i) catch { + queue.put_one(scheduler, i) catch { std.log.err("failed to put item", .{}); continue; }; scheduler.sleep(.from_ms(500)); } - scheduler.yield(.delete); } pub fn main() !void { var heap = microzig.Allocator.init_with_buffer(&heap_buf); const allocator = heap.allocator(); + var scheduler: esp.Scheduler = undefined; + var buffer: [1]u32 = undefined; + var queue: esp.Scheduler.Queue(u32) = .init(&buffer); + scheduler.init(allocator); esp.time.sleep_ms(1000); - _ = try scheduler.spawn(task1, .{}, .{ + _ = try scheduler.spawn(task1, .{ + &scheduler, + &queue, + }, .{ .stack_size = 8000, }); diff --git a/modules/riscv32-common/src/riscv32_common.zig b/modules/riscv32-common/src/riscv32_common.zig index ccc90fa17..72a1da7be 100644 --- a/modules/riscv32-common/src/riscv32_common.zig +++ b/modules/riscv32-common/src/riscv32_common.zig @@ -60,7 +60,6 @@ pub fn fence() void { // NOTE: Contains all CSRs (Control Status Registers) from the riscv manual and should follow their // spec. Cpu implementations can reexport what they need from here. // See https://docs.riscv.org/reference/isa/priv/priv-csrs.html ->>>>>>> main pub const csr = struct { pub const fflags = Csr(0x001, u32); pub const frm = Csr(0x002, u32); diff --git a/port/espressif/esp/build.zig b/port/espressif/esp/build.zig index 7c778d60f..61d959bfd 100644 --- a/port/espressif/esp/build.zig +++ b/port/espressif/esp/build.zig @@ -12,41 +12,29 @@ chips: struct { boards: struct {}, -const esp32_c3_zig_target: std.Target.Query = .{ - .cpu_arch = .riscv32, - .cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32 }, - .cpu_features_add = std.Target.riscv.featureSet(&.{ - .c, - .m, - }), - .os_tag = .freestanding, - .abi = .eabi, -}; - pub fn init(dep: *std.Build.Dependency) Self { const b = dep.builder; + const esp32_c3_zig_target: std.Target.Query = .{ + .cpu_arch = .riscv32, + .cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32 }, + .cpu_features_add = std.Target.riscv.featureSet(&.{ + .c, + .m, + }), + .os_tag = .freestanding, + .abi = .eabi, + }; + const riscv32_common_dep = b.dependency("microzig/modules/riscv32-common", .{}); const riscv32_common_mod = riscv32_common_dep.module("riscv32-common"); const esp_image_dep = b.dependency("microzig/tools/esp-image", .{}); const esp_image_mod = esp_image_dep.module("esp_image"); - const hal: microzig.HardwareAbstractionLayer = .{ - .root_source_file = b.path("src/hal.zig"), - .imports = b.allocator.dupe(std.Build.Module.Import, &.{ - .{ - .name = "esp_image", - .module = esp_image_mod, - }, - .{ - .name = "esp-wifi-driver", - .module = dep.module("esp-wifi-driver"), - }, - }) catch @panic("OOM"), - }; + const esp_wifi_driver_mod = make_esp_wifi_driver_module(b, "esp32c3", esp32_c3_zig_target); - const chip_esp32_c3: microzig.Target = .{ + const chip_esp32c3: microzig.Target = .{ .dep = dep, .preferred_binary_format = .{ .esp = .{ .chip_id = .esp32_c3, @@ -81,7 +69,19 @@ pub fn init(dep: *std.Build.Dependency) Self { .{ .name = "DRAM", .tag = .ram, .offset = 0x3FC7C000 + 0x4000, .length = 313 * 1024, .access = .rw }, }, }, - .hal = hal, + .hal = .{ + .root_source_file = b.path("src/hal.zig"), + .imports = b.allocator.dupe(std.Build.Module.Import, &.{ + .{ + .name = "esp_image", + .module = esp_image_mod, + }, + .{ + .name = "esp-wifi-driver", + .module = esp_wifi_driver_mod, + }, + }) catch @panic("OOM"), + }, .linker_script = .{ .generate = .memory_regions, .file = generate_linker_script( @@ -95,8 +95,8 @@ pub fn init(dep: *std.Build.Dependency) Self { return .{ .chips = .{ - .esp32_c3 = chip_esp32_c3.derive(.{}), - .esp32_c3_direct_boot = chip_esp32_c3.derive(.{ + .esp32_c3 = chip_esp32c3.derive(.{}), + .esp32_c3_direct_boot = chip_esp32c3.derive(.{ .preferred_binary_format = .bin, .cpu = .{ .name = "esp_riscv", @@ -122,7 +122,7 @@ pub fn init(dep: *std.Build.Dependency) Self { ), }, }), - .esp32_c3_flashless = chip_esp32_c3.derive(.{ + .esp32_c3_flashless = chip_esp32c3.derive(.{ .ram_image = true, .linker_script = .{ .generate = .memory_regions, @@ -164,27 +164,56 @@ pub fn build(b: *std.Build) void { }), }); b.installArtifact(cat_exe); +} + +const BootMode = enum { + direct, + image, +}; + +fn get_cpu_config(b: *std.Build, boot_mode: BootMode) *std.Build.Module { + const options = b.addOptions(); + options.addOption(BootMode, "boot_mode", boot_mode); + return b.createModule(.{ + .root_source_file = options.getOutput(), + }); +} + +fn generate_linker_script( + dep: *std.Build.Dependency, + output_name: []const u8, + base_path: std.Build.LazyPath, + rom_functions_path: std.Build.LazyPath, +) std.Build.LazyPath { + const b = dep.builder; + const cat_exe = dep.artifact("cat"); - const esp32_c3_resolved_zig_target = b.resolveTargetQuery(esp32_c3_zig_target); + const run = b.addRunArtifact(cat_exe); + run.addFileArg(base_path); + run.addFileArg(rom_functions_path); + return run.addOutputFileArg(output_name); +} +fn make_esp_wifi_driver_module(b: *std.Build, chip_name: []const u8, target_query: std.Target.Query) *std.Build.Module { const esp_wifi_sys_dep = b.dependency("esp-wifi-sys", .{}); + const esp32_c3_resolved_zig_target = b.resolveTargetQuery(target_query); const translate_c = b.addTranslateC(.{ - .root_source_file = esp_wifi_sys_dep.path("esp-wifi-sys/include/include.h"), + .root_source_file = esp_wifi_sys_dep.path("c/include/include.h"), .target = esp32_c3_resolved_zig_target, .optimize = .Debug, .link_libc = false, }); translate_c.addIncludePath(b.path("src/hal/radio/libc_dummy_include")); - translate_c.addIncludePath(esp_wifi_sys_dep.path("esp-wifi-sys/include")); - translate_c.addIncludePath(esp_wifi_sys_dep.path("esp-wifi-sys/headers")); - - // esp32_c3 specific - translate_c.addIncludePath(esp_wifi_sys_dep.path("esp-wifi-sys/headers/esp32c3")); + translate_c.addIncludePath(esp_wifi_sys_dep.path("c/headers")); + translate_c.addIncludePath(esp_wifi_sys_dep.path("c/include")); + translate_c.addIncludePath(esp_wifi_sys_dep.path(b.fmt("c/include/{s}", .{chip_name}))); + translate_c.addIncludePath(esp_wifi_sys_dep.path(b.fmt("c/headers/{s}", .{chip_name}))); const mod = translate_c.addModule("esp-wifi-driver"); - mod.addLibraryPath(esp_wifi_sys_dep.path("esp-wifi-sys/libs/esp32c3")); + mod.addLibraryPath(esp_wifi_sys_dep.path(b.fmt("esp-wifi-sys-{s}/libs", .{chip_name}))); + inline for (&.{ "btbb", "btdm_app", @@ -195,6 +224,7 @@ pub fn build(b: *std.Build) void { "net80211", "phy", "pp", + "regulatory", "smartconfig", "wapi", "wpa_supplicant", @@ -205,32 +235,6 @@ pub fn build(b: *std.Build) void { mod.linkSystemLibrary("printf", .{ .weak = true, }); -} - -const BootMode = enum { - direct, - image, -}; - -fn get_cpu_config(b: *std.Build, boot_mode: BootMode) *std.Build.Module { - const options = b.addOptions(); - options.addOption(BootMode, "boot_mode", boot_mode); - return b.createModule(.{ - .root_source_file = options.getOutput(), - }); -} - -fn generate_linker_script( - dep: *std.Build.Dependency, - output_name: []const u8, - base_path: std.Build.LazyPath, - rom_functions_path: std.Build.LazyPath, -) std.Build.LazyPath { - const b = dep.builder; - const cat_exe = dep.artifact("cat"); - const run = b.addRunArtifact(cat_exe); - run.addFileArg(base_path); - run.addFileArg(rom_functions_path); - return run.addOutputFileArg(output_name); + return mod; } diff --git a/port/espressif/esp/build.zig.zon b/port/espressif/esp/build.zig.zon index f2bd34b37..45d32550d 100644 --- a/port/espressif/esp/build.zig.zon +++ b/port/espressif/esp/build.zig.zon @@ -7,8 +7,8 @@ .@"microzig/modules/riscv32-common" = .{ .path = "../../../modules/riscv32-common" }, .@"microzig/tools/esp-image" = .{ .path = "../../../tools/esp-image" }, .@"esp-wifi-sys" = .{ - .url = "git+https://github.com/esp-rs/esp-wifi-sys#99dd43f9992b276efff3c75467839801e7ea55c7", - .hash = "N-V-__8AAHPIYgTLdblCzREIT-te95dusmO6q1o0PST0CziO", + .url = "git+https://github.com/esp-rs/esp-wifi-sys#7623c8d746b55cd8d9f7473359069aef381b7d3b", + .hash = "N-V-__8AANWs5wTMnVsq2oG-zKWSf91LJ6q8Vv6EUD6xNbKJ", }, }, .paths = .{ diff --git a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld index 28651d043..1afedad5c 100644 --- a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld @@ -90,7 +90,7 @@ SECTIONS .data.wifi : { *(.dram1*) - } + } > DRAM .bss (NOLOAD) : { @@ -100,5 +100,10 @@ SECTIONS microzig_bss_end = .; } > DRAM + .tbss (NOLOAD) : + { + *(.tbss*) + } + PROVIDE(__global_pointer$ = microzig_data_start + 0x800); } diff --git a/port/espressif/esp/src/hal/Scheduler.zig b/port/espressif/esp/src/hal/Scheduler.zig index 81f02996c..e6ac4c053 100644 --- a/port/espressif/esp/src/hal/Scheduler.zig +++ b/port/espressif/esp/src/hal/Scheduler.zig @@ -19,13 +19,13 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; // task on the stack. What is interresting is that the two context switches are // compatible. Voluntary yield can resume a task that was interrupted by force // and vice versa. Because of the forced yield, tasks are required to have a -// minimum stack size available. +// minimum stack size available at all times. // TODO: add identifier names to tasks // TODO: stack usage report based on stack painting -// TODO: implement task garbage collection +// TODO: idle improvements +// - implement task garbage collection // TODO: for other esp32 chips with multicore support SMP -// TODO: make a cancelation system // TODO: implement std.Io // TODO: use @stackUpperBound when implemented // TODO: implement priority inheritance for respective sync primitives @@ -61,7 +61,7 @@ scheduled_for_deletion_list: std.DoublyLinkedList = .{}, main_task: Task, // Idle only requires the stack space used by the yield interrupt -idle_stack: [std.mem.alignForward(usize, EXTRA_STACK_SIZE, STACK_ALIGN.toByteUnits())]u8 align(STACK_ALIGN.toByteUnits()) = undefined, +idle_stack: [STACK_ALIGN.forward(EXTRA_STACK_SIZE)]u8 align(STACK_ALIGN.toByteUnits()) = undefined, idle_task: Task, /// The task in .running state @@ -133,68 +133,56 @@ fn idle() callconv(.naked) void { ); } -fn task_entry() callconv(.naked) void { - asm volatile ( - \\lw a0, -4(sp) - \\lw a1, -8(sp) - \\jr a1 - \\ - ); -} +pub const SpawnOptions = struct { + stack_size: usize = 4096, + // TODO: should we ban idle priority? + priority: Priority = .lowest, +}; pub fn spawn( scheduler: *Scheduler, - function: anytype, + comptime function: anytype, args: std.meta.ArgsTuple(@TypeOf(function)), options: SpawnOptions, ) !*Task { const Args = @TypeOf(args); + const args_align: std.mem.Alignment = comptime .fromByteUnits(@alignOf(Args)); + const TypeErased = struct { - fn start(context: ?*anyopaque) callconv(.c) noreturn { - const args_casted: *const Args = @ptrCast(@alignCast(context)); + fn call() callconv(.c) void { + const sched = maybe_instance orelse @panic("no active scheduler"); - @call(.auto, function, args_casted.*); + const context_ptr: *const Args = + @ptrFromInt(args_align.forward(@intFromPtr(sched.current_task) + @sizeOf(Task))); + @call(.auto, function, context_ptr.*); - const sched = maybe_instance orelse @panic("no active scheduler"); - sched.yield(.delete); - unreachable; + if (@typeInfo(@TypeOf(function)).@"fn".return_type.? != noreturn) { + sched.yield(.delete); + unreachable; + } } }; - // SAFETY: @constCast is safe to use since the task entry only dereferences the ptr - return scheduler.raw_spawn_with_options(TypeErased.start, @ptrCast(@constCast(&args)), options); -} + const alloc_align = comptime STACK_ALIGN.max(.of(Task)).max(args_align); -pub const SpawnOptions = struct { - stack_size: usize = 4096, - // TODO: should we ban idle priority? - priority: Priority = .lowest, -}; + const args_start = args_align.forward(@sizeOf(Task)); + const stack_start = STACK_ALIGN.forward(args_start + @sizeOf(Args)); + const stack_end = STACK_ALIGN.forward(stack_start + options.stack_size + EXTRA_STACK_SIZE); -pub fn raw_spawn_with_options( - scheduler: *Scheduler, - function: *const fn (param: ?*anyopaque) callconv(.c) noreturn, - param: ?*anyopaque, - options: SpawnOptions, -) !*Task { - const alignment = comptime STACK_ALIGN.max(.of(Task)); + const alloc_size = stack_end; + const raw_alloc = try scheduler.gpa.alignedAlloc(u8, alloc_align, alloc_size); - const unaligned_allocation_size = @sizeOf(Task) + options.stack_size + EXTRA_STACK_SIZE; - const allocation_size = std.mem.alignForward(usize, unaligned_allocation_size, alignment.toByteUnits()); - const raw_alloc = try scheduler.gpa.alignedAlloc(u8, alignment, allocation_size); + const task: *Task = @ptrCast(@alignCast(raw_alloc)); - const task: *Task = @ptrCast(raw_alloc.ptr); - const stack: []u8 = raw_alloc[@sizeOf(Task)..]; - const stack_top = @intFromPtr(stack[stack.len..].ptr); + const task_args: *Args = @alignCast(@ptrCast(raw_alloc[args_start..][0..@sizeOf(Args)])); + task_args.* = args; - const startup_state: *[2]u32 = @ptrFromInt(stack_top - 2 * @sizeOf(u32)); - startup_state[0] = @intFromPtr(function); - startup_state[1] = @intFromPtr(param); + const stack: []u8 = raw_alloc[stack_start..stack_end]; task.* = .{ .context = .{ - .sp = stack_top, - .pc = @intFromPtr(&task_entry), + .sp = @intFromPtr(stack[stack.len..].ptr), + .pc = @intFromPtr(&TypeErased.call), .fp = 0, }, .stack = stack, @@ -294,8 +282,6 @@ pub fn wake_from_wait_queue(scheduler: *Scheduler, wait_queue: *PriorityWaitQueu } } -pub const TimeoutError = error{Timeout}; - pub fn sleep(scheduler: *Scheduler, duration: time.Duration) void { scheduler.yield(.{ .wait = .{ .timeout = .after(duration), @@ -603,24 +589,6 @@ fn ready(scheduler: *Scheduler, task: *Task, flags: Task.ReadyFlags) void { scheduler.ready_queue.put(task); } -// fn maybe_inherit_priority_section(scheduler: *Scheduler, task: *Task, priority: Priority) void { -// if (!priority.is_greater(task.priority)) return; -// -// task.priority = priority; -// -// switch (task.state) { -// .ready => { -// scheduler.ready_queue.inner.remove(task); -// scheduler.ready_queue.put(task); -// }, -// else => {}, -// } -// -// if (task.active_wait_list) |wait_list| { -// wait_list.list.update_priority(wait_list.awaiter); -// } -// } - pub const Task = struct { context: Context, stack: []u8, @@ -746,6 +714,8 @@ pub const Context = extern struct { } }; +pub const TimeoutError = error{Timeout}; + pub const Mutex = struct { state: State = .unlocked, wait_queue: PriorityWaitQueue = .{}, @@ -791,7 +761,9 @@ pub const Mutex = struct { } }; +// TODO: maybe move inside osi.zig since it is made specifically for it pub const RecursiveMutex = struct { + recursive: bool, value: u32 = 0, owning_task: ?*Task = null, wait_queue: PriorityWaitQueue = .{}, @@ -803,6 +775,7 @@ pub const RecursiveMutex = struct { const current_task = scheduler.current_task; if (mutex.owning_task == current_task) { + assert(mutex.recursive); mutex.value += 1; return; } diff --git a/port/espressif/esp/src/hal/radio/libc_dummy_include/sys/lock.h b/port/espressif/esp/src/hal/radio/libc_dummy_include/sys/lock.h new file mode 100644 index 000000000..e69de29bb diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index d551bee72..a4b938f97 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -152,6 +152,10 @@ pub fn usleep(time_us: u32) callconv(.c) c_int { return 0; } +pub fn vTaskDelay(ticks: u32) callconv(.c) void { + scheduler.sleep(.from_us(ticks)); +} + comptime { // provide some weak links so they can be overriten @@ -169,6 +173,7 @@ comptime { @export(&gettimeofday, .{ .name = "gettimeofday", .linkage = .weak }); @export(&sleep, .{ .name = "sleep", .linkage = .weak }); @export(&usleep, .{ .name = "usleep", .linkage = .weak }); + @export(&vTaskDelay, .{ .name = "vTaskDelay", .linkage = .weak }); } pub export var WIFI_EVENT: c.esp_event_base_t = "WIFI_EVENT"; @@ -350,7 +355,19 @@ pub fn wifi_thread_semphr_get() callconv(.c) ?*anyopaque { } pub fn mutex_create() callconv(.c) ?*anyopaque { - @panic("mutex_create: not implemented"); + log.debug("mutex_create", .{}); + + const mutex = allocator.create(Scheduler.RecursiveMutex) catch { + log.warn("failed to allocate recursive mutex", .{}); + return null; + }; + mutex.* = .{ + .recursive = false, + }; + + log.debug(">>>> mutex create: {*}", .{mutex}); + + return mutex; } pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { @@ -360,7 +377,9 @@ pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { log.warn("failed to allocate recursive mutex", .{}); return null; }; - mutex.* = .{}; + mutex.* = .{ + .recursive = true, + }; log.debug(">>>> mutex create: {*}", .{mutex}); @@ -528,6 +547,13 @@ pub fn event_group_wait_bits() callconv(.c) void { @panic("event_group_wait_bits: not implemented"); } +fn task_wrapper( + task_entry: *const fn (param: ?*anyopaque) callconv(.c) noreturn, + param: ?*anyopaque, +) noreturn { + task_entry(param); +} + fn task_create_common( task_func: ?*anyopaque, name: [*c]const u8, @@ -540,14 +566,12 @@ fn task_create_common( _ = name; // autofix _ = core_id; // autofix - const task: *Scheduler.Task = scheduler.raw_spawn_with_options( - @ptrCast(@alignCast(task_func)), - param, - .{ - .priority = @enumFromInt(prio), - .stack_size = stack_depth, - }, - ) catch { + const task_entry: *const fn (param: ?*anyopaque) callconv(.c) noreturn = @ptrCast(@alignCast(task_func)); + + const task: *Scheduler.Task = scheduler.spawn(task_wrapper, .{task_entry, param}, .{ + .priority = @enumFromInt(prio), + .stack_size = stack_depth, + }) catch { log.warn("failed to create task", .{}); return 0; }; diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index aca8a91c1..977a3af18 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -335,11 +335,11 @@ pub fn stop() InternalError!void { /// Non-blocking. pub fn connect() InternalError!void { - try c_result(c.esp_wifi_connect()); + try c_result(c.esp_wifi_connect_internal()); } pub fn disconnect() InternalError!void { - try c_result(c.esp_wifi_disconnect()); + try c_result(c.esp_wifi_disconnect_internal()); } pub const Event = enum(i32) { @@ -445,7 +445,7 @@ pub const Event = enum(i32) { StaNeighborRep, }; -fn event_post( +export fn esp_event_post( base: [*c]const u8, id: i32, data: ?*anyopaque, @@ -673,7 +673,7 @@ export var g_wifi_osi_funcs: c.wifi_osi_funcs_t = .{ ._task_get_max_priority = osi.task_get_max_priority, ._malloc = osi.malloc, ._free = osi.free, - ._event_post = event_post, + ._event_post = esp_event_post, ._get_free_heap_size = @ptrCast(&osi.get_free_heap_size), ._rand = osi.rand, ._dport_access_stall_other_cpu_start_wrap = osi.dport_access_stall_other_cpu_start_wrap, From e18e7ae3ae8fa24f27fd7bf491c3406af75aec9c Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Sat, 3 Jan 2026 23:35:22 +0200 Subject: [PATCH 20/43] Refactor and cleanup no. 1 --- build.zig | 4 +- examples/espressif/esp/build.zig | 149 +++------ examples/espressif/esp/src/wifi.zig | 31 +- .../esp/ld/esp32_c3/direct_boot_sections.ld | 9 +- .../esp/ld/esp32_c3/flashless_sections.ld | 12 +- .../esp/ld/esp32_c3/image_boot_sections.ld | 9 +- port/espressif/esp/src/hal.zig | 9 +- .../esp/src/hal/{Scheduler.zig => RTOS.zig} | 313 +++++++++--------- port/espressif/esp/src/hal/radio.zig | 48 +-- port/espressif/esp/src/hal/radio/osi.zig | 71 ++-- port/espressif/esp/src/hal/radio/wifi.zig | 8 +- port/espressif/esp/src/hal/system.zig | 28 ++ 12 files changed, 331 insertions(+), 360 deletions(-) rename port/espressif/esp/src/hal/{Scheduler.zig => RTOS.zig} (71%) diff --git a/build.zig b/build.zig index 4b396f09e..f1fd346ab 100644 --- a/build.zig +++ b/build.zig @@ -44,12 +44,10 @@ const exe_targets: []const std.Target.Query = &.{ }; pub fn build(b: *Build) void { - const optimize = b.standardOptimizeOption(.{}); - const generate_linker_script_mod = b.createModule(.{ .root_source_file = b.path("tools/generate_linker_script.zig"), .target = b.graph.host, - .optimize = optimize, + .optimize = .ReleaseSafe, }); const generate_linker_script_exe = b.addExecutable(.{ diff --git a/examples/espressif/esp/build.zig b/examples/espressif/esp/build.zig index 1a10bf4ec..3e0732ad8 100644 --- a/examples/espressif/esp/build.zig +++ b/examples/espressif/esp/build.zig @@ -25,17 +25,10 @@ pub fn build(b: *std.Build) void { .{ .name = "systimer", .file = "src/systimer.zig" }, .{ .name = "ws2812_blinky", .file = "src/ws2812_blinky.zig" }, .{ .name = "scheduler", .file = "src/scheduler.zig" }, - }; - - const targeted_examples: []const TargetedExample = &.{ - .{ - .target = .esp32_c3, - .example = .{ - .name = "wifi", - .file = "src/wifi.zig", - .features = .{ .lwip = true }, - }, - }, + .{ .name = "wifi", .file = "src/wifi.zig", .features = .{ + .flashless = false, + .lwip = true, + } }, }; for (examples) |example| { @@ -45,6 +38,9 @@ pub fn build(b: *std.Build) void { continue; for (std.enums.values(TargetEnum)) |target_enum| { + if (!example.features.flashless and std.mem.containsAtLeast(u8, @tagName(target_enum), 1, "flashless")) + continue; + const target_desc = target_enum.get_target_desc(mb); // `add_firmware` basically works like addExecutable, but takes a @@ -59,6 +55,50 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path(example.file), }); + if (example.features.lwip) { + const resolved_zig_target = b.resolveTargetQuery(firmware.target.zig_target); + + const foundation_libc_dep = b.dependency("foundation_libc", .{ + .optimize = optimize, + .target = resolved_zig_target, + }); + const lwip_dep = b.dependency("lwip", .{}); + + const lwip_lib = b.addLibrary(.{ + .name = "lwip", + .root_module = b.createModule(.{ + .optimize = optimize, + .target = resolved_zig_target, + .link_libc = false, + }), + .linkage = .static, + }); + + lwip_lib.addCSourceFiles(.{ + .root = lwip_dep.path("src"), + .files = &lwip_files, + .flags = &lwip_flags, + }); + + lwip_lib.linkLibrary(foundation_libc_dep.artifact("foundation")); + + lwip_lib.addIncludePath(b.path("src/include")); + lwip_lib.addIncludePath(lwip_dep.path("src/include")); + + firmware.app_mod.linkLibrary(lwip_lib); + + const lwip_translate_c = b.addTranslateC(.{ + .root_source_file = b.path("src/include/lwip.h"), + .target = resolved_zig_target, + .optimize = optimize, + .link_libc = false, + }); + lwip_translate_c.addIncludePath(b.path("src/include")); + lwip_translate_c.addIncludePath(lwip_dep.path("src/include")); + + firmware.app_mod.addImport("lwip", lwip_translate_c.createModule()); + } + // `installFirmware()` is the MicroZig pendant to `Build.installArtifact()` // and allows installing the firmware as a typical firmware file. // @@ -69,90 +109,9 @@ pub fn build(b: *std.Build) void { mb.install_firmware(firmware, .{ .format = .elf }); } } - - for (targeted_examples) |targeted_example| { - // If we specify example, only select the ones that match - if (maybe_example) |selected_example| - if (!std.mem.containsAtLeast(u8, targeted_example.example.name, 1, selected_example)) - continue; - - const target_desc = targeted_example.target.get_target_desc(mb); - const example = targeted_example.example; - - // `add_firmware` basically works like addExecutable, but takes a - // `microzig.Target` for target instead of a `std.zig.CrossTarget`. - // - // The target will convey all necessary information on the chip, - // cpu and potentially the board as well. - const firmware = mb.add_firmware(.{ - .name = b.fmt("{s}_{s}", .{ target_desc.prefix, example.name }), - .target = target_desc.target, - .optimize = optimize, - .root_source_file = b.path(example.file), - }); - - if (example.features.lwip) { - const resolved_zig_target = b.resolveTargetQuery(firmware.target.zig_target); - - const foundation_libc_dep = b.dependency("foundation_libc", .{ - .optimize = optimize, - .target = resolved_zig_target, - }); - const lwip_dep = b.dependency("lwip", .{}); - - const lwip_lib = b.addLibrary(.{ - .name = "lwip", - .root_module = b.createModule(.{ - .optimize = optimize, - .target = resolved_zig_target, - .link_libc = false, - }), - .linkage = .static, - }); - - lwip_lib.addCSourceFiles(.{ - .root = lwip_dep.path("src"), - .files = &lwip_files, - .flags = &lwip_flags, - }); - - lwip_lib.linkLibrary(foundation_libc_dep.artifact("foundation")); - - lwip_lib.addIncludePath(b.path("src/include")); - lwip_lib.addIncludePath(lwip_dep.path("src/include")); - - firmware.app_mod.linkLibrary(lwip_lib); - - const lwip_translate_c = b.addTranslateC(.{ - .root_source_file = b.path("src/include/lwip.h"), - .target = resolved_zig_target, - .optimize = optimize, - .link_libc = false, - }); - lwip_translate_c.addIncludePath(b.path("src/include")); - lwip_translate_c.addIncludePath(lwip_dep.path("src/include")); - - firmware.app_mod.addImport("lwip", lwip_translate_c.createModule()); - } - - // `installFirmware()` is the MicroZig pendant to `Build.installArtifact()` - // and allows installing the firmware as a typical firmware file. - // - // This will also install into `$prefix/firmware` instead of `$prefix/bin`. - mb.install_firmware(firmware, .{}); - - // For debugging, we also always install the firmware as an ELF file - mb.install_firmware(firmware, .{ .format = .elf }); - } } const TargetEnum = enum { - const all: []TargetEnum = &.{ - .esp32_c3, - .esp32_c3_direct_boot, - .esp32_c3_flashless, - }; - esp32_c3, esp32_c3_direct_boot, esp32_c3_flashless, @@ -182,6 +141,7 @@ const TargetDescription = struct { const Example = struct { const Features = packed struct { + flashless: bool = true, lwip: bool = false, }; @@ -190,11 +150,6 @@ const Example = struct { features: Features = .{}, }; -const TargetedExample = struct { - target: TargetEnum, - example: Example, -}; - const lwip_flags = [_][]const u8{ "-std=c99", "-fno-sanitize=undefined" }; const lwip_files = [_][]const u8{ // Core files diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index 7bf80305a..062e6f816 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -3,7 +3,7 @@ const microzig = @import("microzig"); const SPSC_Queue = microzig.concurrency.SPSC_Queue; const interrupt = microzig.cpu.interrupt; const hal = microzig.hal; -const Scheduler = hal.Scheduler; +const RTOS = hal.RTOS; const radio = hal.radio; const usb_serial_jtag = hal.usb_serial_jtag; @@ -31,31 +31,24 @@ pub const microzig_options: microzig.Options = .{ }, .logFn = usb_serial_jtag.logger.log, .interrupts = .{ - .interrupt29 = .{ .c = radio.interrupt_handlers.wifi_xxx }, - .interrupt30 = .{ .c = Scheduler.generic_interrupt_handler }, - .interrupt31 = .{ .naked = Scheduler.isr_yield_handler }, + .interrupt29 = .{ .c = radio.interrupt_handler }, + .interrupt30 = .{ .c = RTOS.general_purpose_interrupt_handler }, + .interrupt31 = .{ .naked = RTOS.yield_handler }, }, .cpu = .{ .interrupt_stack_size = 4096, }, - .hal = .{ - .radio = .{ - .wifi_interrupt = .interrupt29, - }, - }, }; var buffer: [50 * 1024]u8 = undefined; -var scheduler: Scheduler = undefined; +var rtos: RTOS = undefined; pub fn main() !void { - // var fba: std.heap.FixedBufferAllocator = .init(&buffer); - // const allocator = fba.threadSafeAllocator(); - var alloc = microzig.Allocator.init_with_buffer(&buffer); - const allocator = alloc.allocator(); - scheduler.init(allocator); + var heap_allocator: microzig.Allocator = .init_with_buffer(&buffer); + const gpa = heap_allocator.allocator(); + rtos.init(gpa); - try radio.init(allocator, &scheduler); + try radio.init(gpa, &rtos); defer radio.deinit(); try radio.wifi.init(); @@ -102,8 +95,6 @@ pub fn main() !void { while (radio.wifi.recv_packet(.sta)) |packet| { defer packet.deinit(); - std.log.info("packet received", .{}); - const maybe_pbuf: ?*c.struct_pbuf = c.pbuf_alloc(c.PBUF_RAW, @intCast(packet.data.len), c.PBUF_POOL); if (maybe_pbuf) |pbuf| { _ = c.pbuf_take(pbuf, packet.data.ptr, @intCast(packet.data.len)); @@ -119,8 +110,8 @@ pub fn main() !void { const now = hal.time.get_time_since_boot(); if (!now.diff(last_mem_show).less_than(.from_ms(1000))) { - const used_mem = 50 * 1024 - alloc.free_heap(); - std.log.info("used memory: {}K ({})", .{ used_mem / 1024, used_mem }); + const free_heap = heap_allocator.free_heap(); + std.log.info("free memory: {}K ({})", .{ free_heap / 1024, free_heap }); last_mem_show = now; } } diff --git a/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld index 78d0ddb2f..f147f586d 100644 --- a/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld @@ -29,8 +29,6 @@ SECTIONS KEEP(*(.ram_text)) KEEP(*(.ram_vectors)) - *(.rwtext*) - /* wifi rwtext */ *(.wifi0iram .wifi0iram.*) *(.wifirxiram .wifirxiram.*) @@ -65,11 +63,16 @@ SECTIONS .bss (NOLOAD) : { microzig_bss_start = .; - *(.bss*) *(.sbss*) + *(.bss*) microzig_bss_end = .; } > DRAM + .tbss (NOLOAD) : + { + *(.tbss*) + } > DRAM + .heap (NOLOAD) : { microzig_heap_start = .; diff --git a/port/espressif/esp/ld/esp32_c3/flashless_sections.ld b/port/espressif/esp/ld/esp32_c3/flashless_sections.ld index 987fd2991..05b10e218 100644 --- a/port/espressif/esp/ld/esp32_c3/flashless_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/flashless_sections.ld @@ -9,8 +9,6 @@ SECTIONS KEEP(*(.ram_text)) KEEP(*(.ram_vectors)) - *(.rwtext*) - /* wifi rwtext */ *(.wifi0iram .wifi0iram.*) *(.wifirxiram .wifirxiram.*) @@ -29,7 +27,9 @@ SECTIONS .data _dram_start : AT(_iram_size) { microzig_data_start = .; + *(.srodata*) *(.sdata*) + *(.rodata*) *(.data*) /* wifi rodata */ @@ -37,9 +37,6 @@ SECTIONS /* wifi data */ *(.dram1 .dram1.*) - - *(.rodata*) - *(.srodata*) } > DRAM .bss (NOLOAD) : @@ -50,6 +47,11 @@ SECTIONS microzig_bss_end = .; } > DRAM + .tbss : + { + *(.tbss*) + } > DRAM + .heap (NOLOAD) : { microzig_heap_start = .; diff --git a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld index 1afedad5c..5570e9165 100644 --- a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld @@ -103,7 +103,14 @@ SECTIONS .tbss (NOLOAD) : { *(.tbss*) - } + } > DRAM + + .heap (NOLOAD) : + { + microzig_heap_start = .; + . = ORIGIN(DRAM) + LENGTH(DRAM); + microzig_heap_end = .; + } > DRAM PROVIDE(__global_pointer$ = microzig_data_start + 0x800); } diff --git a/port/espressif/esp/src/hal.zig b/port/espressif/esp/src/hal.zig index fab329ce4..c68b32f4a 100644 --- a/port/espressif/esp/src/hal.zig +++ b/port/espressif/esp/src/hal.zig @@ -3,7 +3,7 @@ const microzig = @import("microzig"); pub const esp_image = @import("esp_image"); -pub const Scheduler = @import("hal/Scheduler.zig"); +pub const RTOS = @import("hal/RTOS.zig"); pub const cache = @import("hal/cache.zig"); pub const clocks = @import("hal/clocks.zig"); pub const compatibility = @import("hal/compatibility.zig"); @@ -25,8 +25,6 @@ pub const usb_serial_jtag = @import("hal/usb_serial_jtag.zig"); comptime { // export atomic intrinsics _ = @import("hal/atomic.zig"); - - _ = esp_app_desc; } pub const HAL_Options = struct { @@ -35,11 +33,12 @@ pub const HAL_Options = struct { secure_version: u32 = 0, version: []const u8 = "0.0.0", } = .{}, - radio: ?radio.Options = null, + rtos: RTOS.Options = .{}, + radio: radio.Options = .{}, }; /// Clock config applied by the default `init()` function of the hal. -pub const clock_config: clocks.Config = .init_comptime(160_000_000); +pub const clock_config: clocks.Config = .init_comptime(80_000_000); pub fn init() void { init_sequence(clock_config); diff --git a/port/espressif/esp/src/hal/Scheduler.zig b/port/espressif/esp/src/hal/RTOS.zig similarity index 71% rename from port/espressif/esp/src/hal/Scheduler.zig rename to port/espressif/esp/src/hal/RTOS.zig index e6ac4c053..58a14d248 100644 --- a/port/espressif/esp/src/hal/Scheduler.zig +++ b/port/espressif/esp/src/hal/RTOS.zig @@ -9,9 +9,12 @@ const TrapFrame = microzig.cpu.TrapFrame; const SYSTEM = microzig.chip.peripherals.SYSTEM; const time = microzig.drivers.time; +const system = @import("system.zig"); const systimer = @import("systimer.zig"); const get_time_since_boot = @import("time.zig").get_time_since_boot; +const rtos_options = microzig.options.hal.rtos; + // How it works? // For simple task to task context switches, only necessary registers are // saved. But if a higher priority task becomes available during the handling @@ -33,24 +36,12 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; // NOTE: don't use anonymous structs for reinitializing state unions with // previous union state to avoid compiler bug (0.15.2) -comptime { - if (microzig.options.cpu.interrupt_stack_size == null) - @compileError("Please enable interrupt stacks to use the scheduler"); -} - const STACK_ALIGN: std.mem.Alignment = .@"16"; const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 31 * @sizeOf(usize)); -// TODO: configurable -const generic_interrupt: microzig.cpu.Interrupt = .interrupt30; -// TODO: configurable -const yield_interrupt: microzig.cpu.Interrupt = .interrupt31; -const systimer_unit = systimer.unit(0); -const systimer_alarm = systimer.alarm(0); - -const Scheduler = @This(); +const RTOS = @This(); -var maybe_instance: ?*Scheduler = null; +var maybe_instance: ?*RTOS = null; gpa: Allocator, @@ -80,10 +71,23 @@ pub const Priority = enum(u8) { } }; -pub fn init(scheduler: *Scheduler, gpa: Allocator) void { +pub const Options = struct { + general_purpose_interrupt: microzig.cpu.Interrupt = .interrupt30, + systimer_unit: systimer.Unit = .unit0, + systimer_alarm: systimer.Alarm = .alarm0, + cpu_interrupt: system.CpuInterrupt = .cpu_interrupt_0, + yield_interrupt: microzig.cpu.Interrupt = .interrupt31, +}; + +pub fn init(rtos: *RTOS, gpa: Allocator) void { + comptime { + if (microzig.options.cpu.interrupt_stack_size == null) + @compileError("Please enable interrupt stacks to use the rtos"); + } + assert(maybe_instance == null); - scheduler.* = .{ + rtos.* = .{ .gpa = gpa, .main_task = .{ .context = undefined, @@ -93,34 +97,37 @@ pub fn init(scheduler: *Scheduler, gpa: Allocator) void { .idle_task = .{ .context = .{ .pc = @intFromPtr(&idle), - .sp = @intFromPtr(scheduler.idle_stack[scheduler.idle_stack.len..].ptr), + .sp = @intFromPtr(rtos.idle_stack[rtos.idle_stack.len..].ptr), .fp = 0, }, - .stack = &scheduler.idle_stack, + .stack = &rtos.idle_stack, .priority = .idle, }, - .current_task = &scheduler.main_task, + .current_task = &rtos.main_task, }; - scheduler.ready(&scheduler.idle_task, .{}); + rtos.ready(&rtos.idle_task, .{}); - maybe_instance = scheduler; + maybe_instance = rtos; - microzig.cpu.interrupt.map(.from_cpu_intr0, yield_interrupt); - microzig.cpu.interrupt.set_type(yield_interrupt, .level); - microzig.cpu.interrupt.set_priority(yield_interrupt, .lowest); - microzig.cpu.interrupt.enable(yield_interrupt); + microzig.cpu.interrupt.map(rtos_options.cpu_interrupt.source(), rtos_options.yield_interrupt); + microzig.cpu.interrupt.set_type(rtos_options.yield_interrupt, .level); + microzig.cpu.interrupt.set_priority(rtos_options.yield_interrupt, .lowest); + microzig.cpu.interrupt.enable(rtos_options.yield_interrupt); // unit0 is already enabled as it is used by `hal.time`. - systimer_alarm.set_unit(systimer_unit); - systimer_alarm.set_mode(.target); - systimer_alarm.set_enabled(false); - systimer_alarm.set_interrupt_enabled(true); - - microzig.cpu.interrupt.map(.systimer_target0, generic_interrupt); - microzig.cpu.interrupt.set_type(generic_interrupt, .level); - microzig.cpu.interrupt.set_priority(generic_interrupt, .lowest); - microzig.cpu.interrupt.enable(generic_interrupt); + if (rtos_options.systimer_unit != .unit0) { + rtos_options.systimer_unit.apply(.enabled); + } + rtos_options.systimer_alarm.set_unit(rtos_options.systimer_unit); + rtos_options.systimer_alarm.set_mode(.target); + rtos_options.systimer_alarm.set_enabled(false); + rtos_options.systimer_alarm.set_interrupt_enabled(true); + + microzig.cpu.interrupt.map(.systimer_target0, rtos_options.general_purpose_interrupt); + microzig.cpu.interrupt.set_type(rtos_options.general_purpose_interrupt, .level); + microzig.cpu.interrupt.set_priority(rtos_options.general_purpose_interrupt, .lowest); + microzig.cpu.interrupt.enable(rtos_options.general_purpose_interrupt); } // TODO: deinit @@ -140,7 +147,7 @@ pub const SpawnOptions = struct { }; pub fn spawn( - scheduler: *Scheduler, + rtos: *RTOS, comptime function: anytype, args: std.meta.ArgsTuple(@TypeOf(function)), options: SpawnOptions, @@ -150,7 +157,7 @@ pub fn spawn( const TypeErased = struct { fn call() callconv(.c) void { - const sched = maybe_instance orelse @panic("no active scheduler"); + const sched = maybe_instance orelse @panic("no active rtos"); const context_ptr: *const Args = @ptrFromInt(args_align.forward(@intFromPtr(sched.current_task) + @sizeOf(Task))); @@ -170,11 +177,11 @@ pub fn spawn( const stack_end = STACK_ALIGN.forward(stack_start + options.stack_size + EXTRA_STACK_SIZE); const alloc_size = stack_end; - const raw_alloc = try scheduler.gpa.alignedAlloc(u8, alloc_align, alloc_size); + const raw_alloc = try rtos.gpa.alignedAlloc(u8, alloc_align, alloc_size); const task: *Task = @ptrCast(@alignCast(raw_alloc)); - const task_args: *Args = @alignCast(@ptrCast(raw_alloc[args_start..][0..@sizeOf(Args)])); + const task_args: *Args = @ptrCast(@alignCast(raw_alloc[args_start..][0..@sizeOf(Args)])); task_args.* = args; const stack: []u8 = raw_alloc[stack_start..stack_end]; @@ -192,7 +199,7 @@ pub fn spawn( const cs = enter_critical_section(); defer cs.leave(); - scheduler.ready(task, .{}); + rtos.ready(task, .{}); return task; } @@ -206,30 +213,30 @@ pub const YieldAction = union(enum) { delete, }; -pub inline fn yield(scheduler: *Scheduler, action: YieldAction) void { +pub inline fn yield(rtos: *RTOS, action: YieldAction) void { const cs = enter_critical_section(); - scheduler.yield_and_leave_cs(action, cs); + rtos.yield_and_leave_cs(action, cs); } /// Must be called inside critical section. Calling leave on the critical /// section becomes unnecessary. -pub inline fn yield_and_leave_cs(scheduler: *Scheduler, action: YieldAction, cs: CriticalSection) void { +pub inline fn yield_and_leave_cs(rtos: *RTOS, action: YieldAction, cs: CriticalSection) void { defer if (!cs.enable_on_leave) { microzig.cpu.interrupt.disable_interrupts(); }; - const current_task, const next_task = scheduler.yield_inner(action); + const current_task, const next_task = rtos.yield_inner(action); context_switch(¤t_task.context, &next_task.context); } -fn yield_inner(scheduler: *Scheduler, action: YieldAction) struct { *Task, *Task } { - const current_task = scheduler.current_task; +fn yield_inner(rtos: *RTOS, action: YieldAction) struct { *Task, *Task } { + const current_task = rtos.current_task; switch (action) { .reschedule => { current_task.state = .{ .ready = .{} }; - scheduler.ready_queue.put(current_task); + rtos.ready_queue.put(current_task); }, .wait => |wait_action| { - assert(current_task != &scheduler.idle_task); + assert(current_task != &rtos.idle_task); if (wait_action.wait_queue) |wait_queue| { assert(current_task.wait_queue == null); @@ -238,28 +245,28 @@ fn yield_inner(scheduler: *Scheduler, action: YieldAction) struct { *Task, *Task } if (wait_action.timeout) |timeout| { - scheduler.schedule_wake_at(current_task, timeout); + rtos.schedule_wake_at(current_task, timeout); } else { current_task.state = .suspended; } }, .delete => { - assert(current_task != &scheduler.idle_task and current_task != &scheduler.main_task); + assert(current_task != &rtos.idle_task and current_task != &rtos.main_task); current_task.state = .scheduled_for_deletion; - scheduler.scheduled_for_deletion_list.append(¤t_task.node); + rtos.scheduled_for_deletion_list.append(¤t_task.node); }, } - const next_task: *Task = scheduler.ready_queue.pop(null) orelse @panic("No task ready to run!"); + const next_task: *Task = rtos.ready_queue.pop(null) orelse @panic("No task ready to run!"); next_task.state = Task.State{ .running = next_task.state.ready }; - scheduler.current_task = next_task; + rtos.current_task = next_task; return .{ current_task, next_task }; } -pub fn wake_from_wait_queue(scheduler: *Scheduler, wait_queue: *PriorityWaitQueue, how_many: enum(usize) { +pub fn wake_from_wait_queue(rtos: *RTOS, wait_queue: *PriorityWaitQueue, how_many: enum(usize) { one = 1, all = std.math.maxInt(usize), _, @@ -273,20 +280,20 @@ pub fn wake_from_wait_queue(scheduler: *Scheduler, wait_queue: *PriorityWaitQueu task.wait_queue = null; switch (task.state) { - .alarm_set => |_| scheduler.timer_queue.remove(&task.node), + .alarm_set => |_| rtos.timer_queue.remove(&task.node), .suspended => {}, else => @panic("invalid state for waiting task"), } - scheduler.ready(task, .{}); + rtos.ready(task, .{}); } } -pub fn sleep(scheduler: *Scheduler, duration: time.Duration) void { - scheduler.yield(.{ .wait = .{ +pub fn sleep(rtos: *RTOS, duration: time.Duration) void { + rtos.yield(.{ .wait = .{ .timeout = .after(duration), } }); - assert(scheduler.current_task.state.running.timeout); + assert(rtos.current_task.state.running.timeout); } inline fn context_switch(prev_context: *Context, next_context: *Context) void { @@ -344,19 +351,17 @@ inline fn context_switch(prev_context: *Context, next_context: *Context) void { } pub fn yield_from_isr() void { - SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ - .CPU_INTR_FROM_CPU_0 = 1, - }); + rtos_options.cpu_interrupt.set_pending(true); } -pub fn is_a_higher_priority_task_ready(scheduler: *Scheduler) bool { - return if (scheduler.ready_queue.peek_top()) |top_ready_task| - top_ready_task.priority.is_greater(scheduler.current_task.priority) +pub fn is_a_higher_priority_task_ready(rtos: *RTOS) bool { + return if (rtos.ready_queue.peek_top()) |top_ready_task| + top_ready_task.priority.is_greater(rtos.current_task.priority) else false; } -pub fn isr_yield_handler() linksection(".ram_vectors") callconv(.naked) void { +pub fn yield_handler() linksection(".ram_vectors") callconv(.naked) void { comptime { assert(@sizeOf(Context) == 3 * @sizeOf(usize)); } @@ -488,14 +493,12 @@ pub fn isr_yield_handler() linksection(".ram_vectors") callconv(.naked) void { } fn schedule_in_isr(context: *Context) linksection(".ram_vectors") callconv(.c) void { - const scheduler = maybe_instance orelse @panic("no active scheduler"); + const rtos = maybe_instance orelse @panic("no active rtos"); - SYSTEM.CPU_INTR_FROM_CPU_0.write(.{ - .CPU_INTR_FROM_CPU_0 = 0, - }); + rtos_options.cpu_interrupt.set_pending(false); - const current_task = scheduler.current_task; - const ready_task = scheduler.ready_queue.pop(scheduler.current_task.priority) orelse return; + const current_task = rtos.current_task; + const ready_task = rtos.ready_queue.pop(rtos.current_task.priority) orelse return; // swap contexts current_task.context = context.*; @@ -503,40 +506,40 @@ fn schedule_in_isr(context: *Context) linksection(".ram_vectors") callconv(.c) v // keep the state until the next yield current_task.state = Task.State{ .ready = current_task.state.running }; - scheduler.ready_queue.put(current_task); + rtos.ready_queue.put(current_task); ready_task.state = Task.State{ .running = ready_task.state.ready }; - scheduler.current_task = ready_task; + rtos.current_task = ready_task; } /// Must be called from a critical section. -fn schedule_wake_at(scheduler: *Scheduler, sleeping_task: *Task, ticks: TimerTicks) void { +fn schedule_wake_at(rtos: *RTOS, sleeping_task: *Task, ticks: TimerTicks) void { sleeping_task.state = .{ .alarm_set = ticks }; - var maybe_node = scheduler.timer_queue.first; + var maybe_node = rtos.timer_queue.first; while (maybe_node) |node| : (maybe_node = node.next) { const task: *Task = @alignCast(@fieldParentPtr("node", node)); if (ticks.is_before(task.state.alarm_set)) { - scheduler.timer_queue.insertBefore(&task.node, &sleeping_task.node); + rtos.timer_queue.insertBefore(&task.node, &sleeping_task.node); break; } } else { - scheduler.timer_queue.append(&sleeping_task.node); + rtos.timer_queue.append(&sleeping_task.node); } // If we updated the first element of the list, it means that we have to // reschedule the timer - if (scheduler.timer_queue.first == &sleeping_task.node) { - systimer_alarm.set_target(@intFromEnum(ticks)); - systimer_alarm.set_enabled(true); + if (rtos.timer_queue.first == &sleeping_task.node) { + rtos_options.systimer_alarm.set_target(@intFromEnum(ticks)); + rtos_options.systimer_alarm.set_enabled(true); if (ticks.is_reached()) - scheduler.sweep_timer_queue_for_timeouts(); + rtos.sweep_timer_queue_for_timeouts(); } } -pub fn generic_interrupt_handler(_: *TrapFrame) callconv(.c) void { - const scheduler = maybe_instance orelse @panic("no active scheduler"); +pub fn general_purpose_interrupt_handler(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { + const rtos = maybe_instance orelse @panic("no active rtos"); var iter: microzig.cpu.interrupt.SourceIterator = .init(); while (iter.next()) |source| { @@ -545,24 +548,24 @@ pub fn generic_interrupt_handler(_: *TrapFrame) callconv(.c) void { const cs = enter_critical_section(); defer cs.leave(); - systimer_alarm.clear_interrupt(); + rtos_options.systimer_alarm.clear_interrupt(); - scheduler.sweep_timer_queue_for_timeouts(); + rtos.sweep_timer_queue_for_timeouts(); }, else => {}, } } - if (scheduler.is_a_higher_priority_task_ready()) { + if (rtos.is_a_higher_priority_task_ready()) { yield_from_isr(); } } -fn sweep_timer_queue_for_timeouts(scheduler: *Scheduler) void { - while (scheduler.timer_queue.popFirst()) |node| { +fn sweep_timer_queue_for_timeouts(rtos: *RTOS) void { + while (rtos.timer_queue.popFirst()) |node| { const task: *Task = @alignCast(@fieldParentPtr("node", node)); if (!task.state.alarm_set.is_reached()) { - scheduler.timer_queue.prepend(&task.node); + rtos.timer_queue.prepend(&task.node); break; } @@ -571,22 +574,22 @@ fn sweep_timer_queue_for_timeouts(scheduler: *Scheduler) void { task.wait_queue = null; } - scheduler.ready(task, .{ .timeout = true }); + rtos.ready(task, .{ .timeout = true }); } - if (scheduler.timer_queue.first) |node| { + if (rtos.timer_queue.first) |node| { const task: *Task = @alignCast(@fieldParentPtr("node", node)); - systimer_alarm.set_target(@intFromEnum(task.state.alarm_set)); - systimer_alarm.set_enabled(true); + rtos_options.systimer_alarm.set_target(@intFromEnum(task.state.alarm_set)); + rtos_options.systimer_alarm.set_enabled(true); } else { - systimer_alarm.set_enabled(false); + rtos_options.systimer_alarm.set_enabled(false); } } -fn ready(scheduler: *Scheduler, task: *Task, flags: Task.ReadyFlags) void { +fn ready(rtos: *RTOS, task: *Task, flags: Task.ReadyFlags) void { assert(task.state != .ready); task.state = .{ .ready = flags }; - scheduler.ready_queue.put(task); + rtos.ready_queue.put(task); } pub const Task = struct { @@ -597,7 +600,7 @@ pub const Task = struct { /// What is the deal with this task right now? state: State = .none, - /// Node used for scheduler internal lists. + /// Node used for rtos internal lists. node: std.DoublyLinkedList.Node = .{}, /// Task specific semaphore (required by the wifi driver) @@ -725,11 +728,11 @@ pub const Mutex = struct { unlocked, }; - pub fn lock(mutex: *Mutex, scheduler: *Scheduler) void { - lock_with_timeout(mutex, scheduler, null) catch unreachable; + pub fn lock(mutex: *Mutex, rtos: *RTOS) void { + lock_with_timeout(mutex, rtos, null) catch unreachable; } - pub fn lock_with_timeout(mutex: *Mutex, scheduler: *Scheduler, maybe_timeout: ?time.Duration) TimeoutError!void { + pub fn lock_with_timeout(mutex: *Mutex, rtos: *RTOS, maybe_timeout: ?time.Duration) TimeoutError!void { const cs = enter_critical_section(); defer cs.leave(); @@ -739,25 +742,25 @@ pub const Mutex = struct { null; while (mutex.state != .unlocked) { - scheduler.yield(.{ .wait = .{ + rtos.yield(.{ .wait = .{ .timeout = maybe_timeout_ticks, .wait_queue = &mutex.wait_queue, } }); - if (scheduler.current_task.state.running.timeout) + if (rtos.current_task.state.running.timeout) return error.Timeout; } mutex.state = .locked; } - pub fn unlock(mutex: *Mutex, scheduler: *Scheduler) void { + pub fn unlock(mutex: *Mutex, rtos: *RTOS) void { const cs = enter_critical_section(); - defer scheduler.yield_and_leave_cs(.reschedule, cs); + defer rtos.yield_and_leave_cs(.reschedule, cs); assert(mutex.state == .locked); mutex.state = .unlocked; - scheduler.wake_from_wait_queue(&mutex.wait_queue, .one); + rtos.wake_from_wait_queue(&mutex.wait_queue, .one); } }; @@ -768,11 +771,11 @@ pub const RecursiveMutex = struct { owning_task: ?*Task = null, wait_queue: PriorityWaitQueue = .{}, - pub fn lock(mutex: *RecursiveMutex, scheduler: *Scheduler) void { + pub fn lock(mutex: *RecursiveMutex, rtos: *RTOS) void { const cs = enter_critical_section(); defer cs.leave(); - const current_task = scheduler.current_task; + const current_task = rtos.current_task; if (mutex.owning_task == current_task) { assert(mutex.recursive); @@ -782,11 +785,11 @@ pub const RecursiveMutex = struct { // if (mutex.owning_task) |owning_task| { // // if (current_task.priority.is_greater(owning_task.priority)) { - // // scheduler.change_task_priority_from_cs(owning_task, current_task.priority, cs); + // // rtos.change_task_priority_from_cs(owning_task, current_task.priority, cs); // // } // while (mutex.owning_task != null) { - scheduler.yield(.{ .wait = .{ + rtos.yield(.{ .wait = .{ .wait_queue = &mutex.wait_queue, } }); } @@ -797,16 +800,16 @@ pub const RecursiveMutex = struct { mutex.owning_task = current_task; } - pub fn unlock(mutex: *RecursiveMutex, scheduler: *Scheduler) bool { + pub fn unlock(mutex: *RecursiveMutex, rtos: *RTOS) bool { const cs = enter_critical_section(); assert(mutex.value > 0); mutex.value -= 1; if (mutex.value <= 0) { - defer scheduler.yield_and_leave_cs(.reschedule, cs); + defer rtos.yield_and_leave_cs(.reschedule, cs); mutex.owning_task = null; - scheduler.wake_from_wait_queue(&mutex.wait_queue, .one); + rtos.wake_from_wait_queue(&mutex.wait_queue, .one); return true; } else { @@ -826,11 +829,11 @@ pub const Semaphore = struct { }; } - pub fn take(sem: *Semaphore, scheduler: *Scheduler) void { - sem.take_with_timeout(scheduler, null) catch unreachable; + pub fn take(sem: *Semaphore, rtos: *RTOS) void { + sem.take_with_timeout(rtos, null) catch unreachable; } - pub fn take_with_timeout(sem: *Semaphore, scheduler: *Scheduler, maybe_timeout: ?time.Duration) TimeoutError!void { + pub fn take_with_timeout(sem: *Semaphore, rtos: *RTOS, maybe_timeout: ?time.Duration) TimeoutError!void { const cs = enter_critical_section(); defer cs.leave(); @@ -840,24 +843,24 @@ pub const Semaphore = struct { null; while (sem.value <= 0) { - scheduler.yield(.{ .wait = .{ + rtos.yield(.{ .wait = .{ .timeout = maybe_timeout_ticks, .wait_queue = &sem.wait_queue, } }); - if (scheduler.current_task.state.running.timeout) + if (rtos.current_task.state.running.timeout) return error.Timeout; } sem.value -= 1; } - pub fn give(sem: *Semaphore, scheduler: *Scheduler) void { + pub fn give(sem: *Semaphore, rtos: *RTOS) void { const cs = enter_critical_section(); - defer scheduler.yield_and_leave_cs(.reschedule, cs); + defer rtos.yield_and_leave_cs(.reschedule, cs); sem.value += 1; - scheduler.wake_from_wait_queue(&sem.wait_queue, .one); + rtos.wake_from_wait_queue(&sem.wait_queue, .one); } }; @@ -882,7 +885,7 @@ pub const TypeErasedQueue = struct { pub fn put( q: *TypeErasedQueue, - scheduler: *Scheduler, + rtos: *RTOS, elements: []const u8, min: usize, maybe_timeout: ?time.Duration, @@ -891,7 +894,7 @@ pub const TypeErasedQueue = struct { if (elements.len == 0) return 0; const cs = enter_critical_section(); - defer scheduler.yield_and_leave_cs(.reschedule, cs); + defer rtos.yield_and_leave_cs(.reschedule, cs); const maybe_timeout_ticks: ?TimerTicks = if (maybe_timeout) |timeout| .after(timeout) @@ -901,26 +904,26 @@ pub const TypeErasedQueue = struct { var n: usize = 0; while (true) { - n += q.put_non_blocking_from_cs(scheduler, elements[n..]); + n += q.put_non_blocking_from_cs(rtos, elements[n..]); if (n >= min) return n; - scheduler.yield(.{ .wait = .{ + rtos.yield(.{ .wait = .{ .timeout = maybe_timeout_ticks, .wait_queue = &q.putters, } }); - if (scheduler.current_task.state.running.timeout) + if (rtos.current_task.state.running.timeout) return n; } } - pub fn put_non_blocking(q: *TypeErasedQueue, scheduler: *Scheduler, elements: []const u8) usize { + pub fn put_non_blocking(q: *TypeErasedQueue, rtos: *RTOS, elements: []const u8) usize { const cs = enter_critical_section(); defer cs.leave(); - return q.put_non_blocking_from_cs(scheduler, elements); + return q.put_non_blocking_from_cs(rtos, elements); } - fn put_non_blocking_from_cs(q: *TypeErasedQueue, scheduler: *Scheduler, elements: []const u8) usize { + fn put_non_blocking_from_cs(q: *TypeErasedQueue, rtos: *RTOS, elements: []const u8) usize { var n: usize = 0; while (q.puttable_slice()) |slice| { const copy_len = @min(slice.len, elements.len - n); @@ -930,7 +933,7 @@ pub const TypeErasedQueue = struct { n += copy_len; if (n == elements.len) break; } - if (n > 0) scheduler.wake_from_wait_queue(&q.getters, .one); + if (n > 0) rtos.wake_from_wait_queue(&q.getters, .one); return n; } @@ -946,7 +949,7 @@ pub const TypeErasedQueue = struct { pub fn get( q: *TypeErasedQueue, - scheduler: *Scheduler, + rtos: *RTOS, buffer: []u8, min: usize, maybe_timeout: ?time.Duration, @@ -955,7 +958,7 @@ pub const TypeErasedQueue = struct { if (buffer.len == 0) return 0; const cs = enter_critical_section(); - defer scheduler.yield_and_leave_cs(.reschedule, cs); + defer rtos.yield_and_leave_cs(.reschedule, cs); const maybe_timeout_ticks: ?TimerTicks = if (maybe_timeout) |timeout| .after(timeout) @@ -965,26 +968,26 @@ pub const TypeErasedQueue = struct { var n: usize = 0; while (true) { - n += q.get_non_blocking_from_cs(scheduler, buffer[n..]); + n += q.get_non_blocking_from_cs(rtos, buffer[n..]); if (n >= min) return n; - scheduler.yield(.{ .wait = .{ + rtos.yield(.{ .wait = .{ .timeout = maybe_timeout_ticks, .wait_queue = &q.getters, } }); - if (scheduler.current_task.state.running.timeout) + if (rtos.current_task.state.running.timeout) return n; } } - pub fn get_non_blocking(q: *TypeErasedQueue, scheduler: *Scheduler, buffer: []u8) usize { + pub fn get_non_blocking(q: *TypeErasedQueue, rtos: *RTOS, buffer: []u8) usize { const cs = enter_critical_section(); defer cs.leave(); - return q.get_non_blocking_from_cs(scheduler, buffer); + return q.get_non_blocking_from_cs(rtos, buffer); } - fn get_non_blocking_from_cs(q: *TypeErasedQueue, scheduler: *Scheduler, buffer: []u8) usize { + fn get_non_blocking_from_cs(q: *TypeErasedQueue, rtos: *RTOS, buffer: []u8) usize { var n: usize = 0; while (q.gettable_slice()) |slice| { const copy_len = @min(slice.len, buffer.len - n); @@ -996,7 +999,7 @@ pub const TypeErasedQueue = struct { n += copy_len; if (n == buffer.len) break; } - if (n > 0) scheduler.wake_from_wait_queue(&q.putters, .one); + if (n > 0) rtos.wake_from_wait_queue(&q.putters, .one); return n; } @@ -1017,39 +1020,39 @@ pub fn Queue(Elem: type) type { return .{ .type_erased = .init(@ptrCast(buffer)) }; } - pub fn close(q: *Self, scheduler: *Scheduler) void { - q.type_erased.close(scheduler); + pub fn close(q: *Self, rtos: *RTOS) void { + q.type_erased.close(rtos); } - pub fn put(q: *Self, scheduler: *Scheduler, elements: []const Elem, min: usize, timeout: ?time.Duration) usize { - return @divExact(q.type_erased.put(scheduler, @ptrCast(elements), min * @sizeOf(Elem), timeout), @sizeOf(Elem)); + pub fn put(q: *Self, rtos: *RTOS, elements: []const Elem, min: usize, timeout: ?time.Duration) usize { + return @divExact(q.type_erased.put(rtos, @ptrCast(elements), min * @sizeOf(Elem), timeout), @sizeOf(Elem)); } - pub fn put_all(q: *Self, scheduler: *Scheduler, elements: []const Elem, timeout: ?time.Duration) TimeoutError!void { - if (q.put(scheduler, elements, elements.len, timeout) != elements.len) + pub fn put_all(q: *Self, rtos: *RTOS, elements: []const Elem, timeout: ?time.Duration) TimeoutError!void { + if (q.put(rtos, elements, elements.len, timeout) != elements.len) return error.Timeout; } - pub fn put_one(q: *Self, scheduler: *Scheduler, item: Elem) TimeoutError!void { - if (q.put(scheduler, &.{item}, 1, null) != 1) + pub fn put_one(q: *Self, rtos: *RTOS, item: Elem) TimeoutError!void { + if (q.put(rtos, &.{item}, 1, null) != 1) return error.Timeout; } - pub fn put_non_blocking(q: *Self, scheduler: *Scheduler, elements: []const Elem) usize { - return @divExact(q.type_erased.put_non_blocking(scheduler, @ptrCast(elements)), @sizeOf(Elem)); + pub fn put_non_blocking(q: *Self, rtos: *RTOS, elements: []const Elem) usize { + return @divExact(q.type_erased.put_non_blocking(rtos, @ptrCast(elements)), @sizeOf(Elem)); } - pub fn put_one_non_blocking(q: *Self, scheduler: *Scheduler, item: Elem) bool { - return q.put_non_blocking(scheduler, @ptrCast(&item)) == 1; + pub fn put_one_non_blocking(q: *Self, rtos: *RTOS, item: Elem) bool { + return q.put_non_blocking(rtos, @ptrCast(&item)) == 1; } - pub fn get(q: *Self, scheduler: *Scheduler, buffer: []Elem, target: usize, timeout: ?time.Duration) usize { - return @divExact(q.type_erased.get(scheduler, @ptrCast(buffer), target * @sizeOf(Elem), timeout), @sizeOf(Elem)); + pub fn get(q: *Self, rtos: *RTOS, buffer: []Elem, target: usize, timeout: ?time.Duration) usize { + return @divExact(q.type_erased.get(rtos, @ptrCast(buffer), target * @sizeOf(Elem), timeout), @sizeOf(Elem)); } - pub fn get_one(q: *Self, scheduler: *Scheduler, timeout: ?time.Duration) TimeoutError!Elem { + pub fn get_one(q: *Self, rtos: *RTOS, timeout: ?time.Duration) TimeoutError!Elem { var buf: [1]Elem = undefined; - if (q.get(scheduler, &buf, 1, timeout) != 1) + if (q.get(rtos, &buf, 1, timeout) != 1) return error.Timeout; return buf[0]; } @@ -1064,7 +1067,7 @@ pub const TimerTicks = enum(u52) { _, pub fn now() TimerTicks { - return @enumFromInt(systimer_unit.read()); + return @enumFromInt(rtos_options.systimer_unit.read()); } pub fn after(duration: time.Duration) TimerTicks { diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index 256c7a066..6be22e857 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -17,25 +17,16 @@ pub const bluetooth = @import("radio/bluetooth.zig"); const osi = @import("radio/osi.zig"); const timer = @import("radio/timer.zig"); pub const wifi = @import("radio/wifi.zig"); -const Scheduler = @import("Scheduler.zig"); +const RTOS = @import("RTOS.zig"); const log = std.log.scoped(.esp_radio); pub const Options = struct { - wifi_interrupt: microzig.cpu.Interrupt, - + interrupt: microzig.cpu.Interrupt = .interrupt29, wifi: wifi.Options = .{}, }; -pub const options = microzig.options.hal.radio orelse - @compileError("Please specify options if you want to use radio."); - -// TODO: We should allow the user to select the scheduling algorithm. Maybe at -// compile time? - -/// Radio uses the official esp drivers. You should enable interrupts -/// after/before this. -pub fn init(allocator: Allocator, scheduler: *Scheduler) Allocator.Error!void { +pub fn init(allocator: Allocator, rtos: *RTOS) Allocator.Error!void { // TODO: check that clock frequency is higher or equal to 80mhz { @@ -46,7 +37,7 @@ pub fn init(allocator: Allocator, scheduler: *Scheduler) Allocator.Error!void { // phy_mem_init(); // only sets some global variable on esp32c3 osi.allocator = allocator; - osi.scheduler = scheduler; + osi.rtos = rtos; setup_interrupts(); } @@ -136,26 +127,17 @@ fn enable_wifi_power_domain_and_init_clocks() void { } fn setup_interrupts() void { - // TODO: which interrupts are used should be configurable. - - microzig.cpu.interrupt.map(.wifi_mac, options.wifi_interrupt); - microzig.cpu.interrupt.map(.wifi_pwr, options.wifi_interrupt); - - microzig.cpu.interrupt.set_type(options.wifi_interrupt, .level); - microzig.cpu.interrupt.set_priority(options.wifi_interrupt, .highest); + const radio_interrupt = microzig.options.hal.radio.interrupt; + microzig.cpu.interrupt.map(.wifi_mac, radio_interrupt); + microzig.cpu.interrupt.map(.wifi_pwr, radio_interrupt); + microzig.cpu.interrupt.set_type(radio_interrupt, .level); + microzig.cpu.interrupt.set_priority(radio_interrupt, .highest); } -pub const interrupt_handlers = struct { - pub fn wifi_xxx(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { - if (osi.wifi_interrupt_handler) |handler| { - log.debug("interrupt WIFI_xxx {} {?}", .{ - handler.f, - handler.arg, - }); - - handler.f(handler.arg); - } else { - // should be unreachable - } +pub fn interrupt_handler(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { + if (osi.wifi_interrupt_handler) |handler| { + handler.f(handler.arg); + } else { + // should be unreachable } -}; +} diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index a4b938f97..6c160d8e1 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -9,7 +9,8 @@ const peripherals = microzig.chip.peripherals; const APB_CTRL = peripherals.APB_CTRL; const hal = microzig.hal; -const Scheduler = @import("../Scheduler.zig"); +const radio = @import("../radio.zig"); +const RTOS = @import("../RTOS.zig"); const systimer = @import("../systimer.zig"); const get_time_since_boot = @import("../time.zig").get_time_since_boot; const timer = @import("timer.zig"); @@ -21,13 +22,15 @@ const c = @import("esp-wifi-driver"); const coex_enabled: bool = false; pub var allocator: std.mem.Allocator = undefined; -pub var scheduler: *Scheduler = undefined; +pub var rtos: *RTOS = undefined; pub var wifi_interrupt_handler: ?struct { f: *const fn (?*anyopaque) callconv(.c) void, arg: ?*anyopaque, } = undefined; +const radio_interrupt = microzig.options.hal.radio.interrupt; + extern fn vsnprintf(buffer: [*c]u8, len: usize, fmt: [*c]const u8, va_list: std.builtin.VaList) callconv(.c) void; const log_esp_wifi_driver_internal = std.log.scoped(.esp_wifi_driver_internal); @@ -143,17 +146,17 @@ pub fn gettimeofday(tv: ?*c.timeval, _: ?*anyopaque) callconv(.c) i32 { } pub fn sleep(time_sec: c_uint) callconv(.c) c_int { - scheduler.sleep(.from_us(time_sec * 1_000_000)); + rtos.sleep(.from_us(time_sec * 1_000_000)); return 0; } pub fn usleep(time_us: u32) callconv(.c) c_int { - scheduler.sleep(.from_us(time_us)); + rtos.sleep(.from_us(time_us)); return 0; } pub fn vTaskDelay(ticks: u32) callconv(.c) void { - scheduler.sleep(.from_us(ticks)); + rtos.sleep(.from_us(ticks)); } comptime { @@ -251,7 +254,7 @@ pub fn ints_on(mask: u32) callconv(.c) void { log.debug("ints_on {}", .{mask}); if (mask == 2) { - microzig.cpu.interrupt.enable(.interrupt29); + microzig.cpu.interrupt.enable(radio_interrupt); } else { @panic("ints_on: not implemented"); } @@ -261,7 +264,7 @@ pub fn ints_off(mask: u32) callconv(.c) void { log.debug("ints_off {}", .{mask}); if (mask == 2) { - microzig.cpu.interrupt.disable(.interrupt29); + microzig.cpu.interrupt.disable(radio_interrupt); } else { @panic("ints_off: not implemented"); } @@ -303,13 +306,13 @@ pub fn wifi_int_restore(mux_ptr: ?*anyopaque, state: u32) callconv(.c) void { pub fn task_yield_from_isr() callconv(.c) void { log.debug("task_yield_from_isr", .{}); - Scheduler.yield_from_isr(); + RTOS.yield_from_isr(); } pub fn semphr_create(max_value: u32, init_value: u32) callconv(.c) ?*anyopaque { log.debug("semphr_create {} {}", .{ max_value, init_value }); - const sem = allocator.create(Scheduler.Semaphore) catch { + const sem = allocator.create(RTOS.Semaphore) catch { log.warn("failed to allocate semaphore", .{}); return null; }; @@ -323,18 +326,18 @@ pub fn semphr_create(max_value: u32, init_value: u32) callconv(.c) ?*anyopaque { pub fn semphr_delete(ptr: ?*anyopaque) callconv(.c) void { log.debug("semphr_delete {?}", .{ptr}); - allocator.destroy(@as(*Scheduler.Semaphore, @ptrCast(@alignCast(ptr)))); + allocator.destroy(@as(*RTOS.Semaphore, @ptrCast(@alignCast(ptr)))); } pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { log.debug("semphr_take {?} {}", .{ ptr, tick }); - const sem: *Scheduler.Semaphore = @ptrCast(@alignCast(ptr)); + const sem: *RTOS.Semaphore = @ptrCast(@alignCast(ptr)); const maybe_timeout: ?time.Duration = if (tick == c.OSI_FUNCS_TIME_BLOCKING) .from_us(tick) else null; - sem.take_with_timeout(scheduler, maybe_timeout) catch { + sem.take_with_timeout(rtos, maybe_timeout) catch { log.debug(">>>> return from semaphore take with timeout: {*}", .{sem}); return 1; }; @@ -345,19 +348,19 @@ pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { pub fn semphr_give(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("semphr_give {?}", .{ptr}); - const sem: *Scheduler.Semaphore = @ptrCast(@alignCast(ptr)); - sem.give(scheduler); + const sem: *RTOS.Semaphore = @ptrCast(@alignCast(ptr)); + sem.give(rtos); return 1; } pub fn wifi_thread_semphr_get() callconv(.c) ?*anyopaque { - return &scheduler.current_task.semaphore; + return &rtos.current_task.semaphore; } pub fn mutex_create() callconv(.c) ?*anyopaque { log.debug("mutex_create", .{}); - const mutex = allocator.create(Scheduler.RecursiveMutex) catch { + const mutex = allocator.create(RTOS.RecursiveMutex) catch { log.warn("failed to allocate recursive mutex", .{}); return null; }; @@ -373,7 +376,7 @@ pub fn mutex_create() callconv(.c) ?*anyopaque { pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { log.debug("recursive_mutex_create", .{}); - const mutex = allocator.create(Scheduler.RecursiveMutex) catch { + const mutex = allocator.create(RTOS.RecursiveMutex) catch { log.warn("failed to allocate recursive mutex", .{}); return null; }; @@ -389,15 +392,15 @@ pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { pub fn mutex_delete(ptr: ?*anyopaque) callconv(.c) void { log.debug("mutex_delete {?}", .{ptr}); - const mutex: *Scheduler.RecursiveMutex = @ptrCast(@alignCast(ptr)); + const mutex: *RTOS.RecursiveMutex = @ptrCast(@alignCast(ptr)); allocator.destroy(mutex); } pub fn mutex_lock(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("mutex lock {?}", .{ptr}); - const mutex: *Scheduler.RecursiveMutex = @ptrCast(@alignCast(ptr)); - mutex.lock(scheduler); + const mutex: *RTOS.RecursiveMutex = @ptrCast(@alignCast(ptr)); + mutex.lock(rtos); return 1; } @@ -405,13 +408,13 @@ pub fn mutex_lock(ptr: ?*anyopaque) callconv(.c) i32 { pub fn mutex_unlock(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("mutex unlock {?}", .{ptr}); - const mutex: *Scheduler.RecursiveMutex = @ptrCast(@alignCast(ptr)); - return @intFromBool(mutex.unlock(scheduler)); + const mutex: *RTOS.RecursiveMutex = @ptrCast(@alignCast(ptr)); + return @intFromBool(mutex.unlock(rtos)); } pub const QueueWrapper = struct { item_len: u32, - inner: Scheduler.TypeErasedQueue, + inner: RTOS.TypeErasedQueue, }; pub fn queue_create(capacity: u32, item_len: u32) callconv(.c) ?*anyopaque { @@ -460,9 +463,9 @@ pub fn queue_send(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) const item: [*]const u8 = @ptrCast(@alignCast(item_ptr)); const size = switch (block_time_tick) { - 0 => queue.inner.put_non_blocking(scheduler, item[0..queue.item_len]), + 0 => queue.inner.put_non_blocking(rtos, item[0..queue.item_len]), else => queue.inner.put( - scheduler, + rtos, item[0..queue.item_len], 1, if (block_time_tick == c.OSI_FUNCS_TIME_BLOCKING) @@ -480,9 +483,9 @@ pub fn queue_send_from_isr(ptr: ?*anyopaque, item_ptr: ?*anyopaque, _hptw: ?*any const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); const item: [*]const u8 = @ptrCast(@alignCast(item_ptr)); - const n = @divExact(queue.inner.put_non_blocking(scheduler, item[0..queue.item_len]), queue.item_len); + const n = @divExact(queue.inner.put_non_blocking(rtos, item[0..queue.item_len]), queue.item_len); - @as(*u32, @ptrCast(@alignCast(_hptw))).* = @intFromBool(scheduler.is_a_higher_priority_task_ready()); + @as(*u32, @ptrCast(@alignCast(_hptw))).* = @intFromBool(rtos.is_a_higher_priority_task_ready()); return @intCast(n); } @@ -503,11 +506,11 @@ pub fn queue_recv(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) const size = switch (block_time_tick) { 0 => queue.inner.get_non_blocking( - scheduler, + rtos, item[0..queue.item_len], ), else => queue.inner.get( - scheduler, + rtos, item[0..queue.item_len], queue.item_len, if (block_time_tick == c.OSI_FUNCS_TIME_BLOCKING) @@ -568,7 +571,7 @@ fn task_create_common( const task_entry: *const fn (param: ?*anyopaque) callconv(.c) noreturn = @ptrCast(@alignCast(task_func)); - const task: *Scheduler.Task = scheduler.spawn(task_wrapper, .{task_entry, param}, .{ + const task: *RTOS.Task = rtos.spawn(task_wrapper, .{ task_entry, param }, .{ .priority = @enumFromInt(prio), .stack_size = stack_depth, }) catch { @@ -628,13 +631,13 @@ pub fn task_delete(handle: ?*anyopaque) callconv(.c) void { if (handle != null) { @panic("task_delete(non-null): not implemented"); } - scheduler.yield(.delete); + rtos.yield(.delete); } pub fn task_delay(tick: u32) callconv(.c) void { log.debug("task_delay {}", .{tick}); - scheduler.sleep(.from_us(tick)); + rtos.sleep(.from_us(tick)); } pub fn task_ms_to_tick(ms: u32) callconv(.c) i32 { @@ -642,11 +645,11 @@ pub fn task_ms_to_tick(ms: u32) callconv(.c) i32 { } pub fn task_get_current_task() callconv(.c) ?*anyopaque { - return scheduler.current_task; + return rtos.current_task; } pub fn task_get_max_priority() callconv(.c) i32 { - return @intFromEnum(Scheduler.Priority.highest); + return @intFromEnum(RTOS.Priority.highest); } pub fn get_free_heap_size() callconv(.c) void { diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index 977a3af18..dff11044a 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -5,7 +5,7 @@ const microzig = @import("microzig"); const SPSC_Queue = microzig.concurrency.SPSC_Queue; const radio = @import("../radio.zig"); -const options = radio.options.wifi; +const wifi_options = microzig.options.hal.radio.wifi; const osi = @import("osi.zig"); pub const c = @import("esp-wifi-driver"); @@ -509,7 +509,7 @@ var packets_in_flight: usize = 0; pub fn send_packet(iface: Interface, data: []const u8) (error{TooManyPacketsInFlight} || InternalError)!void { const pkts_in_flight = @atomicLoad(usize, &packets_in_flight, .acquire); - if (pkts_in_flight >= options.tx_queue_len) { + if (pkts_in_flight >= wifi_options.tx_queue_len) { log.warn("too many packets in flight", .{}); return error.TooManyPacketsInFlight; } @@ -545,8 +545,8 @@ pub const ReceivedPacket = struct { } }; -var ap_rx_queue: SPSC_Queue(ReceivedPacket, options.rx_queue_len) = .empty; -var sta_rx_queue: SPSC_Queue(ReceivedPacket, options.rx_queue_len) = .empty; +var ap_rx_queue: SPSC_Queue(ReceivedPacket, wifi_options.rx_queue_len) = .empty; +var sta_rx_queue: SPSC_Queue(ReceivedPacket, wifi_options.rx_queue_len) = .empty; pub fn recv_packet(comptime iface: Interface) ?ReceivedPacket { return switch (iface) { diff --git a/port/espressif/esp/src/hal/system.zig b/port/espressif/esp/src/hal/system.zig index b31221d1d..af0019cac 100644 --- a/port/espressif/esp/src/hal/system.zig +++ b/port/espressif/esp/src/hal/system.zig @@ -124,3 +124,31 @@ pub fn enable_clocks_and_release_reset(mask: PeripheralMask) void { clocks_enable_set(mask); peripheral_reset_clear(mask); } + +pub const CpuInterrupt = enum { + cpu_interrupt_0, + cpu_interrupt_1, + cpu_interrupt_2, + cpu_interrupt_3, + + pub fn source(cpu_interrupt: CpuInterrupt) microzig.cpu.interrupt.Source { + return switch (cpu_interrupt) { + .cpu_interrupt_0 => .from_cpu_intr0, + .cpu_interrupt_1 => .from_cpu_intr1, + .cpu_interrupt_2 => .from_cpu_intr2, + .cpu_interrupt_3 => .from_cpu_intr3, + }; + } + + pub fn set_pending(cpu_interrupt: CpuInterrupt, enabled: bool) void { + const regs: @TypeOf(&SYSTEM.CPU_INTR_FROM_CPU_0) = switch (cpu_interrupt) { + .cpu_interrupt_0 => @ptrCast(&SYSTEM.CPU_INTR_FROM_CPU_0), + .cpu_interrupt_1 => @ptrCast(&SYSTEM.CPU_INTR_FROM_CPU_1), + .cpu_interrupt_2 => @ptrCast(&SYSTEM.CPU_INTR_FROM_CPU_2), + .cpu_interrupt_3 => @ptrCast(&SYSTEM.CPU_INTR_FROM_CPU_3), + }; + regs.write(.{ + .CPU_INTR_FROM_CPU_0 = @intFromBool(enabled), + }); + } +}; From 3448341492e543da4424fc3477545b6ae2d114ef Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Sun, 11 Jan 2026 21:35:03 +0200 Subject: [PATCH 21/43] Refactor scheduler and more --- examples/espressif/esp/src/wifi.zig | 21 +- port/espressif/esp/src/cpus/esp_riscv.zig | 66 +- port/espressif/esp/src/hal.zig | 13 +- port/espressif/esp/src/hal/radio.zig | 42 +- port/espressif/esp/src/hal/radio/osi.zig | 78 +- .../espressif/esp/src/hal/radio/timer_new.zig | 174 ++++ .../esp/src/hal/{RTOS.zig => rtos.zig} | 896 +++++++++--------- port/espressif/esp/src/hal/systimer.zig | 8 + 8 files changed, 717 insertions(+), 581 deletions(-) create mode 100644 port/espressif/esp/src/hal/radio/timer_new.zig rename port/espressif/esp/src/hal/{RTOS.zig => rtos.zig} (51%) diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index 062e6f816..3eea14df0 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -3,7 +3,7 @@ const microzig = @import("microzig"); const SPSC_Queue = microzig.concurrency.SPSC_Queue; const interrupt = microzig.cpu.interrupt; const hal = microzig.hal; -const RTOS = hal.RTOS; +const rtos = hal.rtos; const radio = hal.radio; const usb_serial_jtag = hal.usb_serial_jtag; @@ -31,24 +31,29 @@ pub const microzig_options: microzig.Options = .{ }, .logFn = usb_serial_jtag.logger.log, .interrupts = .{ - .interrupt29 = .{ .c = radio.interrupt_handler }, - .interrupt30 = .{ .c = RTOS.general_purpose_interrupt_handler }, - .interrupt31 = .{ .naked = RTOS.yield_handler }, + .interrupt29 = radio.interrupt_handler, + .interrupt30 = rtos.general_purpose_interrupt_handler, + .interrupt31 = rtos.yield_interrupt_handler, }, .cpu = .{ - .interrupt_stack_size = 4096, + .interrupt_stack = .{ + .enable = true, + }, + }, + .hal = .{ + .rtos = .{ + .enable = true, + }, }, }; var buffer: [50 * 1024]u8 = undefined; -var rtos: RTOS = undefined; pub fn main() !void { var heap_allocator: microzig.Allocator = .init_with_buffer(&buffer); const gpa = heap_allocator.allocator(); - rtos.init(gpa); - try radio.init(gpa, &rtos); + try radio.init(gpa); defer radio.deinit(); try radio.wifi.init(); diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index bdb1aaa26..63e12f1ef 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -4,13 +4,18 @@ const microzig = @import("microzig"); const cpu_config = @import("cpu-config"); const riscv32_common = @import("riscv32-common"); +const interrupt_stack_options = microzig.options.cpu.interrupt_stack; + pub const CPU_Options = struct { - /// If null, interrupts will use same stack. Otherwise, the interrupt - /// handler will switch to a custom stack after pushing the TrapFrame. - /// Nested interrupts use the interrupt stack as well. This feature uses - /// mscratch to store the user's stack pointer. While not in an interrupt, - /// mscratch must be zero. - interrupt_stack_size: ?usize = null, + /// If not enabled, interrupts will use same stack. Otherwise, the + /// interrupt handler will switch to a custom stack after pushing the + /// TrapFrame. Nested interrupts use the interrupt stack as well. This + /// feature uses mscratch to store the old stack pointer. While not in + /// an interrupt, mscratch must be zero. + interrupt_stack: struct { + enable: bool = false, + size: usize = 4096, + } = .{}, }; pub const Exception = enum(u5) { @@ -243,39 +248,26 @@ pub const interrupt = struct { return @ptrFromInt(base + @sizeOf(u32) * @as(usize, @intFromEnum(source))); } - // TODO: implement some sort of masking so as to only check sources you - // care about - pub const SourceIterator = struct { - index: usize, - status_reg: u32, + pub const Status = struct { + reg: u61, - pub fn init() SourceIterator { + pub fn init() Status { return .{ - .index = 0, - .status_reg = INTERRUPT_CORE0.INTR_STATUS_REG_0.raw, + .reg = INTERRUPT_CORE0.INTR_STATUS_REG_0.raw | + (@as(u61, INTERRUPT_CORE0.INTR_STATUS_REG_1.raw) << 32), }; } - pub fn next(iter: *SourceIterator) ?Source { - var leading_zeroes = @ctz(iter.status_reg); - if (leading_zeroes == @bitSizeOf(u32)) { - if (iter.index >= 32) { - return null; - } - iter.status_reg = INTERRUPT_CORE0.INTR_STATUS_REG_1.raw; - leading_zeroes = @ctz(iter.status_reg); - if (leading_zeroes == @bitSizeOf(u32)) { - return null; - } - iter.index = 32; - } - - iter.index += leading_zeroes; - iter.status_reg >>= @truncate(leading_zeroes + 1); - - return @enumFromInt(iter.index); + pub fn is_set(status: Status, source: Source) bool { + return status.reg & (@as(u61, 1) << @intFromEnum(source)) != 0; } }; + + pub fn expect_handler(comptime int: Interrupt, comptime expected_handler: InterruptHandler) void { + const actual_handler = @field(microzig.options.interrupts, @tagName(int)); + if (!std.meta.eql(actual_handler, expected_handler)) + @compileError(std.fmt.comptimePrint("interrupt {t} not set to the expected handler", .{int})); + } }; pub const nop = riscv32_common.nop; @@ -372,7 +364,7 @@ fn init_interrupts() void { .base = @intCast(@intFromPtr(&_vector_table) >> 2), }); - if (interrupt_stack_size != null) { + if (interrupt_stack_options.enable) { csr.mscratch.write_raw(0); } } @@ -396,8 +388,8 @@ pub const TrapFrame = extern struct { a7: usize, }; -const interrupt_stack_size = microzig.options.cpu.interrupt_stack_size; -pub var interrupt_stack: [std.mem.alignForward(usize, interrupt_stack_size orelse 0, 16)]u8 align(16) = undefined; +/// Statically allocated interrupt stack of the requested size. +pub var interrupt_stack: [std.mem.alignForward(usize, interrupt_stack_options.size, 16)]u8 align(16) = undefined; fn _vector_table() align(256) linksection(".ram_vectors") callconv(.naked) void { const interrupt_jump_asm, const interrupt_c_stubs_asm = comptime blk: { @@ -470,7 +462,7 @@ fn _vector_table() align(256) linksection(".ram_vectors") callconv(.naked) void \\ mv a0, sp # Pass a pointer to TrapFrame to the handler function \\ mv a1, ra # Save address of handler function \\ - ++ (if (interrupt_stack_size != null) + ++ (if (interrupt_stack_options.enable) // switch to interrupt stack if not nested \\ csrr t0, mscratch \\ bnez t0, 1f @@ -510,7 +502,7 @@ fn _vector_table() align(256) linksection(".ram_vectors") callconv(.naked) void \\ mret : - : [interrupt_stack_top] "i" (if (interrupt_stack_size != null) + : [interrupt_stack_top] "i" (if (interrupt_stack_options.enable) interrupt_stack[interrupt_stack.len..].ptr else {}), ); diff --git a/port/espressif/esp/src/hal.zig b/port/espressif/esp/src/hal.zig index c68b32f4a..2575189b5 100644 --- a/port/espressif/esp/src/hal.zig +++ b/port/espressif/esp/src/hal.zig @@ -1,20 +1,20 @@ const std = @import("std"); -const microzig = @import("microzig"); pub const esp_image = @import("esp_image"); +const microzig = @import("microzig"); -pub const RTOS = @import("hal/RTOS.zig"); pub const cache = @import("hal/cache.zig"); pub const clocks = @import("hal/clocks.zig"); pub const compatibility = @import("hal/compatibility.zig"); pub const drivers = @import("hal/drivers.zig"); -pub const gpio = @import("hal/gpio.zig"); pub const efuse = @import("hal/efuse.zig"); +pub const gpio = @import("hal/gpio.zig"); pub const i2c = @import("hal/i2c.zig"); -pub const radio = @import("hal/radio.zig"); pub const ledc = @import("hal/ledc.zig"); +pub const radio = @import("hal/radio.zig"); pub const rng = @import("hal/rng.zig"); pub const rom = @import("hal/rom.zig"); +pub const rtos = @import("hal/rtos.zig"); pub const spi = @import("hal/spi.zig"); pub const system = @import("hal/system.zig"); pub const systimer = @import("hal/systimer.zig"); @@ -33,7 +33,7 @@ pub const HAL_Options = struct { secure_version: u32 = 0, version: []const u8 = "0.0.0", } = .{}, - rtos: RTOS.Options = .{}, + rtos: rtos.Options = .{}, radio: radio.Options = .{}, }; @@ -57,6 +57,9 @@ pub fn init_sequence(clock_cfg: clocks.Config) void { system.init(); time.init(); + + if (microzig.options.hal.rtos.enable) + rtos.init(); } // NOTE: might be esp32c3 specific only + temporary until timers hal. diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index 6be22e857..e9e99f67d 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -17,7 +17,6 @@ pub const bluetooth = @import("radio/bluetooth.zig"); const osi = @import("radio/osi.zig"); const timer = @import("radio/timer.zig"); pub const wifi = @import("radio/wifi.zig"); -const RTOS = @import("RTOS.zig"); const log = std.log.scoped(.esp_radio); @@ -26,9 +25,13 @@ pub const Options = struct { wifi: wifi.Options = .{}, }; -pub fn init(allocator: Allocator, rtos: *RTOS) Allocator.Error!void { +pub fn init(gpa: Allocator) Allocator.Error!void { // TODO: check that clock frequency is higher or equal to 80mhz + const radio_interrupt = microzig.options.hal.radio.interrupt; + + comptime microzig.cpu.interrupt.expect_handler(radio_interrupt, interrupt_handler); + { const cs = microzig.interrupt.enter_critical_section(); defer cs.leave(); @@ -36,10 +39,12 @@ pub fn init(allocator: Allocator, rtos: *RTOS) Allocator.Error!void { enable_wifi_power_domain_and_init_clocks(); // phy_mem_init(); // only sets some global variable on esp32c3 - osi.allocator = allocator; - osi.rtos = rtos; + osi.gpa = gpa; - setup_interrupts(); + microzig.cpu.interrupt.map(.wifi_mac, radio_interrupt); + microzig.cpu.interrupt.map(.wifi_pwr, radio_interrupt); + microzig.cpu.interrupt.set_type(radio_interrupt, .level); + microzig.cpu.interrupt.set_priority(radio_interrupt, .highest); } log.debug("initialization complete", .{}); @@ -126,18 +131,15 @@ fn enable_wifi_power_domain_and_init_clocks() void { }); } -fn setup_interrupts() void { - const radio_interrupt = microzig.options.hal.radio.interrupt; - microzig.cpu.interrupt.map(.wifi_mac, radio_interrupt); - microzig.cpu.interrupt.map(.wifi_pwr, radio_interrupt); - microzig.cpu.interrupt.set_type(radio_interrupt, .level); - microzig.cpu.interrupt.set_priority(radio_interrupt, .highest); -} - -pub fn interrupt_handler(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { - if (osi.wifi_interrupt_handler) |handler| { - handler.f(handler.arg); - } else { - // should be unreachable - } -} +pub const interrupt_handler: microzig.cpu.InterruptHandler = .{ + .c = struct { + fn handler_fn(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { + // TODO: multiplex the wifi and bluetooth handlers + if (osi.wifi_interrupt_handler) |handler| { + handler.f(handler.arg); + } else { + // should be unreachable + } + } + }.handler_fn, +}; diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 6c160d8e1..d02edd2fd 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -10,7 +10,7 @@ const APB_CTRL = peripherals.APB_CTRL; const hal = microzig.hal; const radio = @import("../radio.zig"); -const RTOS = @import("../RTOS.zig"); +const rtos = @import("../rtos.zig"); const systimer = @import("../systimer.zig"); const get_time_since_boot = @import("../time.zig").get_time_since_boot; const timer = @import("timer.zig"); @@ -21,8 +21,7 @@ const c = @import("esp-wifi-driver"); // TODO: config const coex_enabled: bool = false; -pub var allocator: std.mem.Allocator = undefined; -pub var rtos: *RTOS = undefined; +pub var gpa: std.mem.Allocator = undefined; pub var wifi_interrupt_handler: ?struct { f: *const fn (?*anyopaque) callconv(.c) void, @@ -94,7 +93,7 @@ pub fn __assert_func( pub fn malloc(len: usize) callconv(.c) ?*anyopaque { log.debug("malloc {}", .{len}); - const buf = allocator.rawAlloc(8 + len, .@"8", @returnAddress()) orelse { + const buf = gpa.rawAlloc(8 + len, .@"8", @returnAddress()) orelse { log.warn("failed to allocate memory: {}", .{len}); return null; }; @@ -127,7 +126,7 @@ pub fn free(ptr: ?*anyopaque) callconv(.c) void { const buf_ptr: [*]u8 = @ptrFromInt(@intFromPtr(ptr) - 8); const buf_len: *usize = @ptrCast(@alignCast(buf_ptr)); - allocator.rawFree(buf_ptr[0 .. @sizeOf(usize) + buf_len.*], .@"8", @returnAddress()); + gpa.rawFree(buf_ptr[0 .. @sizeOf(usize) + buf_len.*], .@"8", @returnAddress()); } pub fn puts(ptr: ?*anyopaque) callconv(.c) void { @@ -306,13 +305,13 @@ pub fn wifi_int_restore(mux_ptr: ?*anyopaque, state: u32) callconv(.c) void { pub fn task_yield_from_isr() callconv(.c) void { log.debug("task_yield_from_isr", .{}); - RTOS.yield_from_isr(); + rtos.yield_from_isr(); } pub fn semphr_create(max_value: u32, init_value: u32) callconv(.c) ?*anyopaque { log.debug("semphr_create {} {}", .{ max_value, init_value }); - const sem = allocator.create(RTOS.Semaphore) catch { + const sem = gpa.create(rtos.Semaphore) catch { log.warn("failed to allocate semaphore", .{}); return null; }; @@ -326,18 +325,18 @@ pub fn semphr_create(max_value: u32, init_value: u32) callconv(.c) ?*anyopaque { pub fn semphr_delete(ptr: ?*anyopaque) callconv(.c) void { log.debug("semphr_delete {?}", .{ptr}); - allocator.destroy(@as(*RTOS.Semaphore, @ptrCast(@alignCast(ptr)))); + gpa.destroy(@as(*rtos.Semaphore, @ptrCast(@alignCast(ptr)))); } pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { log.debug("semphr_take {?} {}", .{ ptr, tick }); - const sem: *RTOS.Semaphore = @ptrCast(@alignCast(ptr)); + const sem: *rtos.Semaphore = @ptrCast(@alignCast(ptr)); const maybe_timeout: ?time.Duration = if (tick == c.OSI_FUNCS_TIME_BLOCKING) .from_us(tick) else null; - sem.take_with_timeout(rtos, maybe_timeout) catch { + sem.take_with_timeout(maybe_timeout) catch { log.debug(">>>> return from semaphore take with timeout: {*}", .{sem}); return 1; }; @@ -348,19 +347,19 @@ pub fn semphr_take(ptr: ?*anyopaque, tick: u32) callconv(.c) i32 { pub fn semphr_give(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("semphr_give {?}", .{ptr}); - const sem: *RTOS.Semaphore = @ptrCast(@alignCast(ptr)); - sem.give(rtos); + const sem: *rtos.Semaphore = @ptrCast(@alignCast(ptr)); + sem.give(); return 1; } pub fn wifi_thread_semphr_get() callconv(.c) ?*anyopaque { - return &rtos.current_task.semaphore; + return &rtos.get_current_task().semaphore; } pub fn mutex_create() callconv(.c) ?*anyopaque { log.debug("mutex_create", .{}); - const mutex = allocator.create(RTOS.RecursiveMutex) catch { + const mutex = gpa.create(rtos.RecursiveMutex) catch { log.warn("failed to allocate recursive mutex", .{}); return null; }; @@ -376,7 +375,7 @@ pub fn mutex_create() callconv(.c) ?*anyopaque { pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { log.debug("recursive_mutex_create", .{}); - const mutex = allocator.create(RTOS.RecursiveMutex) catch { + const mutex = gpa.create(rtos.RecursiveMutex) catch { log.warn("failed to allocate recursive mutex", .{}); return null; }; @@ -392,15 +391,15 @@ pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { pub fn mutex_delete(ptr: ?*anyopaque) callconv(.c) void { log.debug("mutex_delete {?}", .{ptr}); - const mutex: *RTOS.RecursiveMutex = @ptrCast(@alignCast(ptr)); - allocator.destroy(mutex); + const mutex: *rtos.RecursiveMutex = @ptrCast(@alignCast(ptr)); + gpa.destroy(mutex); } pub fn mutex_lock(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("mutex lock {?}", .{ptr}); - const mutex: *RTOS.RecursiveMutex = @ptrCast(@alignCast(ptr)); - mutex.lock(rtos); + const mutex: *rtos.RecursiveMutex = @ptrCast(@alignCast(ptr)); + mutex.lock(); return 1; } @@ -408,13 +407,13 @@ pub fn mutex_lock(ptr: ?*anyopaque) callconv(.c) i32 { pub fn mutex_unlock(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("mutex unlock {?}", .{ptr}); - const mutex: *RTOS.RecursiveMutex = @ptrCast(@alignCast(ptr)); - return @intFromBool(mutex.unlock(rtos)); + const mutex: *rtos.RecursiveMutex = @ptrCast(@alignCast(ptr)); + return @intFromBool(mutex.unlock()); } pub const QueueWrapper = struct { item_len: u32, - inner: RTOS.TypeErasedQueue, + inner: rtos.TypeErasedQueue, }; pub fn queue_create(capacity: u32, item_len: u32) callconv(.c) ?*anyopaque { @@ -427,14 +426,14 @@ pub fn queue_create(capacity: u32, item_len: u32) callconv(.c) ?*anyopaque { break :blk .{ 3, 8 }; }; - const buf: []u8 = allocator.alloc(u8, new_cap * item_len) catch { + const buf: []u8 = gpa.alloc(u8, new_cap * item_len) catch { log.warn("failed to allocate queue buffer", .{}); return null; }; - const queue = allocator.create(QueueWrapper) catch { + const queue = gpa.create(QueueWrapper) catch { log.warn("failed to allocate queue", .{}); - allocator.free(buf); + gpa.free(buf); return null; }; @@ -452,8 +451,8 @@ pub fn queue_delete(ptr: ?*anyopaque) callconv(.c) void { log.debug("queue_delete {?}", .{ptr}); const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); - allocator.free(queue.inner.buffer); - allocator.destroy(queue); + gpa.free(queue.inner.buffer); + gpa.destroy(queue); } pub fn queue_send(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) callconv(.c) i32 { @@ -463,9 +462,8 @@ pub fn queue_send(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) const item: [*]const u8 = @ptrCast(@alignCast(item_ptr)); const size = switch (block_time_tick) { - 0 => queue.inner.put_non_blocking(rtos, item[0..queue.item_len]), + 0 => queue.inner.put_non_blocking(item[0..queue.item_len]), else => queue.inner.put( - rtos, item[0..queue.item_len], 1, if (block_time_tick == c.OSI_FUNCS_TIME_BLOCKING) @@ -483,7 +481,7 @@ pub fn queue_send_from_isr(ptr: ?*anyopaque, item_ptr: ?*anyopaque, _hptw: ?*any const queue: *QueueWrapper = @ptrCast(@alignCast(ptr)); const item: [*]const u8 = @ptrCast(@alignCast(item_ptr)); - const n = @divExact(queue.inner.put_non_blocking(rtos, item[0..queue.item_len]), queue.item_len); + const n = @divExact(queue.inner.put_non_blocking(item[0..queue.item_len]), queue.item_len); @as(*u32, @ptrCast(@alignCast(_hptw))).* = @intFromBool(rtos.is_a_higher_priority_task_ready()); @@ -505,12 +503,8 @@ pub fn queue_recv(ptr: ?*anyopaque, item_ptr: ?*anyopaque, block_time_tick: u32) const item: [*]u8 = @ptrCast(@alignCast(item_ptr)); const size = switch (block_time_tick) { - 0 => queue.inner.get_non_blocking( - rtos, - item[0..queue.item_len], - ), + 0 => queue.inner.get_non_blocking(item[0..queue.item_len]), else => queue.inner.get( - rtos, item[0..queue.item_len], queue.item_len, if (block_time_tick == c.OSI_FUNCS_TIME_BLOCKING) @@ -571,7 +565,7 @@ fn task_create_common( const task_entry: *const fn (param: ?*anyopaque) callconv(.c) noreturn = @ptrCast(@alignCast(task_func)); - const task: *RTOS.Task = rtos.spawn(task_wrapper, .{ task_entry, param }, .{ + const task: *rtos.Task = rtos.spawn(gpa, task_wrapper, .{ task_entry, param }, .{ .priority = @enumFromInt(prio), .stack_size = stack_depth, }) catch { @@ -645,11 +639,11 @@ pub fn task_ms_to_tick(ms: u32) callconv(.c) i32 { } pub fn task_get_current_task() callconv(.c) ?*anyopaque { - return rtos.current_task; + return rtos.get_current_task(); } pub fn task_get_max_priority() callconv(.c) i32 { - return @intFromEnum(RTOS.Priority.highest); + return @intFromEnum(rtos.Priority.highest); } pub fn get_free_heap_size() callconv(.c) void { @@ -898,7 +892,7 @@ pub fn timer_done(ets_timer_ptr: ?*anyopaque) callconv(.c) void { if (timer.find(ets_timer)) |tim| { ets_timer.priv = null; ets_timer.expire = 0; - timer.remove(allocator, tim); + timer.remove(gpa, tim); } else { log.warn("timer not found based on ets_timer", .{}); } @@ -925,7 +919,7 @@ pub fn timer_setfn(ets_timer_ptr: ?*anyopaque, callback_ptr: ?*anyopaque, arg: ? ets_timer.func = null; ets_timer.priv = null; - timer.add(allocator, ets_timer, callback, arg) catch { + timer.add(gpa, ets_timer, callback, arg) catch { log.warn("failed to allocate timer", .{}); }; } @@ -1094,7 +1088,7 @@ pub fn wifi_create_queue(capacity: c_int, item_len: c_int) callconv(.c) ?*anyopa std.debug.assert(wifi_queue_handle == null); - const buf: []u8 = allocator.alloc(u8, @intCast(capacity * item_len)) catch { + const buf: []u8 = gpa.alloc(u8, @intCast(capacity * item_len)) catch { log.warn("failed to allocate queue buffer", .{}); return null; }; @@ -1116,7 +1110,7 @@ pub fn wifi_delete_queue(ptr: ?*anyopaque) callconv(.c) void { std.debug.assert(ptr == @as(?*anyopaque, @ptrCast(&wifi_queue_handle))); const queue: *?*QueueWrapper = @ptrCast(@alignCast(ptr)); - allocator.free(queue.*.?.inner.buffer); + gpa.free(queue.*.?.inner.buffer); wifi_queue_handle = null; wifi_queue = undefined; diff --git a/port/espressif/esp/src/hal/radio/timer_new.zig b/port/espressif/esp/src/hal/radio/timer_new.zig new file mode 100644 index 000000000..a44289890 --- /dev/null +++ b/port/espressif/esp/src/hal/radio/timer_new.zig @@ -0,0 +1,174 @@ +const std = @import("std"); +const log = std.log.scoped(.radio_timer); +const Allocator = std.mem.Allocator; + +const microzig = @import("microzig"); +const time = microzig.drivers.time; +const rtos = @import("../rtos.zig"); +const get_time_since_boot = @import("../time.zig").get_time_since_boot; + +const c = @import("esp-wifi-driver"); + +pub const CallbackFn = *const fn (?*anyopaque) callconv(.c) void; + +pub const Timer = struct { + ets_timer: *c.ets_timer, + callback: CallbackFn, + arg: ?*anyopaque, + deadline: time.Deadline, + periodic: ?time.Duration, + node: std.SinglyLinkedList.Node = .{}, +}; + +pub const Command = union(enum) { + exit, + setfn: struct { + ets_timer: *c.ets_timer, + func: CallbackFn, + arg: ?*anyopaque, + }, + arm: struct { + ets_timer: *c.ets_timer, + duration: time.Duration, + repeat: bool, + }, + disarm: *c.ets_timer, + done: *c.ets_timer, +}; + +var buf: [1]Command = undefined; +pub var command_queue: rtos.Queue(Command) = undefined; + +var timer_list: std.SinglyLinkedList = .{}; + +pub fn init(gpa: Allocator) Allocator.Error!void { + command_queue = .init(&buf); + + _ = try rtos.spawn(gpa, task_fn, .{gpa}, .{ + .priority = .lowest, + .stack_size = 8192, + }); +} + +pub fn deinit() void { + command_queue.put_one(.exit, null); + + // TODO: wait for task to exit +} + +fn task_fn(gpa: std.mem.Allocator) void { + while (true) { + const now = get_time_since_boot(); + while (find_expired(now)) |tim| { + if (tim.periodic) |period| { + tim.deadline = .init_relative(now, period); + } else { + tim.deadline = .no_deadline; + } + tim.callback(tim.arg); + } + + const sleep_duration = if (find_next_wake_absolute()) |next_wake_absolute| + next_wake_absolute.diff(now) + else + null; + + const command = command_queue.get_one(sleep_duration) catch continue; + switch (command) { + .exit => break, + .setfn => |setfn| { + if (find(setfn.ets_timer)) |tim| { + tim.callback = setfn.func; + tim.arg = setfn.arg; + tim.deadline = .no_deadline; + + // setfn.ets_timer.expire = 0; + } else { + // setfn.ets_timer.next = null; + // setfn.ets_timer.period = 0; + // setfn.ets_timer.func = null; + // setfn.ets_timer.priv = null; + + add(gpa, setfn.ets_timer, setfn.func, setfn.arg) catch { + std.log.warn("failed to allocate timer", .{}); + }; + } + }, + .arm => |arm| { + if (find(arm.ets_timer)) |tim| { + tim.deadline = .init_relative(get_time_since_boot(), arm.duration); + tim.periodic = if (arm.repeat) arm.duration else null; + } else { + std.log.warn("timer not found based on ets_timer", .{}); + } + }, + .disarm => |ets_timer| { + if (find(ets_timer)) |tim| { + tim.deadline = .no_deadline; + } else { + std.log.warn("timer not found based on ets_timer", .{}); + } + }, + .done => |ets_timer| { + if (find(ets_timer)) |tim| { + // ets_timer.priv = null; + // ets_timer.expire = 0; + remove(gpa, tim); + } else { + std.log.warn("timer not found based on ets_timer", .{}); + } + }, + } + } +} + +fn add(allocator: Allocator, ets_timer: *c.ets_timer, callback: CallbackFn, arg: ?*anyopaque) !void { + const timer = try allocator.create(Timer); + timer.* = .{ + .ets_timer = ets_timer, + .callback = callback, + .arg = arg, + .deadline = .no_deadline, + .periodic = null, + }; + timer_list.prepend(&timer.node); +} + +fn remove(allocator: Allocator, timer: *Timer) void { + timer_list.remove(&timer.node); + allocator.destroy(timer); +} + +fn find(ets_timer: *c.ets_timer) ?*Timer { + var it = timer_list.first; + while (it) |node| : (it = node.next) { + const timer: *Timer = @alignCast(@fieldParentPtr("node", node)); + if (timer.ets_timer == ets_timer) { + return timer; + } + } + return null; +} + +fn find_expired(now: time.Absolute) ?*Timer { + var it = timer_list.first; + while (it) |node| : (it = node.next) { + const timer: *Timer = @alignCast(@fieldParentPtr("node", node)); + if (timer.deadline.is_reached_by(now)) { + return timer; + } + } + return null; +} + +fn find_next_wake_absolute() ?time.Absolute { + var it = timer_list.first; + var min_deadline: time.Deadline = .no_deadline; + while (it) |node| : (it = node.next) { + const timer: *Timer = @alignCast(@fieldParentPtr("node", node)); + if (@intFromEnum(timer.deadline.timeout) < @intFromEnum(min_deadline.timeout)) { + min_deadline = timer.deadline; + } + } + return if (min_deadline.can_be_reached()) min_deadline.timeout else null; +} diff --git a/port/espressif/esp/src/hal/RTOS.zig b/port/espressif/esp/src/hal/rtos.zig similarity index 51% rename from port/espressif/esp/src/hal/RTOS.zig rename to port/espressif/esp/src/hal/rtos.zig index 58a14d248..8b48f38b4 100644 --- a/port/espressif/esp/src/hal/RTOS.zig +++ b/port/espressif/esp/src/hal/rtos.zig @@ -13,8 +13,6 @@ const system = @import("system.zig"); const systimer = @import("systimer.zig"); const get_time_since_boot = @import("time.zig").get_time_since_boot; -const rtos_options = microzig.options.hal.rtos; - // How it works? // For simple task to task context switches, only necessary registers are // saved. But if a higher priority task becomes available during the handling @@ -26,52 +24,31 @@ const rtos_options = microzig.options.hal.rtos; // TODO: add identifier names to tasks // TODO: stack usage report based on stack painting -// TODO: idle improvements -// - implement task garbage collection -// TODO: for other esp32 chips with multicore support SMP +// TODO: question: should idle do more stuff (like task garbage collection)? +// TODO: implement task garbage collection and recycling // TODO: implement std.Io // TODO: use @stackUpperBound when implemented // TODO: implement priority inheritance for respective sync primitives // TODO: better handling if timeout is in the past or very short +// TODO: support SMP for other esp32 chips with multicore (far future) // NOTE: don't use anonymous structs for reinitializing state unions with // previous union state to avoid compiler bug (0.15.2) const STACK_ALIGN: std.mem.Alignment = .@"16"; const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 31 * @sizeOf(usize)); -const RTOS = @This(); - -var maybe_instance: ?*RTOS = null; - -gpa: Allocator, - -ready_queue: ReadyPriorityQueue = .{}, -timer_queue: std.DoublyLinkedList = .{}, -scheduled_for_deletion_list: std.DoublyLinkedList = .{}, - -main_task: Task, - -// Idle only requires the stack space used by the yield interrupt -idle_stack: [STACK_ALIGN.forward(EXTRA_STACK_SIZE)]u8 align(STACK_ALIGN.toByteUnits()) = undefined, -idle_task: Task, - -/// The task in .running state -current_task: *Task, - -// TODO: configurable -pub const Priority = enum(u8) { - idle = 0, - lowest = 1, - _, - - pub const highest: Priority = @enumFromInt(std.math.maxInt(@typeInfo(Priority).@"enum".tag_type)); - - pub fn is_greater(prio: Priority, other: Priority) bool { - return @intFromEnum(prio) > @intFromEnum(other); - } -}; +const rtos_options = microzig.options.hal.rtos; +pub const Priority = rtos_options.Priority; pub const Options = struct { + enable: bool = false, + Priority: type = enum(u8) { + idle = 0, + lowest = 1, + _, + + pub const highest: @This() = @enumFromInt(std.math.maxInt(@typeInfo(@This()).@"enum".tag_type)); + }, general_purpose_interrupt: microzig.cpu.Interrupt = .interrupt30, systimer_unit: systimer.Unit = .unit0, systimer_alarm: systimer.Alarm = .alarm0, @@ -79,36 +56,50 @@ pub const Options = struct { yield_interrupt: microzig.cpu.Interrupt = .interrupt31, }; -pub fn init(rtos: *RTOS, gpa: Allocator) void { +var main_task: Task = .{ + .context = undefined, + .priority = .lowest, + .stack = &.{}, +}; +var idle_stack: [STACK_ALIGN.forward(EXTRA_STACK_SIZE)]u8 align(STACK_ALIGN.toByteUnits()) = undefined; +var idle_task: Task = .{ + .context = .{ + .pc = &idle, + .sp = idle_stack[idle_stack.len..].ptr, + .fp = null, + }, + .priority = .idle, + .stack = &.{}, +}; + +var rtos_state: RTOS_State = undefined; +pub const RTOS_State = struct { + ready_queue: ReadyPriorityQueue = .{}, + timer_queue: std.DoublyLinkedList = .{}, + scheduled_for_deletion_list: std.DoublyLinkedList = .{}, + + /// The task in .running state. Safe to access outside of critical section + /// as it is always the same for the currently executing task. + current_task: *Task, +}; + +/// Automatically called inside hal startup sequence if it the rtos is enabled +/// in hal options. +pub fn init() void { comptime { - if (microzig.options.cpu.interrupt_stack_size == null) - @compileError("Please enable interrupt stacks to use the rtos"); + if (!microzig.options.cpu.interrupt_stack.enable) + @compileError("Please enable interrupt stack to use the rtos"); + microzig.cpu.interrupt.expect_handler(rtos_options.general_purpose_interrupt, general_purpose_interrupt_handler); + microzig.cpu.interrupt.expect_handler(rtos_options.yield_interrupt, yield_interrupt_handler); } - assert(maybe_instance == null); + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); - rtos.* = .{ - .gpa = gpa, - .main_task = .{ - .context = undefined, - .priority = .lowest, - .stack = &.{}, - }, - .idle_task = .{ - .context = .{ - .pc = @intFromPtr(&idle), - .sp = @intFromPtr(rtos.idle_stack[rtos.idle_stack.len..].ptr), - .fp = 0, - }, - .stack = &rtos.idle_stack, - .priority = .idle, - }, - .current_task = &rtos.main_task, + rtos_state = .{ + .current_task = &main_task, }; - - rtos.ready(&rtos.idle_task, .{}); - - maybe_instance = rtos; + make_ready(&idle_task); microzig.cpu.interrupt.map(rtos_options.cpu_interrupt.source(), rtos_options.yield_interrupt); microzig.cpu.interrupt.set_type(rtos_options.yield_interrupt, .level); @@ -124,7 +115,7 @@ pub fn init(rtos: *RTOS, gpa: Allocator) void { rtos_options.systimer_alarm.set_enabled(false); rtos_options.systimer_alarm.set_interrupt_enabled(true); - microzig.cpu.interrupt.map(.systimer_target0, rtos_options.general_purpose_interrupt); + microzig.cpu.interrupt.map(rtos_options.systimer_alarm.interrupt_source(), rtos_options.general_purpose_interrupt); microzig.cpu.interrupt.set_type(rtos_options.general_purpose_interrupt, .level); microzig.cpu.interrupt.set_priority(rtos_options.general_purpose_interrupt, .lowest); microzig.cpu.interrupt.enable(rtos_options.general_purpose_interrupt); @@ -140,6 +131,10 @@ fn idle() callconv(.naked) void { ); } +pub fn get_current_task() *Task { + return rtos_state.current_task; +} + pub const SpawnOptions = struct { stack_size: usize = 4096, // TODO: should we ban idle priority? @@ -147,7 +142,7 @@ pub const SpawnOptions = struct { }; pub fn spawn( - rtos: *RTOS, + gpa: std.mem.Allocator, comptime function: anytype, args: std.meta.ArgsTuple(@TypeOf(function)), options: SpawnOptions, @@ -157,14 +152,11 @@ pub fn spawn( const TypeErased = struct { fn call() callconv(.c) void { - const sched = maybe_instance orelse @panic("no active rtos"); - const context_ptr: *const Args = - @ptrFromInt(args_align.forward(@intFromPtr(sched.current_task) + @sizeOf(Task))); + @ptrFromInt(args_align.forward(@intFromPtr(rtos_state.current_task) + @sizeOf(Task))); @call(.auto, function, context_ptr.*); - if (@typeInfo(@TypeOf(function)).@"fn".return_type.? != noreturn) { - sched.yield(.delete); + yield(.delete); unreachable; } } @@ -177,7 +169,7 @@ pub fn spawn( const stack_end = STACK_ALIGN.forward(stack_start + options.stack_size + EXTRA_STACK_SIZE); const alloc_size = stack_end; - const raw_alloc = try rtos.gpa.alignedAlloc(u8, alloc_align, alloc_size); + const raw_alloc = try gpa.alignedAlloc(u8, alloc_align, alloc_size); const task: *Task = @ptrCast(@alignCast(raw_alloc)); @@ -188,9 +180,9 @@ pub fn spawn( task.* = .{ .context = .{ - .sp = @intFromPtr(stack[stack.len..].ptr), - .pc = @intFromPtr(&TypeErased.call), - .fp = 0, + .sp = stack[stack.len..].ptr, + .pc = &TypeErased.call, + .fp = null, }, .stack = stack, .priority = options.priority, @@ -199,103 +191,91 @@ pub fn spawn( const cs = enter_critical_section(); defer cs.leave(); - rtos.ready(task, .{}); + make_ready(task); return task; } +/// Must execute inside a critical section. +pub fn make_ready(task: *Task) void { + switch (task.state) { + .ready, .running, .scheduled_for_deletion => return, + .none, .suspended => {}, + .alarm_set => |_| { + rtos_state.timer_queue.remove(&task.node); + }, + } + + task.state = .ready; + rtos_state.ready_queue.put(task); +} + pub const YieldAction = union(enum) { reschedule, wait: struct { timeout: ?TimerTicks = null, - wait_queue: ?*PriorityWaitQueue = null, }, delete, }; -pub inline fn yield(rtos: *RTOS, action: YieldAction) void { +pub inline fn yield(action: YieldAction) void { const cs = enter_critical_section(); - rtos.yield_and_leave_cs(action, cs); + yield_and_leave_cs(action, cs); } -/// Must be called inside critical section. Calling leave on the critical +/// Must execute inside a critical section. Calling leave on the critical /// section becomes unnecessary. -pub inline fn yield_and_leave_cs(rtos: *RTOS, action: YieldAction, cs: CriticalSection) void { +pub inline fn yield_and_leave_cs(action: YieldAction, cs: CriticalSection) void { defer if (!cs.enable_on_leave) { microzig.cpu.interrupt.disable_interrupts(); }; - const current_task, const next_task = rtos.yield_inner(action); + const current_task, const next_task = yield_inner(action); context_switch(¤t_task.context, &next_task.context); } -fn yield_inner(rtos: *RTOS, action: YieldAction) struct { *Task, *Task } { - const current_task = rtos.current_task; +fn yield_inner(action: YieldAction) linksection(".ram_text") struct { *Task, *Task } { + assert(microzig.cpu.csr.mscratch.read_raw() == 0); + + const current_task = rtos_state.current_task; switch (action) { .reschedule => { - current_task.state = .{ .ready = .{} }; - rtos.ready_queue.put(current_task); + current_task.state = .ready; + rtos_state.ready_queue.put(current_task); }, .wait => |wait_action| { - assert(current_task != &rtos.idle_task); - - if (wait_action.wait_queue) |wait_queue| { - assert(current_task.wait_queue == null); - wait_queue.put(current_task); - current_task.wait_queue = wait_queue; - } + assert(current_task != &idle_task); if (wait_action.timeout) |timeout| { - rtos.schedule_wake_at(current_task, timeout); + schedule_wake_at(current_task, timeout); } else { current_task.state = .suspended; } }, .delete => { - assert(current_task != &rtos.idle_task and current_task != &rtos.main_task); + assert(current_task != &idle_task and current_task != &main_task); current_task.state = .scheduled_for_deletion; - rtos.scheduled_for_deletion_list.append(¤t_task.node); + rtos_state.scheduled_for_deletion_list.append(¤t_task.node); }, } - const next_task: *Task = rtos.ready_queue.pop(null) orelse @panic("No task ready to run!"); + const next_task: *Task = rtos_state.ready_queue.pop(null) orelse @panic("No task ready to run!"); - next_task.state = Task.State{ .running = next_task.state.ready }; - rtos.current_task = next_task; + next_task.state = .running; + rtos_state.current_task = next_task; return .{ current_task, next_task }; } -pub fn wake_from_wait_queue(rtos: *RTOS, wait_queue: *PriorityWaitQueue, how_many: enum(usize) { - one = 1, - all = std.math.maxInt(usize), - _, -}) void { - var remaining: usize = @intFromEnum(how_many); - if (remaining == 0) return; - while (wait_queue.pop()) |task| : (remaining -= 1) { - if (remaining == 0) break; - - assert(task.wait_queue == wait_queue); - task.wait_queue = null; - - switch (task.state) { - .alarm_set => |_| rtos.timer_queue.remove(&task.node), - .suspended => {}, - else => @panic("invalid state for waiting task"), - } - - rtos.ready(task, .{}); +pub fn sleep(duration: time.Duration) void { + const timeout: TimerTicks = .after(duration); + while (!timeout.is_reached()) { + yield(.{ .wait = .{ + .timeout = timeout, + } }); } } -pub fn sleep(rtos: *RTOS, duration: time.Duration) void { - rtos.yield(.{ .wait = .{ - .timeout = .after(duration), - } }); - assert(rtos.current_task.state.running.timeout); -} - inline fn context_switch(prev_context: *Context, next_context: *Context) void { asm volatile ( \\la a2, 1f @@ -354,230 +334,222 @@ pub fn yield_from_isr() void { rtos_options.cpu_interrupt.set_pending(true); } -pub fn is_a_higher_priority_task_ready(rtos: *RTOS) bool { - return if (rtos.ready_queue.peek_top()) |top_ready_task| - top_ready_task.priority.is_greater(rtos.current_task.priority) +pub fn is_a_higher_priority_task_ready() bool { + return if (rtos_state.ready_queue.peek_top()) |top_ready_task| + @intFromEnum(top_ready_task.priority) > @intFromEnum(rtos_state.current_task.priority) else false; } -pub fn yield_handler() linksection(".ram_vectors") callconv(.naked) void { - comptime { - assert(@sizeOf(Context) == 3 * @sizeOf(usize)); - } +pub const yield_interrupt_handler: microzig.cpu.InterruptHandler = .{ + .naked = struct { + pub fn handler_fn() linksection(".ram_vectors") callconv(.naked) void { + comptime { + assert(@sizeOf(Context) == 3 * @sizeOf(usize)); + } - asm volatile ( - \\ - \\addi sp, sp, -31*4 - \\ - \\sw ra, 0*4(sp) - \\sw t0, 1*4(sp) - \\sw t1, 2*4(sp) - \\sw t2, 3*4(sp) - \\sw t3, 4*4(sp) - \\sw t4, 5*4(sp) - \\sw t5, 6*4(sp) - \\sw t6, 7*4(sp) - \\sw a0, 8*4(sp) - \\sw a1, 9*4(sp) - \\sw a2, 10*4(sp) - \\sw a3, 11*4(sp) - \\sw a4, 12*4(sp) - \\sw a5, 13*4(sp) - \\sw a6, 14*4(sp) - \\sw a7, 15*4(sp) - \\sw s1, 16*4(sp) - \\sw s2, 17*4(sp) - \\sw s3, 18*4(sp) - \\sw s4, 19*4(sp) - \\sw s5, 20*4(sp) - \\sw s6, 21*4(sp) - \\sw s7, 22*4(sp) - \\sw s8, 23*4(sp) - \\sw s9, 24*4(sp) - \\sw s10, 25*4(sp) - \\sw s11, 26*4(sp) - \\sw gp, 27*4(sp) - \\sw tp, 28*4(sp) - \\ - \\csrr a1, mepc - \\sw a1, 29*4(sp) - \\ - \\csrr a1, mstatus - \\sw a1, 30*4(sp) - \\ - // save sp for later - \\mv a2, sp - \\ - // use the interrupt stack in this call to minimize task stack size - // NOTE: mscratch doesn't need to be zeroed because this can't be - // interrupted by a higher priority interrupt - \\la sp, %[interrupt_stack_top] - \\ - // allocate `Context` struct and save context - \\addi sp, sp, -16 - \\la a1, 1f - \\sw a1, 0(sp) - \\sw a2, 4(sp) - \\sw s0, 8(sp) - \\ - // first parameter is a pointer to context - \\mv a0, sp - \\mv s1, a1 - \\jal %[schedule_in_isr] - \\ - // load next task context - \\lw a1, 0(sp) - \\lw a2, 4(sp) - \\lw s0, 8(sp) - // change sp to the new task - \\mv sp, a2 - \\ - // if the next task program counter is equal to 1f's location just jump - // to it (ie. the task was interrupted). Technically not required but - // works as an optimization. - \\beq a1, s1, 1f - \\ - // ensure interrupts get enabled after mret - \\li t0, 0x80 - \\csrs mstatus, t0 - \\ - // jump to new task - \\csrw mepc, a1 - \\mret - \\ - \\1: - \\ - \\lw t1, 30*4(sp) - \\csrw mstatus, t1 - \\ - \\lw t0, 29*4(sp) - \\csrw mepc, t0 - \\ - \\lw ra, 0*4(sp) - \\lw t0, 1*4(sp) - \\lw t1, 2*4(sp) - \\lw t2, 3*4(sp) - \\lw t3, 4*4(sp) - \\lw t4, 5*4(sp) - \\lw t5, 6*4(sp) - \\lw t6, 7*4(sp) - \\lw a0, 8*4(sp) - \\lw a1, 9*4(sp) - \\lw a2, 10*4(sp) - \\lw a3, 11*4(sp) - \\lw a4, 12*4(sp) - \\lw a5, 13*4(sp) - \\lw a6, 14*4(sp) - \\lw a7, 15*4(sp) - \\lw s1, 16*4(sp) - \\lw s2, 17*4(sp) - \\lw s3, 18*4(sp) - \\lw s4, 19*4(sp) - \\lw s5, 20*4(sp) - \\lw s6, 21*4(sp) - \\lw s7, 22*4(sp) - \\lw s8, 23*4(sp) - \\lw s9, 24*4(sp) - \\lw s10, 25*4(sp) - \\lw s11, 26*4(sp) - \\lw gp, 27*4(sp) - \\lw tp, 28*4(sp) - \\ - \\addi sp, sp, 31*4 - \\mret - : - : [schedule_in_isr] "i" (&schedule_in_isr), - [interrupt_stack_top] "i" (microzig.cpu.interrupt_stack[microzig.cpu.interrupt_stack.len..].ptr), - ); -} + asm volatile ( + \\ + \\addi sp, sp, -31*4 + \\ + \\sw ra, 0*4(sp) + \\sw t0, 1*4(sp) + \\sw t1, 2*4(sp) + \\sw t2, 3*4(sp) + \\sw t3, 4*4(sp) + \\sw t4, 5*4(sp) + \\sw t5, 6*4(sp) + \\sw t6, 7*4(sp) + \\sw a0, 8*4(sp) + \\sw a1, 9*4(sp) + \\sw a2, 10*4(sp) + \\sw a3, 11*4(sp) + \\sw a4, 12*4(sp) + \\sw a5, 13*4(sp) + \\sw a6, 14*4(sp) + \\sw a7, 15*4(sp) + \\sw s1, 16*4(sp) + \\sw s2, 17*4(sp) + \\sw s3, 18*4(sp) + \\sw s4, 19*4(sp) + \\sw s5, 20*4(sp) + \\sw s6, 21*4(sp) + \\sw s7, 22*4(sp) + \\sw s8, 23*4(sp) + \\sw s9, 24*4(sp) + \\sw s10, 25*4(sp) + \\sw s11, 26*4(sp) + \\sw gp, 27*4(sp) + \\sw tp, 28*4(sp) + \\ + \\csrr a1, mepc + \\sw a1, 29*4(sp) + \\ + \\csrr a1, mstatus + \\sw a1, 30*4(sp) + \\ + // save sp for later + \\mv a2, sp + \\ + // use the interrupt stack in this call to minimize task stack size + // NOTE: mscratch doesn't need to be zeroed because this can't be + // interrupted by a higher priority interrupt + \\la sp, %[interrupt_stack_top] + \\ + // allocate `Context` struct and save context + \\addi sp, sp, -16 + \\la a1, 1f + \\sw a1, 0(sp) + \\sw a2, 4(sp) + \\sw s0, 8(sp) + \\ + // first parameter is a pointer to context + \\mv a0, sp + \\mv s1, a1 + \\jal %[schedule_in_isr] + \\ + // load next task context + \\lw a1, 0(sp) + \\lw a2, 4(sp) + \\lw s0, 8(sp) + // change sp to the new task + \\mv sp, a2 + \\ + // if the next task program counter is equal to 1f's location just jump + // to it (ie. the task was interrupted). Technically not required but + // works as an optimization. + \\beq a1, s1, 1f + \\ + // ensure interrupts get enabled after mret + \\li t0, 0x80 + \\csrs mstatus, t0 + \\ + // jump to new task + \\csrw mepc, a1 + \\mret + \\ + \\1: + \\ + \\lw t1, 30*4(sp) + \\csrw mstatus, t1 + \\ + \\lw t0, 29*4(sp) + \\csrw mepc, t0 + \\ + \\lw ra, 0*4(sp) + \\lw t0, 1*4(sp) + \\lw t1, 2*4(sp) + \\lw t2, 3*4(sp) + \\lw t3, 4*4(sp) + \\lw t4, 5*4(sp) + \\lw t5, 6*4(sp) + \\lw t6, 7*4(sp) + \\lw a0, 8*4(sp) + \\lw a1, 9*4(sp) + \\lw a2, 10*4(sp) + \\lw a3, 11*4(sp) + \\lw a4, 12*4(sp) + \\lw a5, 13*4(sp) + \\lw a6, 14*4(sp) + \\lw a7, 15*4(sp) + \\lw s1, 16*4(sp) + \\lw s2, 17*4(sp) + \\lw s3, 18*4(sp) + \\lw s4, 19*4(sp) + \\lw s5, 20*4(sp) + \\lw s6, 21*4(sp) + \\lw s7, 22*4(sp) + \\lw s8, 23*4(sp) + \\lw s9, 24*4(sp) + \\lw s10, 25*4(sp) + \\lw s11, 26*4(sp) + \\lw gp, 27*4(sp) + \\lw tp, 28*4(sp) + \\ + \\addi sp, sp, 31*4 + \\mret + : + : [schedule_in_isr] "i" (&schedule_in_isr), + [interrupt_stack_top] "i" (microzig.cpu.interrupt_stack[microzig.cpu.interrupt_stack.len..].ptr), + ); + } + }.handler_fn, +}; +// Can't be preempted by a higher priority interrupt. fn schedule_in_isr(context: *Context) linksection(".ram_vectors") callconv(.c) void { - const rtos = maybe_instance orelse @panic("no active rtos"); - rtos_options.cpu_interrupt.set_pending(false); - const current_task = rtos.current_task; - const ready_task = rtos.ready_queue.pop(rtos.current_task.priority) orelse return; + const current_task = rtos_state.current_task; + const ready_task = rtos_state.ready_queue.pop(rtos_state.current_task.priority) orelse return; // swap contexts current_task.context = context.*; context.* = ready_task.context; - // keep the state until the next yield - current_task.state = Task.State{ .ready = current_task.state.running }; - rtos.ready_queue.put(current_task); + current_task.state = .ready; + rtos_state.ready_queue.put(current_task); - ready_task.state = Task.State{ .running = ready_task.state.ready }; - rtos.current_task = ready_task; + ready_task.state = .running; + rtos_state.current_task = ready_task; } -/// Must be called from a critical section. -fn schedule_wake_at(rtos: *RTOS, sleeping_task: *Task, ticks: TimerTicks) void { +pub const general_purpose_interrupt_handler: microzig.cpu.InterruptHandler = .{ .c = struct { + pub fn handler_fn(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { + var status: microzig.cpu.interrupt.Status = .init(); + if (status.is_set(rtos_options.systimer_alarm.interrupt_source())) { + rtos_options.systimer_alarm.clear_interrupt(); + + const cs = enter_critical_section(); + defer cs.leave(); + + sweep_timer_queue_for_timeouts(); + } + + if (is_a_higher_priority_task_ready()) { + yield_from_isr(); + } + } +}.handler_fn }; + +/// Must execute inside a critical section. +fn schedule_wake_at(sleeping_task: *Task, ticks: TimerTicks) void { sleeping_task.state = .{ .alarm_set = ticks }; - var maybe_node = rtos.timer_queue.first; + var maybe_node = rtos_state.timer_queue.first; while (maybe_node) |node| : (maybe_node = node.next) { const task: *Task = @alignCast(@fieldParentPtr("node", node)); if (ticks.is_before(task.state.alarm_set)) { - rtos.timer_queue.insertBefore(&task.node, &sleeping_task.node); + rtos_state.timer_queue.insertBefore(&task.node, &sleeping_task.node); break; } } else { - rtos.timer_queue.append(&sleeping_task.node); + rtos_state.timer_queue.append(&sleeping_task.node); } // If we updated the first element of the list, it means that we have to // reschedule the timer - if (rtos.timer_queue.first == &sleeping_task.node) { + if (rtos_state.timer_queue.first == &sleeping_task.node) { rtos_options.systimer_alarm.set_target(@intFromEnum(ticks)); rtos_options.systimer_alarm.set_enabled(true); if (ticks.is_reached()) - rtos.sweep_timer_queue_for_timeouts(); + sweep_timer_queue_for_timeouts(); } } -pub fn general_purpose_interrupt_handler(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { - const rtos = maybe_instance orelse @panic("no active rtos"); - - var iter: microzig.cpu.interrupt.SourceIterator = .init(); - while (iter.next()) |source| { - switch (source) { - .systimer_target0 => { - const cs = enter_critical_section(); - defer cs.leave(); - - rtos_options.systimer_alarm.clear_interrupt(); - - rtos.sweep_timer_queue_for_timeouts(); - }, - else => {}, - } - } - - if (rtos.is_a_higher_priority_task_ready()) { - yield_from_isr(); - } -} - -fn sweep_timer_queue_for_timeouts(rtos: *RTOS) void { - while (rtos.timer_queue.popFirst()) |node| { +fn sweep_timer_queue_for_timeouts() void { + while (rtos_state.timer_queue.popFirst()) |node| { const task: *Task = @alignCast(@fieldParentPtr("node", node)); if (!task.state.alarm_set.is_reached()) { - rtos.timer_queue.prepend(&task.node); + rtos_state.timer_queue.prepend(&task.node); break; } - - if (task.wait_queue) |wait_queue| { - wait_queue.remove(task); - task.wait_queue = null; - } - - rtos.ready(task, .{ .timeout = true }); + task.state = .ready; + rtos_state.ready_queue.put(task); } - if (rtos.timer_queue.first) |node| { + if (rtos_state.timer_queue.first) |node| { const task: *Task = @alignCast(@fieldParentPtr("node", node)); rtos_options.systimer_alarm.set_target(@intFromEnum(task.state.alarm_set)); rtos_options.systimer_alarm.set_enabled(true); @@ -586,12 +558,6 @@ fn sweep_timer_queue_for_timeouts(rtos: *RTOS) void { } } -fn ready(rtos: *RTOS, task: *Task, flags: Task.ReadyFlags) void { - assert(task.state != .ready); - task.state = .{ .ready = flags }; - rtos.ready_queue.put(task); -} - pub const Task = struct { context: Context, stack: []u8, @@ -606,32 +572,33 @@ pub const Task = struct { /// Task specific semaphore (required by the wifi driver) semaphore: Semaphore = .init(0), - /// Node used for external wait queues. - wait_queue_node: std.DoublyLinkedList.Node = .{}, - - /// The wait queue in which this task is currently placed. - wait_queue: ?*PriorityWaitQueue = null, - pub const State = union(enum) { none, - ready: ReadyFlags, - running: ReadyFlags, + ready, + running, alarm_set: TimerTicks, suspended, scheduled_for_deletion, }; +}; - pub const ReadyFlags = packed struct(u1) { - timeout: bool = false, - }; +pub const Context = extern struct { + pc: ?*const anyopaque, + sp: ?*const anyopaque, + fp: ?*const anyopaque, - pub const WaitQueueEntry = struct { - queue: *std.DoublyLinkedList, - node: std.DoublyLinkedList = .{}, - }; + pub fn format( + self: Context, + writer: *std.Io.Writer, + ) std.Io.Writer.Error!void { + try writer.print(".{{ .pc = 0x{x}, .sp = 0x{x}, .fp = 0x{x} }}", .{ + @intFromPtr(self.pc), + @intFromPtr(self.sp), + @intFromPtr(self.fp), + }); + } }; -// TODO: Maybe swap with something more efficient. pub const ReadyPriorityQueue = struct { inner: std.DoublyLinkedList = .{}, @@ -646,7 +613,7 @@ pub const ReadyPriorityQueue = struct { pub fn pop(pq: *ReadyPriorityQueue, maybe_more_than_prio: ?Priority) ?*Task { if (pq.peek_top()) |task| { if (maybe_more_than_prio) |more_than_prio| { - if (!task.priority.is_greater(more_than_prio)) { + if (@intFromEnum(task.priority) <= @intFromEnum(more_than_prio)) { return null; } } @@ -660,7 +627,7 @@ pub const ReadyPriorityQueue = struct { var maybe_node = pq.inner.first; while (maybe_node) |node| : (maybe_node = node.next) { const task: *Task = @alignCast(@fieldParentPtr("node", node)); - if (new_task.priority.is_greater(task.priority)) { + if (@intFromEnum(new_task.priority) > @intFromEnum(task.priority)) { pq.inner.insertBefore(node, &new_task.node); break; } @@ -670,55 +637,85 @@ pub const ReadyPriorityQueue = struct { } }; +pub const TimerTicks = enum(u52) { + _, + + pub fn now() TimerTicks { + return @enumFromInt(rtos_options.systimer_unit.read()); + } + + pub fn after(duration: time.Duration) TimerTicks { + return TimerTicks.now().add_duration(duration); + } + + pub fn is_reached(ticks: TimerTicks) bool { + return ticks.is_before(.now()); + } + + pub fn is_before(a: TimerTicks, b: TimerTicks) bool { + const _a = @intFromEnum(a); + const _b = @intFromEnum(b); + return _a < _b or _b -% _a < _a; + } + + pub fn add_duration(ticks: TimerTicks, duration: time.Duration) TimerTicks { + return @enumFromInt(@intFromEnum(ticks) +% @as(u52, @intCast(duration.to_us())) * systimer.ticks_per_us()); + } +}; + +pub const TimeoutError = error{Timeout}; + pub const PriorityWaitQueue = struct { list: std.DoublyLinkedList = .{}, - pub fn pop(q: *PriorityWaitQueue) ?*Task { - if (q.list.popFirst()) |first_node| { - return @alignCast(@fieldParentPtr("wait_queue_node", first_node)); - } else { - return null; + pub const Waiter = struct { + task: *Task, + priority: Priority, + node: std.DoublyLinkedList.Node = .{}, + }; + + /// Must execute inside a critical section. + pub fn wake_one(q: *PriorityWaitQueue) void { + if (q.list.first) |first_node| { + const waiter: *Waiter = @alignCast(@fieldParentPtr("node", first_node)); + make_ready(waiter.task); + } + } + + /// Must execute inside a critical section. + pub fn wake_all(q: *PriorityWaitQueue) void { + while (q.list.popFirst()) |current_node| { + const current_waiter: *Waiter = @alignCast(@fieldParentPtr("node", current_node)); + make_ready(current_waiter.task); } } - pub fn put(q: *PriorityWaitQueue, task: *Task) void { + /// Must execute inside a critical section. + pub fn wait(q: *PriorityWaitQueue, task: *Task, maybe_timeout: ?TimerTicks) void { + var waiter: Waiter = .{ + .task = task, + .priority = task.priority, + }; + var it = q.list.first; while (it) |current_node| : (it = current_node.next) { - const current_task: *Task = @alignCast(@fieldParentPtr("wait_queue_node", current_node)); - if (task.priority.is_greater(current_task.priority)) { - q.list.insertBefore(¤t_task.wait_queue_node, &task.wait_queue_node); + const current_waiter: *Waiter = @alignCast(@fieldParentPtr("node", current_node)); + if (@intFromEnum(waiter.priority) > @intFromEnum(current_waiter.priority)) { + q.list.insertBefore(¤t_waiter.node, &waiter.node); break; } } else { - q.list.append(&task.wait_queue_node); + q.list.append(&waiter.node); } - } - - pub fn remove(q: *PriorityWaitQueue, task: *Task) void { - q.list.remove(&task.wait_queue_node); - } - - pub fn update_priority(q: *PriorityWaitQueue, task: *Task) void { - q.remove(&task.wait_queue_node); - q.put(task); - } -}; -pub const Context = extern struct { - pc: usize, - sp: usize, - fp: usize, + yield(.{ .wait = .{ + .timeout = maybe_timeout, + } }); - pub fn format( - self: Context, - writer: *std.Io.Writer, - ) std.Io.Writer.Error!void { - try writer.print(".{{ .pc = 0x{x}, .sp = 0x{x}, .fp = 0x{x} }}", .{ self.pc, self.sp, self.fp }); + q.list.remove(&waiter.node); } }; -pub const TimeoutError = error{Timeout}; - pub const Mutex = struct { state: State = .unlocked, wait_queue: PriorityWaitQueue = .{}, @@ -728,11 +725,11 @@ pub const Mutex = struct { unlocked, }; - pub fn lock(mutex: *Mutex, rtos: *RTOS) void { - lock_with_timeout(mutex, rtos, null) catch unreachable; + pub fn lock(mutex: *Mutex) void { + lock_with_timeout(mutex, null) catch unreachable; } - pub fn lock_with_timeout(mutex: *Mutex, rtos: *RTOS, maybe_timeout: ?time.Duration) TimeoutError!void { + pub fn lock_with_timeout(mutex: *Mutex, maybe_timeout: ?time.Duration) TimeoutError!void { const cs = enter_critical_section(); defer cs.leave(); @@ -742,40 +739,39 @@ pub const Mutex = struct { null; while (mutex.state != .unlocked) { - rtos.yield(.{ .wait = .{ - .timeout = maybe_timeout_ticks, - .wait_queue = &mutex.wait_queue, - } }); - if (rtos.current_task.state.running.timeout) - return error.Timeout; + if (maybe_timeout_ticks) |timeout_ticks| + if (timeout_ticks.is_reached()) + return error.Timeout; + + mutex.wait_queue.wait(rtos_state.current_task, maybe_timeout_ticks); } mutex.state = .locked; } - pub fn unlock(mutex: *Mutex, rtos: *RTOS) void { + pub fn unlock(mutex: *Mutex) void { const cs = enter_critical_section(); - defer rtos.yield_and_leave_cs(.reschedule, cs); + defer yield_and_leave_cs(.reschedule, cs); assert(mutex.state == .locked); mutex.state = .unlocked; - rtos.wake_from_wait_queue(&mutex.wait_queue, .one); + mutex.wait_queue.wake_one(); } }; -// TODO: maybe move inside osi.zig since it is made specifically for it +// TODO: maybe move inside radion/osi.zig since it is made specifically for it pub const RecursiveMutex = struct { recursive: bool, value: u32 = 0, owning_task: ?*Task = null, wait_queue: PriorityWaitQueue = .{}, - pub fn lock(mutex: *RecursiveMutex, rtos: *RTOS) void { + pub fn lock(mutex: *RecursiveMutex) void { const cs = enter_critical_section(); defer cs.leave(); - const current_task = rtos.current_task; + const current_task = rtos_state.current_task; if (mutex.owning_task == current_task) { assert(mutex.recursive); @@ -789,9 +785,7 @@ pub const RecursiveMutex = struct { // // } // while (mutex.owning_task != null) { - rtos.yield(.{ .wait = .{ - .wait_queue = &mutex.wait_queue, - } }); + mutex.wait_queue.wait(current_task, null); } // } @@ -800,16 +794,16 @@ pub const RecursiveMutex = struct { mutex.owning_task = current_task; } - pub fn unlock(mutex: *RecursiveMutex, rtos: *RTOS) bool { + pub fn unlock(mutex: *RecursiveMutex) bool { const cs = enter_critical_section(); assert(mutex.value > 0); mutex.value -= 1; if (mutex.value <= 0) { - defer rtos.yield_and_leave_cs(.reschedule, cs); + defer yield_and_leave_cs(.reschedule, cs); mutex.owning_task = null; - rtos.wake_from_wait_queue(&mutex.wait_queue, .one); + mutex.wait_queue.wake_one(); return true; } else { @@ -829,11 +823,11 @@ pub const Semaphore = struct { }; } - pub fn take(sem: *Semaphore, rtos: *RTOS) void { - sem.take_with_timeout(rtos, null) catch unreachable; + pub fn take(sem: *Semaphore) void { + sem.take_with_timeout(null) catch unreachable; } - pub fn take_with_timeout(sem: *Semaphore, rtos: *RTOS, maybe_timeout: ?time.Duration) TimeoutError!void { + pub fn take_with_timeout(sem: *Semaphore, maybe_timeout: ?time.Duration) TimeoutError!void { const cs = enter_critical_section(); defer cs.leave(); @@ -843,24 +837,22 @@ pub const Semaphore = struct { null; while (sem.value <= 0) { - rtos.yield(.{ .wait = .{ - .timeout = maybe_timeout_ticks, - .wait_queue = &sem.wait_queue, - } }); - if (rtos.current_task.state.running.timeout) - return error.Timeout; + if (maybe_timeout_ticks) |timeout_ticks| + if (timeout_ticks.is_reached()) + return error.Timeout; + + sem.wait_queue.wait(rtos_state.current_task, maybe_timeout_ticks); } sem.value -= 1; } - pub fn give(sem: *Semaphore, rtos: *RTOS) void { + pub fn give(sem: *Semaphore) void { const cs = enter_critical_section(); - defer rtos.yield_and_leave_cs(.reschedule, cs); + defer yield_and_leave_cs(.reschedule, cs); sem.value += 1; - - rtos.wake_from_wait_queue(&sem.wait_queue, .one); + sem.wait_queue.wake_one(); } }; @@ -885,7 +877,6 @@ pub const TypeErasedQueue = struct { pub fn put( q: *TypeErasedQueue, - rtos: *RTOS, elements: []const u8, min: usize, maybe_timeout: ?time.Duration, @@ -893,9 +884,6 @@ pub const TypeErasedQueue = struct { assert(elements.len >= min); if (elements.len == 0) return 0; - const cs = enter_critical_section(); - defer rtos.yield_and_leave_cs(.reschedule, cs); - const maybe_timeout_ticks: ?TimerTicks = if (maybe_timeout) |timeout| .after(timeout) else @@ -903,27 +891,29 @@ pub const TypeErasedQueue = struct { var n: usize = 0; + const cs = enter_critical_section(); + defer if (n > 0) yield_and_leave_cs(.reschedule, cs); + while (true) { - n += q.put_non_blocking_from_cs(rtos, elements[n..]); + n += q.put_non_blocking_from_cs(elements[n..]); if (n >= min) return n; - rtos.yield(.{ .wait = .{ - .timeout = maybe_timeout_ticks, - .wait_queue = &q.putters, - } }); - if (rtos.current_task.state.running.timeout) - return n; + if (maybe_timeout_ticks) |timeout_ticks| + if (timeout_ticks.is_reached()) + return n; + + q.putters.wait(rtos_state.current_task, maybe_timeout_ticks); } } - pub fn put_non_blocking(q: *TypeErasedQueue, rtos: *RTOS, elements: []const u8) usize { + pub fn put_non_blocking(q: *TypeErasedQueue, elements: []const u8) usize { const cs = enter_critical_section(); defer cs.leave(); - return q.put_non_blocking_from_cs(rtos, elements); + return q.put_non_blocking_from_cs(elements); } - fn put_non_blocking_from_cs(q: *TypeErasedQueue, rtos: *RTOS, elements: []const u8) usize { + fn put_non_blocking_from_cs(q: *TypeErasedQueue, elements: []const u8) usize { var n: usize = 0; while (q.puttable_slice()) |slice| { const copy_len = @min(slice.len, elements.len - n); @@ -933,7 +923,7 @@ pub const TypeErasedQueue = struct { n += copy_len; if (n == elements.len) break; } - if (n > 0) rtos.wake_from_wait_queue(&q.getters, .one); + if (n > 0) q.getters.wake_one(); return n; } @@ -949,7 +939,6 @@ pub const TypeErasedQueue = struct { pub fn get( q: *TypeErasedQueue, - rtos: *RTOS, buffer: []u8, min: usize, maybe_timeout: ?time.Duration, @@ -957,9 +946,6 @@ pub const TypeErasedQueue = struct { assert(buffer.len >= min); if (buffer.len == 0) return 0; - const cs = enter_critical_section(); - defer rtos.yield_and_leave_cs(.reschedule, cs); - const maybe_timeout_ticks: ?TimerTicks = if (maybe_timeout) |timeout| .after(timeout) else @@ -967,27 +953,29 @@ pub const TypeErasedQueue = struct { var n: usize = 0; + const cs = enter_critical_section(); + defer if (n > 0) yield_and_leave_cs(.reschedule, cs); + while (true) { - n += q.get_non_blocking_from_cs(rtos, buffer[n..]); + n += q.get_non_blocking_from_cs(buffer[n..]); if (n >= min) return n; - rtos.yield(.{ .wait = .{ - .timeout = maybe_timeout_ticks, - .wait_queue = &q.getters, - } }); - if (rtos.current_task.state.running.timeout) - return n; + if (maybe_timeout_ticks) |timeout_ticks| + if (timeout_ticks.is_reached()) + return n; + + q.getters.wait(rtos_state.current_task, maybe_timeout_ticks); } } - pub fn get_non_blocking(q: *TypeErasedQueue, rtos: *RTOS, buffer: []u8) usize { + pub fn get_non_blocking(q: *TypeErasedQueue, buffer: []u8) usize { const cs = enter_critical_section(); defer cs.leave(); - return q.get_non_blocking_from_cs(rtos, buffer); + return q.get_non_blocking_from_cs(buffer); } - fn get_non_blocking_from_cs(q: *TypeErasedQueue, rtos: *RTOS, buffer: []u8) usize { + fn get_non_blocking_from_cs(q: *TypeErasedQueue, buffer: []u8) usize { var n: usize = 0; while (q.gettable_slice()) |slice| { const copy_len = @min(slice.len, buffer.len - n); @@ -999,7 +987,7 @@ pub const TypeErasedQueue = struct { n += copy_len; if (n == buffer.len) break; } - if (n > 0) rtos.wake_from_wait_queue(&q.putters, .one); + if (n > 0) q.putters.wake_one(); return n; } @@ -1020,39 +1008,35 @@ pub fn Queue(Elem: type) type { return .{ .type_erased = .init(@ptrCast(buffer)) }; } - pub fn close(q: *Self, rtos: *RTOS) void { - q.type_erased.close(rtos); + pub fn put(q: *Self, elements: []const Elem, min: usize, timeout: ?time.Duration) usize { + return @divExact(q.type_erased.put(@ptrCast(elements), min * @sizeOf(Elem), timeout), @sizeOf(Elem)); } - pub fn put(q: *Self, rtos: *RTOS, elements: []const Elem, min: usize, timeout: ?time.Duration) usize { - return @divExact(q.type_erased.put(rtos, @ptrCast(elements), min * @sizeOf(Elem), timeout), @sizeOf(Elem)); - } - - pub fn put_all(q: *Self, rtos: *RTOS, elements: []const Elem, timeout: ?time.Duration) TimeoutError!void { - if (q.put(rtos, elements, elements.len, timeout) != elements.len) + pub fn put_all(q: *Self, elements: []const Elem, timeout: ?time.Duration) TimeoutError!void { + if (q.put(elements, elements.len, timeout) != elements.len) return error.Timeout; } - pub fn put_one(q: *Self, rtos: *RTOS, item: Elem) TimeoutError!void { - if (q.put(rtos, &.{item}, 1, null) != 1) + pub fn put_one(q: *Self, item: Elem, timeout: ?time.Duration) TimeoutError!void { + if (q.put(&.{item}, 1, timeout) != 1) return error.Timeout; } - pub fn put_non_blocking(q: *Self, rtos: *RTOS, elements: []const Elem) usize { - return @divExact(q.type_erased.put_non_blocking(rtos, @ptrCast(elements)), @sizeOf(Elem)); + pub fn put_non_blocking(q: *Self, elements: []const Elem) usize { + return @divExact(q.type_erased.put_non_blocking(@ptrCast(elements)), @sizeOf(Elem)); } - pub fn put_one_non_blocking(q: *Self, rtos: *RTOS, item: Elem) bool { - return q.put_non_blocking(rtos, @ptrCast(&item)) == 1; + pub fn put_one_non_blocking(q: *Self, item: Elem) bool { + return q.put_non_blocking(@ptrCast(&item)) == 1; } - pub fn get(q: *Self, rtos: *RTOS, buffer: []Elem, target: usize, timeout: ?time.Duration) usize { - return @divExact(q.type_erased.get(rtos, @ptrCast(buffer), target * @sizeOf(Elem), timeout), @sizeOf(Elem)); + pub fn get(q: *Self, buffer: []Elem, target: usize, timeout: ?time.Duration) usize { + return @divExact(q.type_erased.get(@ptrCast(buffer), target * @sizeOf(Elem), timeout), @sizeOf(Elem)); } - pub fn get_one(q: *Self, rtos: *RTOS, timeout: ?time.Duration) TimeoutError!Elem { + pub fn get_one(q: *Self, timeout: ?time.Duration) TimeoutError!Elem { var buf: [1]Elem = undefined; - if (q.get(rtos, &buf, 1, timeout) != 1) + if (q.get(&buf, 1, timeout) != 1) return error.Timeout; return buf[0]; } @@ -1062,29 +1046,3 @@ pub fn Queue(Elem: type) type { } }; } - -pub const TimerTicks = enum(u52) { - _, - - pub fn now() TimerTicks { - return @enumFromInt(rtos_options.systimer_unit.read()); - } - - pub fn after(duration: time.Duration) TimerTicks { - return TimerTicks.now().add_duration(duration); - } - - pub fn is_reached(ticks: TimerTicks) bool { - return ticks.is_before(.now()); - } - - pub fn is_before(a: TimerTicks, b: TimerTicks) bool { - const _a = @intFromEnum(a); - const _b = @intFromEnum(b); - return _a < _b or _b -% _a < _a; - } - - pub fn add_duration(ticks: TimerTicks, duration: time.Duration) TimerTicks { - return @enumFromInt(@intFromEnum(ticks) +% @as(u52, @intCast(duration.to_us())) * systimer.ticks_per_us()); - } -}; diff --git a/port/espressif/esp/src/hal/systimer.zig b/port/espressif/esp/src/hal/systimer.zig index 8c20d3414..6c3691adc 100644 --- a/port/espressif/esp/src/hal/systimer.zig +++ b/port/espressif/esp/src/hal/systimer.zig @@ -183,6 +183,14 @@ pub const Alarm = enum(u2) { }); } + pub fn interrupt_source(self: Alarm) microzig.cpu.interrupt.Source { + return switch (self) { + .alarm0 => .systimer_target0, + .alarm1 => .systimer_target1, + .alarm2 => .systimer_target2, + }; + } + pub fn set_interrupt_enabled(self: Alarm, enable: bool) void { switch (self) { .alarm0 => SYSTIMER.INT_ENA.modify(.{ .TARGET0_INT_ENA = @intFromBool(enable) }), From 8ec448659d9e0a40758c9bb5ce7fe47fc14fe191 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Mon, 12 Jan 2026 22:25:38 +0200 Subject: [PATCH 22/43] Fix RTOS bug and more - Fix idle task stack overflow - Add alignment to data sections - Upgrade `radio/timer.zig` to use a separate task - Add max value to semaphore --- examples/espressif/esp/build.zig | 2 +- examples/espressif/esp/src/rtos.zig | 55 ++++++ examples/espressif/esp/src/scheduler.zig | 57 ------ examples/espressif/esp/src/wifi.zig | 6 +- .../esp/ld/esp32_c3/direct_boot_sections.ld | 19 +- .../esp/ld/esp32_c3/flashless_sections.ld | 13 +- .../esp/ld/esp32_c3/image_boot_sections.ld | 16 +- port/espressif/esp/src/cpus/esp_riscv.zig | 5 +- port/espressif/esp/src/hal/radio.zig | 6 +- port/espressif/esp/src/hal/radio/osi.zig | 60 +----- port/espressif/esp/src/hal/radio/timer.zig | 171 ++++++++++++----- .../espressif/esp/src/hal/radio/timer_new.zig | 174 ------------------ port/espressif/esp/src/hal/rtos.zig | 73 ++++---- 13 files changed, 271 insertions(+), 386 deletions(-) create mode 100644 examples/espressif/esp/src/rtos.zig delete mode 100644 examples/espressif/esp/src/scheduler.zig delete mode 100644 port/espressif/esp/src/hal/radio/timer_new.zig diff --git a/examples/espressif/esp/build.zig b/examples/espressif/esp/build.zig index 3e0732ad8..af2e168e9 100644 --- a/examples/espressif/esp/build.zig +++ b/examples/espressif/esp/build.zig @@ -24,7 +24,7 @@ pub fn build(b: *std.Build) void { .{ .name = "stepper_driver_dumb", .file = "src/stepper_driver_dumb.zig" }, .{ .name = "systimer", .file = "src/systimer.zig" }, .{ .name = "ws2812_blinky", .file = "src/ws2812_blinky.zig" }, - .{ .name = "scheduler", .file = "src/scheduler.zig" }, + .{ .name = "rtos", .file = "src/rtos.zig" }, .{ .name = "wifi", .file = "src/wifi.zig", .features = .{ .flashless = false, .lwip = true, diff --git a/examples/espressif/esp/src/rtos.zig b/examples/espressif/esp/src/rtos.zig new file mode 100644 index 000000000..d61b0a924 --- /dev/null +++ b/examples/espressif/esp/src/rtos.zig @@ -0,0 +1,55 @@ +const std = @import("std"); +const log = std.log; + +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; +const esp = microzig.hal; +const gpio = esp.gpio; +const systimer = esp.systimer; +const usb_serial_jtag = esp.usb_serial_jtag; +const rtos = esp.rtos; + +pub const microzig_options: microzig.Options = .{ + .logFn = usb_serial_jtag.logger.log, + .interrupts = .{ + .interrupt30 = rtos.general_purpose_interrupt_handler, + .interrupt31 = rtos.yield_interrupt_handler, + }, + .log_level = .debug, + .cpu = .{ + .interrupt_stack = .{ + .enable = true, + }, + }, + .hal = .{ + .rtos = .{ + .enable = true, + }, + }, +}; + +var heap_buf: [10 * 1024]u8 = undefined; + +fn task1(queue: *rtos.Queue(u32)) void { + for (0..5) |i| { + queue.put_one(i, null) catch unreachable; + rtos.sleep(.from_ms(500)); + } +} + +pub fn main() !void { + var heap = microzig.Allocator.init_with_buffer(&heap_buf); + const gpa = heap.allocator(); + + var buffer: [1]u32 = undefined; + var queue: rtos.Queue(u32) = .init(&buffer); + + esp.time.sleep_ms(1000); + + _ = try rtos.spawn(gpa, task1, .{&queue}, .{}); + + while (true) { + const item = try queue.get_one(.from_ms(1000)); + std.log.info("got item: {}", .{item}); + } +} diff --git a/examples/espressif/esp/src/scheduler.zig b/examples/espressif/esp/src/scheduler.zig deleted file mode 100644 index b0f9f4449..000000000 --- a/examples/espressif/esp/src/scheduler.zig +++ /dev/null @@ -1,57 +0,0 @@ -const std = @import("std"); -const log = std.log; -const microzig = @import("microzig"); -const peripherals = microzig.chip.peripherals; -const esp = microzig.hal; -const gpio = esp.gpio; -const systimer = esp.systimer; -const usb_serial_jtag = esp.usb_serial_jtag; - -pub const microzig_options: microzig.Options = .{ - .logFn = usb_serial_jtag.logger.log, - .interrupts = .{ - .interrupt30 = .{ .c = esp.Scheduler.generic_interrupt_handler }, - .interrupt31 = .{ .naked = esp.Scheduler.isr_yield_handler }, - }, - .log_level = .debug, - .cpu = .{ - .interrupt_stack_size = 4096, - }, -}; - -var heap_buf: [10 * 1024]u8 = undefined; - -fn task1(scheduler: *esp.Scheduler, queue: *esp.Scheduler.Queue(u32)) void { - for (0..5) |i| { - queue.put_one(scheduler, i) catch { - std.log.err("failed to put item", .{}); - continue; - }; - scheduler.sleep(.from_ms(500)); - } -} - -pub fn main() !void { - var heap = microzig.Allocator.init_with_buffer(&heap_buf); - const allocator = heap.allocator(); - - var scheduler: esp.Scheduler = undefined; - var buffer: [1]u32 = undefined; - var queue: esp.Scheduler.Queue(u32) = .init(&buffer); - - scheduler.init(allocator); - - esp.time.sleep_ms(1000); - - _ = try scheduler.spawn(task1, .{ - &scheduler, - &queue, - }, .{ - .stack_size = 8000, - }); - - while (true) { - const item = try queue.get_one(&scheduler, .from_ms(1000)); - std.log.info("got item: {}", .{item}); - } -} diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index 3eea14df0..cb9d07c91 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -47,7 +47,7 @@ pub const microzig_options: microzig.Options = .{ }, }; -var buffer: [50 * 1024]u8 = undefined; +var buffer: [70 * 1024]u8 = undefined; pub fn main() !void { var heap_allocator: microzig.Allocator = .init_with_buffer(&buffer); @@ -84,8 +84,6 @@ pub fn main() !void { var last_mem_show = hal.time.get_time_since_boot(); while (true) { - radio.tick(); - const sta_state = radio.wifi.get_sta_state(); if (!connected and sta_state == .sta_connected) { std.log.info("link up", .{}); @@ -119,6 +117,8 @@ pub fn main() !void { std.log.info("free memory: {}K ({})", .{ free_heap / 1024, free_heap }); last_mem_show = now; } + + rtos.sleep(.from_ms(10)); } } diff --git a/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld index f147f586d..89882a9c5 100644 --- a/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld @@ -25,10 +25,12 @@ SECTIONS .ram_text ORIGIN(IRAM) : AT(_irom_size + _drom_size) { + . = ALIGN(16); microzig_ram_text_start = .; KEEP(*(.ram_text)) KEEP(*(.ram_vectors)) + . = ALIGN(16); /* wifi rwtext */ *(.wifi0iram .wifi0iram.*) *(.wifirxiram .wifirxiram.*) @@ -39,17 +41,17 @@ SECTIONS *(.wifiextrairam.* ) *(.coexiram.* ) - . = ALIGN(8); - + . = ALIGN(16); microzig_ram_text_end = .; } > IRAM - . = ALIGN(4); + . = ALIGN(16); _iram_size = . - microzig_ram_text_start; _dram_start = ORIGIN(DRAM) + _iram_size; .data _dram_start : AT(_irom_size + _drom_size + _iram_size) { + . = ALIGN(16); /* put microzig_data_start right before .ram_text */ microzig_data_start = . - _iram_size; *(.sdata*) @@ -57,27 +59,32 @@ SECTIONS /* wifi data */ *(.dram1*) + + . = ALIGN(16); microzig_data_end = .; } > DRAM .bss (NOLOAD) : { + . = ALIGN(16); microzig_bss_start = .; *(.sbss*) *(.bss*) + . = ALIGN(16); microzig_bss_end = .; } > DRAM .tbss (NOLOAD) : { + . = ALIGN(16); *(.tbss*) } > DRAM .heap (NOLOAD) : { - microzig_heap_start = .; - . = ORIGIN(DRAM) + LENGTH(DRAM); - microzig_heap_end = .; + microzig_heap_start = .; + . = ORIGIN(DRAM) + LENGTH(DRAM); + microzig_heap_end = .; } > DRAM microzig_data_load_start = ORIGIN(DROM) + _irom_size + _drom_size; diff --git a/port/espressif/esp/ld/esp32_c3/flashless_sections.ld b/port/espressif/esp/ld/esp32_c3/flashless_sections.ld index 05b10e218..44e3e9fc9 100644 --- a/port/espressif/esp/ld/esp32_c3/flashless_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/flashless_sections.ld @@ -20,12 +20,13 @@ SECTIONS *(.coexiram.* ) } > IRAM - . = ALIGN(8); + . = ALIGN(16); _iram_size = . - microzig_ram_text_start; _dram_start = ORIGIN(DRAM) + _iram_size; .data _dram_start : AT(_iram_size) { + . = ALIGN(16); microzig_data_start = .; *(.srodata*) *(.sdata*) @@ -41,22 +42,24 @@ SECTIONS .bss (NOLOAD) : { + . = ALIGN(16); microzig_bss_start = .; *(.bss*) *(.sbss*) + . = ALIGN(16); microzig_bss_end = .; } > DRAM - .tbss : + .tbss (NOLOAD) : { *(.tbss*) } > DRAM .heap (NOLOAD) : { - microzig_heap_start = .; - . = ORIGIN(DRAM) + LENGTH(DRAM); - microzig_heap_end = .; + microzig_heap_start = .; + . = ORIGIN(DRAM) + LENGTH(DRAM); + microzig_heap_end = .; } > DRAM PROVIDE(__global_pointer$ = microzig_data_start + 0x800); diff --git a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld index 5570e9165..10fb529a9 100644 --- a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld @@ -54,6 +54,7 @@ SECTIONS .ram.text : { + . = ALIGN(16); KEEP(*(.ram_text)) KEEP(*(.ram_vectors)) @@ -63,6 +64,7 @@ SECTIONS .ram.text.wifi : { + . = ALIGN(16); *( .wifi0iram .wifi0iram.*) *( .wifirxiram .wifirxiram.*) *( .wifislprxiram .wifislprxiram.*) @@ -81,25 +83,33 @@ SECTIONS .data : { + . = ALIGN(16); microzig_data_start = .; *(.sdata*) *(.data*) + . = ALIGN(16); microzig_data_end = .; } > DRAM .data.wifi : { + . = ALIGN(16); *(.dram1*) } > DRAM .bss (NOLOAD) : { + . = ALIGN(16); microzig_bss_start = .; *(.sbss*) *(.bss*) + . = ALIGN(16); microzig_bss_end = .; } > DRAM + /* FIXME: this is a workaround for the threadlocal variable that may be + * created by foundation libc, because otherwise DRAM overflows (if .tbss is + * not explicitly added, it is placed after .heap) */ .tbss (NOLOAD) : { *(.tbss*) @@ -107,9 +117,9 @@ SECTIONS .heap (NOLOAD) : { - microzig_heap_start = .; - . = ORIGIN(DRAM) + LENGTH(DRAM); - microzig_heap_end = .; + microzig_heap_start = .; + . = ORIGIN(DRAM) + LENGTH(DRAM); + microzig_heap_end = .; } > DRAM PROVIDE(__global_pointer$ = microzig_data_start + 0x800); diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index 63e12f1ef..e65c0adbf 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -388,8 +388,9 @@ pub const TrapFrame = extern struct { a7: usize, }; -/// Statically allocated interrupt stack of the requested size. -pub var interrupt_stack: [std.mem.alignForward(usize, interrupt_stack_options.size, 16)]u8 align(16) = undefined; +/// Statically allocated interrupt stack of the requested size. Used when the +/// interrupt stack option is enabled. +pub var interrupt_stack: [std.mem.alignForward(usize, interrupt_stack_options.size, 16)]u8 align(16) linksection(".ram_vectors") = undefined; fn _vector_table() align(256) linksection(".ram_vectors") callconv(.naked) void { const interrupt_jump_asm, const interrupt_c_stubs_asm = comptime blk: { diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index e9e99f67d..58b59a682 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -32,6 +32,8 @@ pub fn init(gpa: Allocator) Allocator.Error!void { comptime microzig.cpu.interrupt.expect_handler(radio_interrupt, interrupt_handler); + try timer.init(gpa); + { const cs = microzig.interrupt.enter_critical_section(); defer cs.leave(); @@ -59,10 +61,6 @@ pub fn init(gpa: Allocator) Allocator.Error!void { // should free everything pub fn deinit() void {} -pub fn tick() void { - timer.tick(); -} - pub fn read_mac(iface: enum { sta, ap, diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index d02edd2fd..73d4e949c 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -315,7 +315,7 @@ pub fn semphr_create(max_value: u32, init_value: u32) callconv(.c) ?*anyopaque { log.warn("failed to allocate semaphore", .{}); return null; }; - sem.* = .init(init_value); + sem.* = .init(init_value, max_value); log.debug(">>>> semaphore create: {*}", .{sem}); @@ -870,32 +870,14 @@ pub fn timer_disarm(ets_timer_ptr: ?*anyopaque) callconv(.c) void { log.debug("timer_disarm {?}", .{ets_timer_ptr}); const ets_timer: *c.ets_timer = @ptrCast(@alignCast(ets_timer_ptr)); - - const cs = enter_critical_section(); - defer cs.leave(); - - if (timer.find(ets_timer)) |tim| { - tim.deadline = .init_absolute(null); - } else { - log.warn("timer not found based on ets_timer", .{}); - } + timer.disarm(ets_timer); } pub fn timer_done(ets_timer_ptr: ?*anyopaque) callconv(.c) void { log.debug("timer_done {?}", .{ets_timer_ptr}); const ets_timer: *c.ets_timer = @ptrCast(@alignCast(ets_timer_ptr)); - - const cs = enter_critical_section(); - defer cs.leave(); - - if (timer.find(ets_timer)) |tim| { - ets_timer.priv = null; - ets_timer.expire = 0; - timer.remove(gpa, tim); - } else { - log.warn("timer not found based on ets_timer", .{}); - } + timer.done(gpa, ets_timer); } pub fn timer_setfn(ets_timer_ptr: ?*anyopaque, callback_ptr: ?*anyopaque, arg: ?*anyopaque) callconv(.c) void { @@ -903,44 +885,16 @@ pub fn timer_setfn(ets_timer_ptr: ?*anyopaque, callback_ptr: ?*anyopaque, arg: ? const ets_timer: *c.ets_timer = @ptrCast(@alignCast(ets_timer_ptr)); const callback: timer.CallbackFn = @ptrCast(@alignCast(callback_ptr)); - - const cs = enter_critical_section(); - defer cs.leave(); - - if (timer.find(ets_timer)) |tim| { - tim.callback = callback; - tim.arg = arg; - tim.deadline = .init_absolute(null); - - ets_timer.expire = 0; - } else { - ets_timer.next = null; - ets_timer.period = 0; - ets_timer.func = null; - ets_timer.priv = null; - - timer.add(gpa, ets_timer, callback, arg) catch { - log.warn("failed to allocate timer", .{}); - }; - } + timer.setfn(gpa, ets_timer, callback, arg) catch { + log.warn("failed to allocate timer", .{}); + }; } pub fn timer_arm_us(ets_timer_ptr: ?*anyopaque, us: u32, repeat: bool) callconv(.c) void { log.debug("timer_arm_us {?} {} {}", .{ ets_timer_ptr, us, repeat }); const ets_timer: *c.ets_timer = @ptrCast(@alignCast(ets_timer_ptr)); - - // TODO: locking - const cs = enter_critical_section(); - defer cs.leave(); - - if (timer.find(ets_timer)) |tim| { - const period: time.Duration = .from_us(us); - tim.deadline = .init_relative(get_time_since_boot(), period); - tim.periodic = if (repeat) period else null; - } else { - log.warn("timer not found based on ets_timer", .{}); - } + timer.arm(ets_timer, .from_us(us), repeat); } pub fn wifi_reset_mac() callconv(.c) void { diff --git a/port/espressif/esp/src/hal/radio/timer.zig b/port/espressif/esp/src/hal/radio/timer.zig index 2d99ad473..89cf51f7b 100644 --- a/port/espressif/esp/src/hal/radio/timer.zig +++ b/port/espressif/esp/src/hal/radio/timer.zig @@ -1,9 +1,11 @@ const std = @import("std"); +const log = std.log.scoped(.radio_timer); const Allocator = std.mem.Allocator; const microzig = @import("microzig"); const time = microzig.drivers.time; -const hal = microzig.hal; +const rtos = @import("../rtos.zig"); +const get_time_since_boot = @import("../time.zig").get_time_since_boot; const c = @import("esp-wifi-driver"); @@ -15,75 +17,160 @@ pub const Timer = struct { arg: ?*anyopaque, deadline: time.Deadline, periodic: ?time.Duration, - list_node: std.SinglyLinkedList.Node = .{}, + node: std.SinglyLinkedList.Node = .{}, }; +var reload_semaphore: rtos.Semaphore = .init(0, 1); +var mutex: rtos.Mutex = .{}; var timer_list: std.SinglyLinkedList = .{}; -pub fn add(allocator: Allocator, ets_timer: *c.ets_timer, callback: CallbackFn, arg: ?*anyopaque) !void { - const timer = try allocator.create(Timer); - timer.* = .{ - .ets_timer = ets_timer, - .callback = callback, - .arg = arg, - .deadline = .init_absolute(null), - .periodic = null, - }; - timer_list.prepend(&timer.list_node); +pub fn init(gpa: Allocator) Allocator.Error!void { + _ = try rtos.spawn(gpa, task_fn, .{}, .{ + .priority = .highest, + .stack_size = 8192, + }); } -pub fn remove(allocator: Allocator, timer: *Timer) void { - timer_list.remove(&timer.list_node); - allocator.destroy(timer); +pub fn deinit() void { + // TODO: exit mechanism } -pub fn find(ets_timer: *c.ets_timer) ?*Timer { - var current_node = timer_list.first; - while (current_node) |node| : (current_node = node.next) { - const timer: *Timer = @alignCast(@fieldParentPtr("list_node", node)); - if (timer.ets_timer == ets_timer) { - return timer; +pub fn setfn( + gpa: std.mem.Allocator, + ets_timer: *c.ets_timer, + callback: CallbackFn, + arg: ?*anyopaque, +) !void { + { + mutex.lock(); + defer mutex.unlock(); + + if (find(ets_timer)) |tim| { + tim.callback = callback; + tim.arg = arg; + tim.deadline = .no_deadline; + } else { + const timer = try gpa.create(Timer); + timer.* = .{ + .ets_timer = ets_timer, + .callback = callback, + .arg = arg, + .deadline = .no_deadline, + .periodic = null, + }; + timer_list.prepend(&timer.node); } } - return null; + + reload_semaphore.give(); } -pub fn tick() void { - while (true) { - const now = hal.time.get_time_since_boot(); +pub fn arm( + ets_timer: *c.ets_timer, + duration: time.Duration, + repeat: bool, +) void { + { + mutex.lock(); + defer mutex.unlock(); - const maybe_call, const arg = blk: { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); + if (find(ets_timer)) |tim| { + tim.deadline = .init_relative(get_time_since_boot(), duration); + tim.periodic = if (repeat) duration else null; + } else { + std.log.warn("timer not found based on ets_timer", .{}); + } + } + + reload_semaphore.give(); +} + +pub fn disarm(ets_timer: *c.ets_timer) void { + mutex.lock(); + defer mutex.unlock(); - if (find_next_due(now)) |tim| { + if (find(ets_timer)) |tim| { + tim.deadline = .no_deadline; + } else { + std.log.warn("timer not found based on ets_timer", .{}); + } +} + +pub fn done(gpa: std.mem.Allocator, ets_timer: *c.ets_timer) void { + mutex.lock(); + defer mutex.unlock(); + + if (find(ets_timer)) |tim| { + timer_list.remove(&tim.node); + gpa.destroy(tim); + } else { + std.log.warn("timer not found based on ets_timer", .{}); + } +} + +fn task_fn() void { + while (true) { + const now = get_time_since_boot(); + while (true) { + const callback, const arg = blk: { + mutex.lock(); + defer mutex.unlock(); + + const tim = find_expired(now) orelse break; if (tim.periodic) |period| { tim.deadline = .init_relative(now, period); } else { - tim.deadline = .init_absolute(null); + tim.deadline = .no_deadline; } - break :blk .{ tim.callback, tim.arg }; - } else { - break :blk .{ null, null }; - } - }; + }; - if (maybe_call) |callback| { callback(arg); - } else { - break; } + + const sleep_duration = blk: { + mutex.lock(); + defer mutex.unlock(); + break :blk if (find_next_wake_absolute()) |next_wake_absolute| + next_wake_absolute.diff(now) + else + null; + }; + + reload_semaphore.take_with_timeout(sleep_duration) catch {}; } } -fn find_next_due(now: time.Absolute) ?*Timer { - var current_node = timer_list.first; - while (current_node) |node| : (current_node = node.next) { - const timer: *Timer = @alignCast(@fieldParentPtr("list_node", node)); +fn find(ets_timer: *c.ets_timer) ?*Timer { + var it = timer_list.first; + while (it) |node| : (it = node.next) { + const timer: *Timer = @alignCast(@fieldParentPtr("node", node)); + if (timer.ets_timer == ets_timer) { + return timer; + } + } + return null; +} + +fn find_expired(now: time.Absolute) ?*Timer { + var it = timer_list.first; + while (it) |node| : (it = node.next) { + const timer: *Timer = @alignCast(@fieldParentPtr("node", node)); if (timer.deadline.is_reached_by(now)) { return timer; } } return null; } + +fn find_next_wake_absolute() ?time.Absolute { + var it = timer_list.first; + var min_deadline: time.Deadline = .no_deadline; + while (it) |node| : (it = node.next) { + const timer: *Timer = @alignCast(@fieldParentPtr("node", node)); + if (@intFromEnum(timer.deadline.timeout) < @intFromEnum(min_deadline.timeout)) { + min_deadline = timer.deadline; + } + } + return if (min_deadline.can_be_reached()) min_deadline.timeout else null; +} diff --git a/port/espressif/esp/src/hal/radio/timer_new.zig b/port/espressif/esp/src/hal/radio/timer_new.zig deleted file mode 100644 index a44289890..000000000 --- a/port/espressif/esp/src/hal/radio/timer_new.zig +++ /dev/null @@ -1,174 +0,0 @@ -const std = @import("std"); -const log = std.log.scoped(.radio_timer); -const Allocator = std.mem.Allocator; - -const microzig = @import("microzig"); -const time = microzig.drivers.time; -const rtos = @import("../rtos.zig"); -const get_time_since_boot = @import("../time.zig").get_time_since_boot; - -const c = @import("esp-wifi-driver"); - -pub const CallbackFn = *const fn (?*anyopaque) callconv(.c) void; - -pub const Timer = struct { - ets_timer: *c.ets_timer, - callback: CallbackFn, - arg: ?*anyopaque, - deadline: time.Deadline, - periodic: ?time.Duration, - node: std.SinglyLinkedList.Node = .{}, -}; - -pub const Command = union(enum) { - exit, - setfn: struct { - ets_timer: *c.ets_timer, - func: CallbackFn, - arg: ?*anyopaque, - }, - arm: struct { - ets_timer: *c.ets_timer, - duration: time.Duration, - repeat: bool, - }, - disarm: *c.ets_timer, - done: *c.ets_timer, -}; - -var buf: [1]Command = undefined; -pub var command_queue: rtos.Queue(Command) = undefined; - -var timer_list: std.SinglyLinkedList = .{}; - -pub fn init(gpa: Allocator) Allocator.Error!void { - command_queue = .init(&buf); - - _ = try rtos.spawn(gpa, task_fn, .{gpa}, .{ - .priority = .lowest, - .stack_size = 8192, - }); -} - -pub fn deinit() void { - command_queue.put_one(.exit, null); - - // TODO: wait for task to exit -} - -fn task_fn(gpa: std.mem.Allocator) void { - while (true) { - const now = get_time_since_boot(); - while (find_expired(now)) |tim| { - if (tim.periodic) |period| { - tim.deadline = .init_relative(now, period); - } else { - tim.deadline = .no_deadline; - } - tim.callback(tim.arg); - } - - const sleep_duration = if (find_next_wake_absolute()) |next_wake_absolute| - next_wake_absolute.diff(now) - else - null; - - const command = command_queue.get_one(sleep_duration) catch continue; - switch (command) { - .exit => break, - .setfn => |setfn| { - if (find(setfn.ets_timer)) |tim| { - tim.callback = setfn.func; - tim.arg = setfn.arg; - tim.deadline = .no_deadline; - - // setfn.ets_timer.expire = 0; - } else { - // setfn.ets_timer.next = null; - // setfn.ets_timer.period = 0; - // setfn.ets_timer.func = null; - // setfn.ets_timer.priv = null; - - add(gpa, setfn.ets_timer, setfn.func, setfn.arg) catch { - std.log.warn("failed to allocate timer", .{}); - }; - } - }, - .arm => |arm| { - if (find(arm.ets_timer)) |tim| { - tim.deadline = .init_relative(get_time_since_boot(), arm.duration); - tim.periodic = if (arm.repeat) arm.duration else null; - } else { - std.log.warn("timer not found based on ets_timer", .{}); - } - }, - .disarm => |ets_timer| { - if (find(ets_timer)) |tim| { - tim.deadline = .no_deadline; - } else { - std.log.warn("timer not found based on ets_timer", .{}); - } - }, - .done => |ets_timer| { - if (find(ets_timer)) |tim| { - // ets_timer.priv = null; - // ets_timer.expire = 0; - remove(gpa, tim); - } else { - std.log.warn("timer not found based on ets_timer", .{}); - } - }, - } - } -} - -fn add(allocator: Allocator, ets_timer: *c.ets_timer, callback: CallbackFn, arg: ?*anyopaque) !void { - const timer = try allocator.create(Timer); - timer.* = .{ - .ets_timer = ets_timer, - .callback = callback, - .arg = arg, - .deadline = .no_deadline, - .periodic = null, - }; - timer_list.prepend(&timer.node); -} - -fn remove(allocator: Allocator, timer: *Timer) void { - timer_list.remove(&timer.node); - allocator.destroy(timer); -} - -fn find(ets_timer: *c.ets_timer) ?*Timer { - var it = timer_list.first; - while (it) |node| : (it = node.next) { - const timer: *Timer = @alignCast(@fieldParentPtr("node", node)); - if (timer.ets_timer == ets_timer) { - return timer; - } - } - return null; -} - -fn find_expired(now: time.Absolute) ?*Timer { - var it = timer_list.first; - while (it) |node| : (it = node.next) { - const timer: *Timer = @alignCast(@fieldParentPtr("node", node)); - if (timer.deadline.is_reached_by(now)) { - return timer; - } - } - return null; -} - -fn find_next_wake_absolute() ?time.Absolute { - var it = timer_list.first; - var min_deadline: time.Deadline = .no_deadline; - while (it) |node| : (it = node.next) { - const timer: *Timer = @alignCast(@fieldParentPtr("node", node)); - if (@intFromEnum(timer.deadline.timeout) < @intFromEnum(min_deadline.timeout)) { - min_deadline = timer.deadline; - } - } - return if (min_deadline.can_be_reached()) min_deadline.timeout else null; -} diff --git a/port/espressif/esp/src/hal/rtos.zig b/port/espressif/esp/src/hal/rtos.zig index 8b48f38b4..4b8849267 100644 --- a/port/espressif/esp/src/hal/rtos.zig +++ b/port/espressif/esp/src/hal/rtos.zig @@ -23,7 +23,7 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; // minimum stack size available at all times. // TODO: add identifier names to tasks -// TODO: stack usage report based on stack painting +// TODO: stack usage report based on stack painting and overflow detection // TODO: question: should idle do more stuff (like task garbage collection)? // TODO: implement task garbage collection and recycling // TODO: implement std.Io @@ -35,7 +35,7 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; // previous union state to avoid compiler bug (0.15.2) const STACK_ALIGN: std.mem.Alignment = .@"16"; -const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 31 * @sizeOf(usize)); +const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 32 * @sizeOf(usize)); const rtos_options = microzig.options.hal.rtos; pub const Priority = rtos_options.Priority; @@ -123,8 +123,9 @@ pub fn init() void { // TODO: deinit -fn idle() callconv(.naked) void { +fn idle() linksection(".ram_text") callconv(.naked) void { asm volatile ( + \\csrsi mstatus, 8 # enable interrupts \\1: \\wfi \\j 1b @@ -152,6 +153,9 @@ pub fn spawn( const TypeErased = struct { fn call() callconv(.c) void { + // interrupts are initially disabled in newly created tasks + microzig.cpu.interrupt.enable_interrupts(); + const context_ptr: *const Args = @ptrFromInt(args_align.forward(@intFromPtr(rtos_state.current_task) + @sizeOf(Task))); @call(.auto, function, context_ptr.*); @@ -220,15 +224,8 @@ pub const YieldAction = union(enum) { pub inline fn yield(action: YieldAction) void { const cs = enter_critical_section(); - yield_and_leave_cs(action, cs); -} + defer cs.leave(); -/// Must execute inside a critical section. Calling leave on the critical -/// section becomes unnecessary. -pub inline fn yield_and_leave_cs(action: YieldAction, cs: CriticalSection) void { - defer if (!cs.enable_on_leave) { - microzig.cpu.interrupt.disable_interrupts(); - }; const current_task, const next_task = yield_inner(action); context_switch(¤t_task.context, &next_task.context); } @@ -287,8 +284,6 @@ inline fn context_switch(prev_context: *Context, next_context: *Context) void { \\lw sp, 4(a1) # load next stack pointer \\lw s0, 8(a1) # load next frame pointer \\ - \\csrsi mstatus, 8 # enable interrupts - \\ \\jr a2 # jump to next task \\1: \\ @@ -350,7 +345,7 @@ pub const yield_interrupt_handler: microzig.cpu.InterruptHandler = .{ asm volatile ( \\ - \\addi sp, sp, -31*4 + \\addi sp, sp, -32*4 \\ \\sw ra, 0*4(sp) \\sw t0, 1*4(sp) @@ -415,14 +410,14 @@ pub const yield_interrupt_handler: microzig.cpu.InterruptHandler = .{ // change sp to the new task \\mv sp, a2 \\ - // if the next task program counter is equal to 1f's location just jump - // to it (ie. the task was interrupted). Technically not required but - // works as an optimization. + // if the next task program counter is equal to 1f's location + // just jump to it (ie. the task forcefully yielded). \\beq a1, s1, 1f \\ - // ensure interrupts get enabled after mret + // ensure interrupts are disabled after mret (when a normal + // context switch occured) \\li t0, 0x80 - \\csrs mstatus, t0 + \\csrc mstatus, t0 \\ // jump to new task \\csrw mepc, a1 @@ -466,7 +461,7 @@ pub const yield_interrupt_handler: microzig.cpu.InterruptHandler = .{ \\lw gp, 27*4(sp) \\lw tp, 28*4(sp) \\ - \\addi sp, sp, 31*4 + \\addi sp, sp, 32*4 \\mret : : [schedule_in_isr] "i" (&schedule_in_isr), @@ -570,7 +565,7 @@ pub const Task = struct { node: std.DoublyLinkedList.Node = .{}, /// Task specific semaphore (required by the wifi driver) - semaphore: Semaphore = .init(0), + semaphore: Semaphore = .init(0, 1), pub const State = union(enum) { none, @@ -751,7 +746,7 @@ pub const Mutex = struct { pub fn unlock(mutex: *Mutex) void { const cs = enter_critical_section(); - defer yield_and_leave_cs(.reschedule, cs); + defer cs.leave(); assert(mutex.state == .locked); mutex.state = .unlocked; @@ -796,30 +791,32 @@ pub const RecursiveMutex = struct { pub fn unlock(mutex: *RecursiveMutex) bool { const cs = enter_critical_section(); + defer cs.leave(); assert(mutex.value > 0); mutex.value -= 1; if (mutex.value <= 0) { - defer yield_and_leave_cs(.reschedule, cs); - mutex.owning_task = null; mutex.wait_queue.wake_one(); - return true; } else { - cs.leave(); return false; } } }; pub const Semaphore = struct { - value: u32, + current_value: u32, + max_value: u32, wait_queue: PriorityWaitQueue = .{}, - pub fn init(initial_value: u32) Semaphore { + pub fn init(initial_value: u32, max_value: u32) Semaphore { + assert(initial_value <= max_value); + assert(max_value > 0); + return .{ - .value = initial_value, + .current_value = initial_value, + .max_value = max_value, }; } @@ -836,7 +833,7 @@ pub const Semaphore = struct { else null; - while (sem.value <= 0) { + while (sem.current_value <= 0) { if (maybe_timeout_ticks) |timeout_ticks| if (timeout_ticks.is_reached()) return error.Timeout; @@ -844,15 +841,19 @@ pub const Semaphore = struct { sem.wait_queue.wait(rtos_state.current_task, maybe_timeout_ticks); } - sem.value -= 1; + sem.current_value -= 1; } pub fn give(sem: *Semaphore) void { const cs = enter_critical_section(); - defer yield_and_leave_cs(.reschedule, cs); + defer cs.leave(); - sem.value += 1; - sem.wait_queue.wake_one(); + sem.current_value += 1; + if (sem.current_value > sem.max_value) { + sem.current_value = sem.max_value; + } else { + sem.wait_queue.wake_one(); + } } }; @@ -892,7 +893,7 @@ pub const TypeErasedQueue = struct { var n: usize = 0; const cs = enter_critical_section(); - defer if (n > 0) yield_and_leave_cs(.reschedule, cs); + defer cs.leave(); while (true) { n += q.put_non_blocking_from_cs(elements[n..]); @@ -954,7 +955,7 @@ pub const TypeErasedQueue = struct { var n: usize = 0; const cs = enter_critical_section(); - defer if (n > 0) yield_and_leave_cs(.reschedule, cs); + defer cs.leave(); while (true) { n += q.get_non_blocking_from_cs(buffer[n..]); From db235ceed605084a79de284cabccc042f14804f7 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Mon, 12 Jan 2026 22:47:07 +0200 Subject: [PATCH 23/43] Port more changes from esp_wifi --- examples/espressif/esp/src/wifi.zig | 5 +- port/espressif/esp/src/hal/radio.zig | 52 +++++++++++----- port/espressif/esp/src/hal/radio/osi.zig | 72 ++++++++++------------ port/espressif/esp/src/hal/radio/timer.zig | 2 +- port/espressif/esp/src/hal/radio/wifi.zig | 8 ++- port/espressif/esp/src/hal/rtos.zig | 2 +- 6 files changed, 80 insertions(+), 61 deletions(-) diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index cb9d07c91..0c4323592 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -53,10 +53,7 @@ pub fn main() !void { var heap_allocator: microzig.Allocator = .init_with_buffer(&buffer); const gpa = heap_allocator.allocator(); - try radio.init(gpa); - defer radio.deinit(); - - try radio.wifi.init(); + try radio.wifi.init(gpa); defer radio.wifi.deinit(); c.lwip_init(); diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index 58b59a682..c57531f2b 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -1,14 +1,12 @@ +const builtin = @import("builtin"); const std = @import("std"); +const assert = std.debug.assert; const Allocator = std.mem.Allocator; const c = @import("esp-wifi-driver"); const microzig = @import("microzig"); const TrapFrame = microzig.cpu.TrapFrame; -const time = microzig.drivers.time; -const hal = microzig.hal; -const systimer = hal.systimer; const peripherals = microzig.chip.peripherals; -const SYSTEM = peripherals.SYSTEM; const RTC_CNTL = peripherals.RTC_CNTL; const APB_CTRL = peripherals.APB_CTRL; @@ -25,12 +23,25 @@ pub const Options = struct { wifi: wifi.Options = .{}, }; +var refcount: std.atomic.Value(usize) = .init(0); + pub fn init(gpa: Allocator) Allocator.Error!void { // TODO: check that clock frequency is higher or equal to 80mhz const radio_interrupt = microzig.options.hal.radio.interrupt; - comptime microzig.cpu.interrupt.expect_handler(radio_interrupt, interrupt_handler); + comptime { + if (!microzig.options.hal.rtos.enable) + @compileError("radio requires the rtos option to be enabled"); + + microzig.cpu.interrupt.expect_handler(radio_interrupt, interrupt_handler); + + osi.export_symbols(); + } + + if (refcount.rmw(.Add, 1, .monotonic) > 0) { + return; + } try timer.init(gpa); @@ -51,15 +62,24 @@ pub fn init(gpa: Allocator) Allocator.Error!void { log.debug("initialization complete", .{}); - // TODO: config - wifi.c_result(c.esp_wifi_internal_set_log_level(c.WIFI_LOG_VERBOSE)) catch { + const internal_wifi_log_level = switch (builtin.mode) { + .Debug => c.WIFI_LOG_VERBOSE, + else => c.WIFI_LOG_NONE, + }; + wifi.c_result(c.esp_wifi_internal_set_log_level(internal_wifi_log_level)) catch { log.warn("failed to set wifi internal log level", .{}); }; } -// TODO -// should free everything -pub fn deinit() void {} +pub fn deinit() void { + const prev_count = refcount.rmw(.Sub, 1, .monotonic); + assert(prev_count != 0); + if (prev_count != 1) { + return; + } + + timer.deinit(); +} pub fn read_mac(iface: enum { sta, @@ -132,11 +152,13 @@ fn enable_wifi_power_domain_and_init_clocks() void { pub const interrupt_handler: microzig.cpu.InterruptHandler = .{ .c = struct { fn handler_fn(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { - // TODO: multiplex the wifi and bluetooth handlers - if (osi.wifi_interrupt_handler) |handler| { - handler.f(handler.arg); - } else { - // should be unreachable + const status: microzig.cpu.interrupt.Status = .init(); + if (status.is_set(.wifi_mac) or status.is_set(.wifi_pwr)) { + if (osi.wifi_interrupt_handler) |handler| { + handler.f(handler.arg); + } else { + // should be unreachable + } } } }.handler_fn, diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 73d4e949c..7cdac7551 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -1,22 +1,23 @@ -const builtin = @import("builtin"); const std = @import("std"); -const log = std.log.scoped(.esp_radio_osi); +const builtin = @import("builtin"); +const c = @import("esp-wifi-driver"); const microzig = @import("microzig"); const enter_critical_section = microzig.interrupt.enter_critical_section; const time = microzig.drivers.time; const peripherals = microzig.chip.peripherals; const APB_CTRL = peripherals.APB_CTRL; -const hal = microzig.hal; +const radio_interrupt = microzig.options.hal.radio.interrupt; const radio = @import("../radio.zig"); +const rng = @import("../rng.zig"); const rtos = @import("../rtos.zig"); const systimer = @import("../systimer.zig"); const get_time_since_boot = @import("../time.zig").get_time_since_boot; const timer = @import("timer.zig"); const wifi = @import("wifi.zig"); -const c = @import("esp-wifi-driver"); +const log = std.log.scoped(.esp_radio_osi); // TODO: config const coex_enabled: bool = false; @@ -28,8 +29,6 @@ pub var wifi_interrupt_handler: ?struct { arg: ?*anyopaque, } = undefined; -const radio_interrupt = microzig.options.hal.radio.interrupt; - extern fn vsnprintf(buffer: [*c]u8, len: usize, fmt: [*c]const u8, va_list: std.builtin.VaList) callconv(.c) void; const log_esp_wifi_driver_internal = std.log.scoped(.esp_wifi_driver_internal); @@ -158,9 +157,20 @@ pub fn vTaskDelay(ticks: u32) callconv(.c) void { rtos.sleep(.from_us(ticks)); } -comptime { - // provide some weak links so they can be overriten +pub var WIFI_EVENT: c.esp_event_base_t = "WIFI_EVENT"; +pub fn printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { + syslog(fmt, @cVaStart()); +} + +pub fn esp_fill_random(buf: [*c]u8, len: usize) callconv(.c) void { + log.debug("esp_fill_random {any} {}", .{ buf, len }); + + rng.read(buf[0..len]); +} + +/// Some are weaklinks which can be overriten. +pub fn export_symbols() void { @export(&strlen, .{ .name = "strlen", .linkage = .weak }); @export(&strnlen, .{ .name = "strnlen", .linkage = .weak }); @export(&strrchr, .{ .name = "strrchr", .linkage = .weak }); @@ -176,34 +186,18 @@ comptime { @export(&sleep, .{ .name = "sleep", .linkage = .weak }); @export(&usleep, .{ .name = "usleep", .linkage = .weak }); @export(&vTaskDelay, .{ .name = "vTaskDelay", .linkage = .weak }); -} - -pub export var WIFI_EVENT: c.esp_event_base_t = "WIFI_EVENT"; -pub export fn rtc_printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { - syslog(fmt, @cVaStart()); -} - -pub export fn phy_printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { - syslog(fmt, @cVaStart()); -} - -pub export fn coexist_printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { - syslog(fmt, @cVaStart()); -} - -pub export fn net80211_printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { - syslog(fmt, @cVaStart()); -} - -pub export fn pp_printf(fmt: ?[*:0]const u8, ...) callconv(.c) void { - syslog(fmt, @cVaStart()); -} - -pub export fn esp_fill_random(buf: [*c]u8, len: usize) callconv(.c) void { - log.debug("esp_fill_random {any} {}", .{ buf, len }); - - hal.rng.read(buf[0..len]); + @export(&WIFI_EVENT, .{ .name = "WIFI_EVENT" }); + inline for (&.{ + "rtc_printf", + "phy_printf", + "coexist_printf", + "net80211_printf", + "pp_printf", + }) |name| { + @export(&printf, .{ .name = name }); + } + @export(&esp_fill_random, .{ .name = "esp_fill_random" }); } // ----- end of exports ----- @@ -651,7 +645,7 @@ pub fn get_free_heap_size() callconv(.c) void { } pub fn rand() callconv(.c) u32 { - return hal.rng.random_u32(); + return rng.random_u32(); } pub fn dport_access_stall_other_cpu_start_wrap() callconv(.c) void { @@ -851,7 +845,7 @@ pub fn phy_update_country_info(country: [*c]const u8) callconv(.c) c_int { pub fn read_mac(mac: [*c]u8, typ: c_uint) callconv(.c) c_int { log.debug("read_mac {*} {}", .{ mac, typ }); - const mac_tmp: [6]u8 = hal.radio.read_mac(switch (typ) { + const mac_tmp: [6]u8 = radio.read_mac(switch (typ) { 0 => .sta, 1 => .ap, 2 => .bt, @@ -979,7 +973,7 @@ pub fn nvs_erase_key() callconv(.c) void { } pub fn get_random(buf: [*c]u8, len: usize) callconv(.c) c_int { - hal.rng.read(buf[0..len]); + rng.read(buf[0..len]); return 0; } @@ -988,7 +982,7 @@ pub fn get_time() callconv(.c) void { } pub fn random() callconv(.c) c_ulong { - return hal.rng.random_u32(); + return rng.random_u32(); } pub fn slowclk_cal_get() callconv(.c) u32 { diff --git a/port/espressif/esp/src/hal/radio/timer.zig b/port/espressif/esp/src/hal/radio/timer.zig index 89cf51f7b..afc604ec8 100644 --- a/port/espressif/esp/src/hal/radio/timer.zig +++ b/port/espressif/esp/src/hal/radio/timer.zig @@ -26,7 +26,7 @@ var timer_list: std.SinglyLinkedList = .{}; pub fn init(gpa: Allocator) Allocator.Error!void { _ = try rtos.spawn(gpa, task_fn, .{}, .{ - .priority = .highest, + .priority = .highest, // TODO: what should the priority be? .stack_size = 8192, }); } diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index dff11044a..fa7739359 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -19,11 +19,15 @@ pub const Options = struct { tx_queue_len: usize = 15, }; -pub fn init() InternalError!void { +const InitError = std.mem.Allocator.Error || InternalError; + +pub fn init(gpa: std.mem.Allocator) InitError!void { if (inited) { @panic("wifi already initialized"); } + try radio.init(gpa); + init_config.wpa_crypto_funcs = c.g_wifi_default_wpa_crypto_funcs; init_config.feature_caps = g_wifi_feature_caps; @@ -68,6 +72,8 @@ pub fn deinit() void { _ = c.esp_wifi_deinit_internal(); _ = c.esp_supplicant_deinit(); + radio.deinit(); + inited = false; } diff --git a/port/espressif/esp/src/hal/rtos.zig b/port/espressif/esp/src/hal/rtos.zig index 4b8849267..5cd6aa14f 100644 --- a/port/espressif/esp/src/hal/rtos.zig +++ b/port/espressif/esp/src/hal/rtos.zig @@ -88,7 +88,7 @@ pub const RTOS_State = struct { pub fn init() void { comptime { if (!microzig.options.cpu.interrupt_stack.enable) - @compileError("Please enable interrupt stack to use the rtos"); + @compileError("rtos requires the interrupt stack cpu option to be enabled"); microzig.cpu.interrupt.expect_handler(rtos_options.general_purpose_interrupt, general_purpose_interrupt_handler); microzig.cpu.interrupt.expect_handler(rtos_options.yield_interrupt, yield_interrupt_handler); } From e64cf8c48248eceae9b5aebd52e47402664c8a78 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Tue, 13 Jan 2026 17:40:47 +0200 Subject: [PATCH 24/43] Refactor wifi and use tcpip for lwip --- examples/espressif/esp/build.zig | 43 +-- examples/espressif/esp/build.zig.zon | 5 +- examples/espressif/esp/src/include/arch/cc.h | 31 -- examples/espressif/esp/src/include/lwip.h | 9 - examples/espressif/esp/src/include/lwipopts.h | 67 ----- examples/espressif/esp/src/lwip/exports.zig | 146 ++++++++++ examples/espressif/esp/src/lwip/include.zig | 12 + .../espressif/esp/src/lwip/include/arch/cc.h | 41 +++ .../esp/src/lwip/include/arch/sys_arch.h | 17 ++ .../espressif/esp/src/lwip/include/lwipopts.h | 37 +++ examples/espressif/esp/src/wifi.zig | 147 +++++----- modules/lwip/build.zig | 50 ++-- modules/riscv32-common/src/riscv32_common.zig | 2 + port/espressif/esp/src/hal/radio/osi.zig | 16 +- port/espressif/esp/src/hal/radio/timer.zig | 1 + port/espressif/esp/src/hal/radio/wifi.zig | 266 ++++++++++++------ port/espressif/esp/src/hal/rtos.zig | 68 ++++- 17 files changed, 615 insertions(+), 343 deletions(-) delete mode 100644 examples/espressif/esp/src/include/arch/cc.h delete mode 100644 examples/espressif/esp/src/include/lwip.h delete mode 100644 examples/espressif/esp/src/include/lwipopts.h create mode 100644 examples/espressif/esp/src/lwip/exports.zig create mode 100644 examples/espressif/esp/src/lwip/include.zig create mode 100644 examples/espressif/esp/src/lwip/include/arch/cc.h create mode 100644 examples/espressif/esp/src/lwip/include/arch/sys_arch.h create mode 100644 examples/espressif/esp/src/lwip/include/lwipopts.h diff --git a/examples/espressif/esp/build.zig b/examples/espressif/esp/build.zig index af2e168e9..4463c65e4 100644 --- a/examples/espressif/esp/build.zig +++ b/examples/espressif/esp/build.zig @@ -56,47 +56,24 @@ pub fn build(b: *std.Build) void { }); if (example.features.lwip) { - const resolved_zig_target = b.resolveTargetQuery(firmware.target.zig_target); + const target = b.resolveTargetQuery(firmware.target.zig_target); - const foundation_libc_dep = b.dependency("foundation_libc", .{ + const foundation_dep = b.dependency("foundation_libc", .{ + .target = target, .optimize = optimize, - .target = resolved_zig_target, }); - const lwip_dep = b.dependency("lwip", .{}); - const lwip_lib = b.addLibrary(.{ - .name = "lwip", - .root_module = b.createModule(.{ - .optimize = optimize, - .target = resolved_zig_target, - .link_libc = false, - }), - .linkage = .static, - }); - - lwip_lib.addCSourceFiles(.{ - .root = lwip_dep.path("src"), - .files = &lwip_files, - .flags = &lwip_flags, + const lwip_dep = b.dependency("lwip", .{ + .target = target, + .optimize = optimize, + .include_dir = b.path("src/lwip/include"), }); - lwip_lib.linkLibrary(foundation_libc_dep.artifact("foundation")); - - lwip_lib.addIncludePath(b.path("src/include")); - lwip_lib.addIncludePath(lwip_dep.path("src/include")); + const libc_lib = foundation_dep.artifact("foundation"); + const lwip_lib = lwip_dep.artifact("lwip"); + lwip_lib.root_module.linkLibrary(libc_lib); firmware.app_mod.linkLibrary(lwip_lib); - - const lwip_translate_c = b.addTranslateC(.{ - .root_source_file = b.path("src/include/lwip.h"), - .target = resolved_zig_target, - .optimize = optimize, - .link_libc = false, - }); - lwip_translate_c.addIncludePath(b.path("src/include")); - lwip_translate_c.addIncludePath(lwip_dep.path("src/include")); - - firmware.app_mod.addImport("lwip", lwip_translate_c.createModule()); } // `installFirmware()` is the MicroZig pendant to `Build.installArtifact()` diff --git a/examples/espressif/esp/build.zig.zon b/examples/espressif/esp/build.zig.zon index 850bacd26..3b85b142c 100644 --- a/examples/espressif/esp/build.zig.zon +++ b/examples/espressif/esp/build.zig.zon @@ -4,10 +4,7 @@ .version = "0.0.0", .dependencies = .{ .microzig = .{ .path = "../../.." }, - .lwip = .{ - .url = "https://download.savannah.nongnu.org/releases/lwip/lwip-2.2.1.zip", - .hash = "N-V-__8AAFwFGgFuXsdQSjgPXt482oovnKYU_8Rm-Ek5DbAQ", - }, + .lwip = .{ .path = "../../../modules/lwip" }, .foundation_libc = .{ .path = "../../../modules/foundation-libc" }, }, .paths = .{ diff --git a/examples/espressif/esp/src/include/arch/cc.h b/examples/espressif/esp/src/include/arch/cc.h deleted file mode 100644 index 1387dfd67..000000000 --- a/examples/espressif/esp/src/include/arch/cc.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef LWIP_ARCH_CC_H -#define LWIP_ARCH_CC_H - -#include -#include - -extern uint32_t rand(void); - -#define LWIP_PLATFORM_DIAG(x) // TODO: Implement these -#define LWIP_PLATFORM_ASSERT(x) // TODO: Implement these - -#define BYTE_ORDER LITTLE_ENDIAN - -#define LWIP_RAND() (rand()) - -#define LWIP_NO_STDDEF_H 0 -#define LWIP_NO_STDINT_H 0 -#define LWIP_NO_INTTYPES_H 1 -#define LWIP_NO_LIMITS_H 0 -#define LWIP_NO_CTYPE_H 1 - -#define LWIP_UNUSED_ARG(x) (void)x -#define LWIP_PROVIDE_ERRNO 1 - -/* ---------- No Protection Needed ---------- */ - -#define SYS_ARCH_DECL_PROTECT(lev) -#define SYS_ARCH_PROTECT(lev) -#define SYS_ARCH_UNPROTECT(lev) - -#endif /* LWIP_ARCH_CC_H */ diff --git a/examples/espressif/esp/src/include/lwip.h b/examples/espressif/esp/src/include/lwip.h deleted file mode 100644 index 61ac15f47..000000000 --- a/examples/espressif/esp/src/include/lwip.h +++ /dev/null @@ -1,9 +0,0 @@ -#include "lwip/init.h" -#include "lwip/tcpip.h" -#include "lwip/netif.h" -#include "lwip/dhcp.h" -#include "lwip/tcp.h" -#include "lwip/udp.h" -#include "lwip/etharp.h" -#include "lwip/ethip6.h" -#include "lwip/timeouts.h" diff --git a/examples/espressif/esp/src/include/lwipopts.h b/examples/espressif/esp/src/include/lwipopts.h deleted file mode 100644 index 78b3de828..000000000 --- a/examples/espressif/esp/src/include/lwipopts.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef LWIPOPTS_H -#define LWIPOPTS_H - -/* ---------- System options ---------- */ -#define NO_SYS 1 -#define SYS_LIGHTWEIGHT_PROT 0 - -/* ---------- Memory options ---------- */ -#define MEM_LIBC_MALLOC 1 // Don't use malloc() -#define MEM_USE_POOLS 0 // No custom pools -#define MEM_USE_POOLS_TRY_BIGGER_POOL 0 -#define MEMP_MEM_MALLOC 0 // Don't malloc pools -#define MEM_ALIGNMENT 4 // 32-bit CPU alignment -#define MEM_SIZE 0 // No dynamic heap - -/* ---------- PBUF options ---------- */ -#define PBUF_POOL_SIZE 8 // Number of static pbufs -#define PBUF_POOL_BUFSIZE 512 // Size of each pbuf -#define PBUF_POOL_ALLOC_SRC PBUF_ALLOC_SRC_INTERNAL - -/* ---------- Raw API ---------- */ -#define LWIP_RAW 1 -#define LWIP_NETCONN 0 -#define LWIP_SOCKET 0 - -#define LWIP_IPV4 1 -#define LWIP_IPV6 1 - -#define LINK_STATS 0 - -#define LWIP_NETIF_LINK_CALLBACK 1 -#define LWIP_NETIF_STATUS_CALLBACK 1 -#define LWIP_NETIF_EXT_STATUS_CALLBACK 1 - -/* ---------- Protocol Support ---------- */ -#define LWIP_ARP 1 -#define LWIP_ICMP 1 -#define LWIP_DHCP 1 // Optional -#define LWIP_DNS 1 // Optional -#define LWIP_TCP 1 -#define LWIP_UDP 1 - -/* ---------- TCP options ---------- */ -#define MEMP_NUM_TCP_PCB 4 -#define MEMP_NUM_TCP_SEG 16 -#define TCP_MSS 1460 -#define TCP_SND_BUF (2 * TCP_MSS) -#define TCP_WND (2 * TCP_MSS) - -/* ---------- UDP options ---------- */ -#define MEMP_NUM_UDP_PCB 2 - -/* ---------- Other MEMP pool sizes ---------- */ -#define MEMP_NUM_PBUF 16 -#define MEMP_NUM_NETCONN 0 -#define MEMP_NUM_SYS_TIMEOUT 12 // Required for timers like DHCP - -/* ---------- DNS ---------- */ -#define MEMP_NUM_NETBUF 0 -#define MEMP_NUM_API_MSG 0 -#define MEMP_NUM_TCPIP_MSG_API 0 -#define MEMP_NUM_TCPIP_MSG_INPKT 0 - -/* ---------- Debug options ---------- */ -#define LWIP_DEBUG 0 - -#endif /* LWIPOPTS_H */ diff --git a/examples/espressif/esp/src/lwip/exports.zig b/examples/espressif/esp/src/lwip/exports.zig new file mode 100644 index 000000000..3b10a58d7 --- /dev/null +++ b/examples/espressif/esp/src/lwip/exports.zig @@ -0,0 +1,146 @@ +/// Platform dependent exports required by lwip +/// +const std = @import("std"); +const microzig = @import("microzig"); +const esp = microzig.hal; +const rtos = esp.rtos; + +const log = std.log.scoped(.lwip); +const assert = std.debug.assert; + +const c = @import("include.zig").c; + +// TODO: we use a different allocator here than the one in osi. maybe we can +// have only one. +// TODO: mutex and mailbox can be implemented without allocations if we define +// the correct types in sys_arch.h + +pub var gpa: std.mem.Allocator = undefined; +pub var core_mutex: rtos.Mutex = .{}; + +export fn lwip_lock_core_mutex() void { + core_mutex.lock(); +} + +export fn lwip_unlock_core_mutex() void { + core_mutex.unlock(); +} + +export fn lwip_rand() u32 { + return esp.rng.random_u32(); +} + +export fn lwip_assert(msg: [*c]const u8, file: [*c]const u8, line: c_int) void { + log.err("assert: {s} in file: {s}, line: {}", .{ msg, file, line }); + @panic("lwip assert"); +} + +export fn lwip_diag(msg: [*c]const u8, file: [*c]const u8, line: c_int) void { + log.debug("{s} in file: {s}, line: {}", .{ msg, file, line }); +} + +export fn sys_now() u32 { + const ts = esp.time.get_time_since_boot(); + return @truncate(ts.to_us() / 1000); +} + +export fn sys_init() void {} + +export fn sys_mutex_new(ptr: *c.sys_mutex_t) c.err_t { + const mutex = gpa.create(rtos.Mutex) catch { + log.warn("failed to allocate mutex", .{}); + return c.ERR_MEM; + }; + mutex.* = .{}; + ptr.* = mutex; + return c.ERR_OK; +} + +export fn sys_mutex_free(ptr: *c.sys_mutex_t) void { + const mutex: *rtos.Mutex = @ptrCast(@alignCast(ptr.*)); + gpa.destroy(mutex); +} + +export fn sys_mutex_lock(ptr: *c.sys_mutex_t) void { + const mutex: *rtos.Mutex = @ptrCast(@alignCast(ptr.*)); + mutex.lock(); +} + +export fn sys_mutex_unlock(ptr: *c.sys_mutex_t) void { + const mutex: *rtos.Mutex = @ptrCast(@alignCast(ptr.*)); + mutex.unlock(); +} + +const MailboxElement = ?*anyopaque; +const Mailbox = rtos.Queue(MailboxElement); + +export fn sys_mbox_new(ptr: *c.sys_mbox_t, size: i32) c.err_t { + const buffer = gpa.alloc(MailboxElement, @intCast(size)) catch { + log.warn("failed to allocate mailbox buffer", .{}); + return c.ERR_MEM; + }; + + const mailbox = gpa.create(Mailbox) catch { + log.warn("failed to allocate mailbox", .{}); + gpa.free(buffer); + return c.ERR_MEM; + }; + mailbox.* = .init(buffer); + + ptr.* = mailbox; + return c.ERR_OK; +} + +export fn sys_mbox_free(ptr: *c.sys_mbox_t) void { + const mailbox: *Mailbox = @ptrCast(@alignCast(ptr.*)); + gpa.free(@as([]MailboxElement, @ptrCast(@alignCast(mailbox.type_erased.buffer)))); + gpa.destroy(mailbox); + ptr.* = null; +} + +export fn sys_mbox_valid(ptr: *c.sys_mbox_t) i32 { + return @intFromBool(ptr.* != null); +} + +export fn sys_mbox_post(ptr: *c.sys_mbox_t, element: MailboxElement) void { + const mailbox: *Mailbox = @ptrCast(@alignCast(ptr.*)); + mailbox.put_one(element, null) catch unreachable; +} + +export fn sys_mbox_trypost(ptr: *c.sys_mbox_t, element: MailboxElement) c.err_t { + const mailbox: *Mailbox = @ptrCast(@alignCast(ptr.*)); + if (mailbox.put_one_non_blocking(element)) { + return c.ERR_OK; + } else { + return c.ERR_MEM; + } +} + +comptime { + @export(&sys_mbox_trypost, .{ .name = "sys_mbox_trypost_fromisr" }); +} + +export fn sys_arch_mbox_fetch(ptr: *c.sys_mbox_t, element_ptr: *MailboxElement, timeout: u32) u32 { + const mailbox: *Mailbox = @ptrCast(@alignCast(ptr.*)); + element_ptr.* = mailbox.get_one(if (timeout != 0) .from_ms(timeout) else null) catch { + return c.SYS_ARCH_TIMEOUT; + }; + return 0; +} + +fn task_wrapper( + task_entry: c.lwip_thread_fn, + param: ?*anyopaque, +) void { + task_entry.?(param); +} + +export fn sys_thread_new(name: [*:0]u8, thread: c.lwip_thread_fn, arg: ?*anyopaque, stacksize: c_int, prio: c_int) c.sys_thread_t { + _ = stacksize; // autofix + _ = prio; // autofix + return rtos.spawn(gpa, task_wrapper, .{thread, arg}, .{ + .name = std.mem.span(name), + .stack_size = 8192, + .priority = @enumFromInt(2), + }) catch @panic("failed to allocate lwip task"); +} diff --git a/examples/espressif/esp/src/lwip/include.zig b/examples/espressif/esp/src/lwip/include.zig new file mode 100644 index 000000000..a5c25869e --- /dev/null +++ b/examples/espressif/esp/src/lwip/include.zig @@ -0,0 +1,12 @@ +pub const c = @cImport({ + @cInclude("lwip/init.h"); + @cInclude("lwip/tcpip.h"); + @cInclude("lwip/netifapi.h"); + @cInclude("lwip/netif.h"); + @cInclude("lwip/dhcp.h"); + @cInclude("lwip/tcp.h"); + @cInclude("lwip/udp.h"); + @cInclude("lwip/etharp.h"); + @cInclude("lwip/ethip6.h"); + @cInclude("lwip/timeouts.h"); +}); diff --git a/examples/espressif/esp/src/lwip/include/arch/cc.h b/examples/espressif/esp/src/lwip/include/arch/cc.h new file mode 100644 index 000000000..df5207f4c --- /dev/null +++ b/examples/espressif/esp/src/lwip/include/arch/cc.h @@ -0,0 +1,41 @@ +#ifndef lwip__cc_h +#define lwip__cc_h + +#include +#include + +typedef unsigned int sys_prot_t; + +extern uint32_t lwip_rand(void); +extern void lwip_lock_core_mutex(); +extern void lwip_unlock_core_mutex(); +extern void lwip_assert(const char *msg, const char *file, int line); +extern void lwip_diag(const char *msg, const char *file, int line); + +#define LWIP_PLATFORM_DIAG(x) \ + do { \ + lwip_diag((msg), __FILE__, __LINE__); \ + } while (0) +#define LWIP_PLATFORM_ASSERT(msg) \ + do { \ + lwip_assert((msg), __FILE__, __LINE__); \ + } while (0) + +#define BYTE_ORDER LITTLE_ENDIAN + +#define LWIP_RAND() ((u32_t)lwip_rand()) + +#define LWIP_NO_STDDEF_H 0 +#define LWIP_NO_STDINT_H 0 +#define LWIP_NO_INTTYPES_H 1 +#define LWIP_NO_LIMITS_H 0 +#define LWIP_NO_CTYPE_H 1 + +#define LWIP_UNUSED_ARG(x) (void)x +#define LWIP_PROVIDE_ERRNO 0 + +#define SYS_ARCH_DECL_PROTECT(lev) sys_prot_t lev +#define SYS_ARCH_PROTECT(lev) lwip_lock_core_mutex() +#define SYS_ARCH_UNPROTECT(lev) lwip_unlock_core_mutex() + +#endif // lwip__cc_h diff --git a/examples/espressif/esp/src/lwip/include/arch/sys_arch.h b/examples/espressif/esp/src/lwip/include/arch/sys_arch.h new file mode 100644 index 000000000..554399ace --- /dev/null +++ b/examples/espressif/esp/src/lwip/include/arch/sys_arch.h @@ -0,0 +1,17 @@ +#ifndef __SYS_ARCH_H__ +#define __SYS_ARCH_H__ + +#include + +typedef void* sys_sem_t; +typedef void* sys_mutex_t; +typedef void* sys_mbox_t; +typedef void* sys_thread_t; + +typedef uint32_t sys_prot_t; + +#define SYS_MBOX_NULL NULL +#define SYS_SEM_NULL NULL +#define SYS_MUTEX_NULL NULL + +#endif /* __SYS_ARCH_H__ */ diff --git a/examples/espressif/esp/src/lwip/include/lwipopts.h b/examples/espressif/esp/src/lwip/include/lwipopts.h new file mode 100644 index 000000000..badf58e86 --- /dev/null +++ b/examples/espressif/esp/src/lwip/include/lwipopts.h @@ -0,0 +1,37 @@ +#ifndef LWIP_LWIPOPTS_H +#define LWIP_LWIPOPTS_H + +#define NO_SYS 0 +#define SYS_LIGHTWEIGHT_PROT 1 + +#define LWIP_TCPIP_CORE_LOCKING 1 +#define LWIP_TCPIP_CORE_LOCKING_INPUT 1 + +#define LWIP_IPV4 1 +#define LWIP_IPV6 1 +#define LWIP_UDP 1 +#define LWIP_TCP 1 +#define LWIP_DHCP 1 +#define LWIP_IGMP LWIP_IPV4 +#define LWIP_ICMP LWIP_IPV4 +#define LWIP_DNS LWIP_UDP +#define LWIP_MDNS_RESPONDER LWIP_UDP + +#define MEM_LIBC_MALLOC 1 +#define MEMP_MEM_MALLOC 1 +#define MEM_ALIGNMENT 4 + +#define TCPIP_THREAD_NAME "lwip_tcpip" +// #define TCPIP_THREAD_STACKSIZE 1024 +// #define TCPIP_THREAD_PRIO 3 + +#define TCPIP_MBOX_SIZE 16 +#define DEFAULT_RAW_RECVMBOX_SIZE 16 +#define DEFAULT_UDP_RECVMBOX_SIZE 16 +#define DEFAULT_TCP_RECVMBOX_SIZE 16 +#define DEFAULT_ACCEPTMBOX_SIZE 16 + +#define LWIP_NETIF_API 1 +#define LWIP_NETIF_STATUS_CALLBACK 1 + +#endif diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index 0c4323592..7555184db 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -1,13 +1,16 @@ const std = @import("std"); const microzig = @import("microzig"); -const SPSC_Queue = microzig.concurrency.SPSC_Queue; const interrupt = microzig.cpu.interrupt; const hal = microzig.hal; const rtos = hal.rtos; const radio = hal.radio; const usb_serial_jtag = hal.usb_serial_jtag; -const c = @import("lwip"); +const lwip = @import("lwip/include.zig"); +const exports = @import("lwip/exports.zig"); +comptime { + _ = exports; +} pub const microzig_options: microzig.Options = .{ .log_level = .debug, @@ -44,29 +47,26 @@ pub const microzig_options: microzig.Options = .{ .rtos = .{ .enable = true, }, + .radio = .{ + .wifi = .{ + .on_event = on_event, + .on_packet_received = on_packet_received, + }, + }, }, }; -var buffer: [70 * 1024]u8 = undefined; +var netif: lwip.c.netif = undefined; pub fn main() !void { - var heap_allocator: microzig.Allocator = .init_with_buffer(&buffer); + var heap_allocator: microzig.Allocator = .init_with_heap(8192); const gpa = heap_allocator.allocator(); try radio.wifi.init(gpa); defer radio.wifi.deinit(); - c.lwip_init(); - - var netif: c.netif = undefined; - _ = c.netif_add(&netif, @ptrCast(c.IP4_ADDR_ANY), @ptrCast(c.IP4_ADDR_ANY), @ptrCast(c.IP4_ADDR_ANY), null, netif_init, c.netif_input); - @memcpy(&netif.name, "e0"); - c.netif_create_ip6_linklocal_address(&netif, 1); - netif.ip6_autoconfig_enabled = 1; - c.netif_set_status_callback(&netif, netif_status_callback); - c.netif_set_default(&netif); - c.netif_set_up(&netif); - _ = c.dhcp_start(&netif); + exports.gpa = gpa; + _ = lwip.c.tcpip_init(init_done, null); try radio.wifi.apply(.{ .sta = .{ @@ -77,76 +77,75 @@ pub fn main() !void { try radio.wifi.start(); try radio.wifi.connect(); - var connected: bool = false; - var last_mem_show = hal.time.get_time_since_boot(); - + var count: usize = 0; while (true) { - const sta_state = radio.wifi.get_sta_state(); - if (!connected and sta_state == .sta_connected) { - std.log.info("link up", .{}); - c.netif_set_link_up(&netif); - connected = true; - } else if (connected and sta_state == .sta_disconnected) { - std.log.info("link down", .{}); - c.netif_set_link_down(&netif); - connected = false; - } - - while (radio.wifi.recv_packet(.sta)) |packet| { - defer packet.deinit(); - - const maybe_pbuf: ?*c.struct_pbuf = c.pbuf_alloc(c.PBUF_RAW, @intCast(packet.data.len), c.PBUF_POOL); - if (maybe_pbuf) |pbuf| { - _ = c.pbuf_take(pbuf, packet.data.ptr, @intCast(packet.data.len)); - defer _ = c.pbuf_free(pbuf); - - if (c.netif_input(pbuf, &netif) != c.ERR_OK) { - std.log.warn("lwip netif input failed", .{}); - } - } - } + const free_heap = heap_allocator.free_heap(); + std.log.info("{} free memory: {}K ({})", .{ count, free_heap / 1024, free_heap }); + count += 1; + rtos.sleep(.from_ms(1000)); + } +} - c.sys_check_timeouts(); +fn on_event(e: radio.wifi.EventType) void { + switch (e) { + .StaConnected => _ = lwip.c.netifapi_netif_common(&netif, lwip.c.netif_set_link_up, null), + .StaDisconnected => _ = lwip.c.netifapi_netif_common(&netif, lwip.c.netif_set_link_down, null), + else => {} + } +} - const now = hal.time.get_time_since_boot(); - if (!now.diff(last_mem_show).less_than(.from_ms(1000))) { - const free_heap = heap_allocator.free_heap(); - std.log.info("free memory: {}K ({})", .{ free_heap / 1024, free_heap }); - last_mem_show = now; +fn on_packet_received(comptime _: radio.wifi.Interface, data: []const u8) void { + const maybe_pbuf: ?*lwip.c.struct_pbuf = lwip.c.pbuf_alloc(lwip.c.PBUF_RAW, @intCast(data.len), lwip.c.PBUF_POOL); + if (maybe_pbuf) |pbuf| { + _ = lwip.c.pbuf_take(pbuf, data.ptr, @intCast(data.len)); + if (lwip.c.tcpip_input(pbuf, &netif) != lwip.c.ERR_OK) { + std.log.warn("lwip netif input failed", .{}); } - - rtos.sleep(.from_ms(10)); } } -fn netif_init(netif_c: [*c]c.struct_netif) callconv(.c) c.err_t { - const netif: *c.struct_netif = netif_c; +fn init_done(_: ?*anyopaque) callconv(.c) void { + _ = lwip.c.netif_add( + &netif, + @ptrCast(lwip.c.IP4_ADDR_ANY), + @ptrCast(lwip.c.IP4_ADDR_ANY), + @ptrCast(lwip.c.IP4_ADDR_ANY), + null, + netif_init, + lwip.c.tcpip_input, + ); + _ = lwip.c.dhcp_start(&netif); +} +fn netif_init(_: [*c]lwip.c.struct_netif) callconv(.c) lwip.c.err_t { + @memcpy(&netif.name, "e0"); netif.linkoutput = netif_output; - netif.output = c.etharp_output; - netif.output_ip6 = c.ethip6_output; + netif.output = lwip.c.etharp_output; + netif.output_ip6 = lwip.c.ethip6_output; netif.mtu = 1500; - netif.flags = c.NETIF_FLAG_BROADCAST | c.NETIF_FLAG_ETHARP | c.NETIF_FLAG_ETHERNET | c.NETIF_FLAG_IGMP | c.NETIF_FLAG_MLD6; + netif.flags = lwip.c.NETIF_FLAG_BROADCAST | lwip.c.NETIF_FLAG_ETHARP | lwip.c.NETIF_FLAG_ETHERNET | lwip.c.NETIF_FLAG_IGMP | lwip.c.NETIF_FLAG_MLD6; @memcpy(&netif.hwaddr, &radio.read_mac(.sta)); netif.hwaddr_len = 6; + lwip.c.netif_create_ip6_linklocal_address(&netif, 1); + lwip.c.netif_set_status_callback(&netif, netif_status_callback); + lwip.c.netif_set_default(&netif); + lwip.c.netif_set_up(&netif); - return c.ERR_OK; + return lwip.c.ERR_OK; } -var packet_buf: [1500]u8 = undefined; - -fn netif_output(netif: [*c]c.struct_netif, pbuf_c: [*c]c.struct_pbuf) callconv(.c) c.err_t { - _ = netif; - const pbuf: *c.struct_pbuf = pbuf_c; +var packet_buf: [1600]u8 = undefined; - // std.log.info("sending packet", .{}); +fn netif_output(_: [*c]lwip.c.struct_netif, pbuf_c: [*c]lwip.c.struct_pbuf) callconv(.c) lwip.c.err_t { + const pbuf: *lwip.c.struct_pbuf = pbuf_c; + std.debug.assert(pbuf.tot_len <= packet_buf.len); var off: usize = 0; while (off < pbuf.tot_len) { - const cnt = c.pbuf_copy_partial(pbuf, packet_buf[off..].ptr, @as(u15, @intCast(pbuf.tot_len - off)), @as(u15, @intCast(off))); + const cnt = lwip.c.pbuf_copy_partial(pbuf, packet_buf[off..].ptr, @as(u15, @intCast(pbuf.tot_len - off)), @as(u15, @intCast(off))); if (cnt == 0) { std.log.err("failed to copy network packet", .{}); - return c.ERR_BUF; + return lwip.c.ERR_BUF; } off += cnt; } @@ -155,31 +154,21 @@ fn netif_output(netif: [*c]c.struct_netif, pbuf_c: [*c]c.struct_pbuf) callconv(. std.log.err("failed to send packet: {}", .{err}); }; - return c.ERR_OK; + return lwip.c.ERR_OK; } const IPFormatter = struct { - addr: c.ip_addr_t, + addr: lwip.c.ip_addr_t, - pub fn init(addr: c.ip_addr_t) IPFormatter { + pub fn init(addr: lwip.c.ip_addr_t) IPFormatter { return .{ .addr = addr }; } pub fn format(addr: IPFormatter, writer: *std.Io.Writer) !void { - try writer.writeAll(std.mem.sliceTo(c.ip4addr_ntoa(@as(*const c.ip4_addr_t, @ptrCast(&addr.addr))), 0)); + try writer.writeAll(std.mem.sliceTo(lwip.c.ip4addr_ntoa(@as(*const lwip.c.ip4_addr_t, @ptrCast(&addr.addr))), 0)); } }; -fn netif_status_callback(netif_c: [*c]c.netif) callconv(.c) void { - const netif: *c.netif = netif_c; - +fn netif_status_callback(_: [*c]lwip.c.netif) callconv(.c) void { std.log.info("netif status changed ip to {f}", .{IPFormatter.init(netif.ip_addr)}); } - -export fn sys_now() callconv(.c) u32 { - return @truncate(hal.time.get_time_since_boot().to_us() * 1_000); -} - -export fn rand() callconv(.c) i32 { - return @bitCast(hal.rng.random_u32()); -} diff --git a/modules/lwip/build.zig b/modules/lwip/build.zig index e53a1faa2..cfea858e3 100644 --- a/modules/lwip/build.zig +++ b/modules/lwip/build.zig @@ -4,11 +4,22 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const maybe_include_dir = b.option( + std.Build.LazyPath, + "include_dir", + "Configurable include directory for lwip", + ); + const upstream = b.dependency("lwip", .{}); - const lwip = b.addModule("lwip", .{ - .target = target, - .optimize = optimize, + const lwip = b.addLibrary(.{ + .name = "lwip", + .root_module = b.createModule(.{ + .optimize = optimize, + .target = target, + .link_libc = false, + }), + .linkage = .static, }); lwip.addCSourceFiles(.{ @@ -17,13 +28,16 @@ pub fn build(b: *std.Build) void { .flags = &flags, }); lwip.addIncludePath(upstream.path("src/include")); -} -// pub fn setup(b: *std.Build, dst: *std.Build.Module) void { -// const upstream = b.dependency("lwip", .{}); -// dst.addIncludePath(upstream.path("src/include")); -// dst.addIncludePath(b.path("src/kernel/components/network/include")); -// } + if (maybe_include_dir) |include_dir| { + lwip.addIncludePath(include_dir); + lwip.installHeadersDirectory(include_dir, "", .{}); + } + + lwip.installHeadersDirectory(upstream.path("src/include"), "", .{}); + + b.installArtifact(lwip); +} const flags = [_][]const u8{ "-std=c99", "-fno-sanitize=undefined" }; const files = [_][]const u8{ @@ -77,15 +91,15 @@ const files = [_][]const u8{ "netif/bridgeif_fdb.c", // sequential APIs - // "api/err.c", - // "api/api_msg.c", - // "api/netifapi.c", - // "api/sockets.c", - // "api/netbuf.c", - // "api/api_lib.c", - // "api/tcpip.c", - // "api/netdb.c", - // "api/if_api.c", + "api/err.c", + "api/api_msg.c", + "api/netifapi.c", + "api/sockets.c", + "api/netbuf.c", + "api/api_lib.c", + "api/tcpip.c", + "api/netdb.c", + "api/if_api.c", // 6LoWPAN "netif/lowpan6.c", diff --git a/modules/riscv32-common/src/riscv32_common.zig b/modules/riscv32-common/src/riscv32_common.zig index 72a1da7be..4687dd7e1 100644 --- a/modules/riscv32-common/src/riscv32_common.zig +++ b/modules/riscv32-common/src/riscv32_common.zig @@ -33,11 +33,13 @@ pub const interrupt = struct { } pub fn enable_interrupts() void { + fence(); csr.mstatus.set(.{ .mie = 1 }); } pub fn disable_interrupts() void { csr.mstatus.clear(.{ .mie = 1 }); + fence(); } pub const core = utilities.interrupt.CoreImpl(CoreInterrupt); diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 7cdac7551..80ff329b2 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -198,6 +198,7 @@ pub fn export_symbols() void { @export(&printf, .{ .name = name }); } @export(&esp_fill_random, .{ .name = "esp_fill_random" }); + @export(&esp_event_post, .{ .name = "esp_event_post" }); } // ----- end of exports ----- @@ -554,12 +555,12 @@ fn task_create_common( task_handle: ?*anyopaque, core_id: u32, ) i32 { - _ = name; // autofix _ = core_id; // autofix const task_entry: *const fn (param: ?*anyopaque) callconv(.c) noreturn = @ptrCast(@alignCast(task_func)); const task: *rtos.Task = rtos.spawn(gpa, task_wrapper, .{ task_entry, param }, .{ + .name = std.mem.span(name), .priority = @enumFromInt(prio), .stack_size = stack_depth, }) catch { @@ -640,6 +641,19 @@ pub fn task_get_max_priority() callconv(.c) i32 { return @intFromEnum(rtos.Priority.highest); } +pub fn esp_event_post( + base: [*c]const u8, + id: i32, + data: ?*anyopaque, + data_size: usize, + ticks_to_wait: u32, +) callconv(.c) i32 { + _ = base; + _ = ticks_to_wait; + wifi.on_event_post(id, data, data_size); + return 0; +} + pub fn get_free_heap_size() callconv(.c) void { @panic("get_free_heap_size: not implemented"); } diff --git a/port/espressif/esp/src/hal/radio/timer.zig b/port/espressif/esp/src/hal/radio/timer.zig index afc604ec8..dc25a2e93 100644 --- a/port/espressif/esp/src/hal/radio/timer.zig +++ b/port/espressif/esp/src/hal/radio/timer.zig @@ -26,6 +26,7 @@ var timer_list: std.SinglyLinkedList = .{}; pub fn init(gpa: Allocator) Allocator.Error!void { _ = try rtos.spawn(gpa, task_fn, .{}, .{ + .name = "radio_timer", .priority = .highest, // TODO: what should the priority be? .stack_size = 8192, }); diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index fa7739359..5882b96db 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -1,27 +1,31 @@ const std = @import("std"); -const log = std.log.scoped(.esp_radio_wifi); +const assert = std.debug.assert; +pub const c = @import("esp-wifi-driver"); const microzig = @import("microzig"); -const SPSC_Queue = microzig.concurrency.SPSC_Queue; +const wifi_options = microzig.options.hal.radio.wifi; const radio = @import("../radio.zig"); -const wifi_options = microzig.options.hal.radio.wifi; +const rtos = @import("../rtos.zig"); const osi = @import("osi.zig"); -pub const c = @import("esp-wifi-driver"); +const log = std.log.scoped(.esp_radio_wifi); pub const InternalError = error{InternalError}; var inited: bool = false; pub const Options = struct { - rx_queue_len: usize = 15, - tx_queue_len: usize = 15, + on_event: ?*const fn (e: EventType) void = null, + on_packet_received: ?*const fn (comptime interface: Interface, data: []const u8) void = null, + on_packet_transmitted: ?*const fn (interface: Interface, data: []const u8, status: bool) void = null, }; const InitError = std.mem.Allocator.Error || InternalError; pub fn init(gpa: std.mem.Allocator) InitError!void { + comptime export_symbols(); + if (inited) { @panic("wifi already initialized"); } @@ -41,7 +45,6 @@ pub fn init(gpa: std.mem.Allocator) InitError!void { try c_result(c.esp_supplicant_init()); try c_result(c.esp_wifi_set_tx_done_cb(tx_done_cb)); - try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_AP, recv_cb_ap)); try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_STA, recv_cb_sta)); @@ -348,7 +351,7 @@ pub fn disconnect() InternalError!void { try c_result(c.esp_wifi_disconnect_internal()); } -pub const Event = enum(i32) { +pub const EventType = enum(i32) { /// Wi-Fi is ready for operation. WifiReady = 0, /// Scan operation has completed. @@ -451,29 +454,149 @@ pub const Event = enum(i32) { StaNeighborRep, }; -export fn esp_event_post( - base: [*c]const u8, - id: i32, - data: ?*anyopaque, - data_size: usize, - ticks_to_wait: u32, -) callconv(.c) i32 { - _ = base; // autofix - _ = data; // autofix - _ = data_size; // autofix - _ = ticks_to_wait; // autofix +// var event_state_mutex: rtos.Mutex = .{}; +// var event_state_condition: rtos.Condition = .{}; - const event: Event = @enumFromInt(id); - log.info("received event: {s}", .{@tagName(event)}); +pub const Event = union(EventType) { + /// Wi-Fi is ready for operation. + WifiReady, + /// Scan operation has completed. + ScanDone: c.wifi_event_sta_scan_done_t, + /// Station mode started. + StaStart, + /// Station mode stopped. + StaStop, + /// Station connected to a network. + StaConnected: c.wifi_event_sta_connected_t, + /// Station disconnected from a network. + StaDisconnected: c.wifi_event_sta_disconnected_t, + /// Station authentication mode changed. + StaAuthmodeChange: c.wifi_event_sta_authmode_change_t, - update_sta_state(event); + /// Station WPS succeeds in enrollee mode. + StaWpsErSuccess: c.wifi_event_sta_wps_er_success_t, + /// Station WPS fails in enrollee mode. + StaWpsErFailed, + /// Station WPS timeout in enrollee mode. + StaWpsErTimeout, + /// Station WPS pin code in enrollee mode. + StaWpsErPin: c.wifi_event_sta_wps_er_pin_t, + /// Station WPS overlap in enrollee mode. + StaWpsErPbcOverlap, - return 0; -} + /// Soft-AP start. + ApStart, + /// Soft-AP stop. + ApStop, + /// A station connected to Soft-AP. + ApStaconnected: c.wifi_event_ap_staconnected_t, + /// A station disconnected from Soft-AP. + ApStadisconnected: c.wifi_event_ap_stadisconnected_t, + /// Received probe request packet in Soft-AP interface. + ApProbereqrecved: c.wifi_event_ap_probe_req_rx_t, + + /// Received report of FTM procedure. + FtmReport: c.wifi_event_ftm_report_t, + + /// AP's RSSI crossed configured threshold. + StaBssRssiLow: c.wifi_event_bss_rssi_low_t, + /// Status indication of Action Tx operation. + ActionTxStatus: c.wifi_event_action_tx_status_t, + /// Remain-on-Channel operation complete. + RocDone: c.wifi_event_roc_done_t, + + /// Station beacon timeout. + StaBeaconTimeout, + + /// Connectionless module wake interval has started. + ConnectionlessModuleWakeIntervalStart, + + /// Soft-AP WPS succeeded in registrar mode. + ApWpsRgSuccess: c.wifi_event_ap_wps_rg_success_t, + /// Soft-AP WPS failed in registrar mode. + ApWpsRgFailed: c.wifi_event_ap_wps_rg_fail_reason_t, + /// Soft-AP WPS timed out in registrar mode. + ApWpsRgTimeout, + /// Soft-AP WPS pin code in registrar mode. + ApWpsRgPin: c.wifi_event_ap_wps_rg_pin_t, + /// Soft-AP WPS overlap in registrar mode. + ApWpsRgPbcOverlap, + + /// iTWT setup. + ItwtSetup, + /// iTWT teardown. + ItwtTeardown, + /// iTWT probe. + ItwtProbe, + /// iTWT suspended. + ItwtSuspend, + /// TWT wakeup event. + TwtWakeup, + /// bTWT setup. + BtwtSetup, + /// bTWT teardown. + BtwtTeardown, -// TODO: ApState + /// NAN (Neighbor Awareness Networking) discovery has started. + NanStarted, + /// NAN discovery has stopped. + NanStopped, + /// NAN service discovery match found. + // TODO: c.wifi_event_nan_svc_match_t + NanSvcMatch, + /// Replied to a NAN peer with service discovery match. + // TODO: c.wifi_event_nan_replied_t + NanReplied, + /// Received a follow-up message in NAN. + // TODO: c.wifi_event_nan_receive_t + NanReceive, + /// Received NDP (Neighbor Discovery Protocol) request from a NAN peer. + // TODO: c.wifi_event_ndp_indication_t + NdpIndication, + /// NDP confirm indication. + // TODO: c.wifi_event_ndp_confirm_t + NdpConfirm, + /// NAN datapath terminated indication. + // TODO: c.wifi_event_ndp_terminated_t + NdpTerminated, + /// Wi-Fi home channel change, doesn't occur when scanning. + HomeChannelChange: c.wifi_event_home_channel_change_t, -pub const StaState = enum { + /// Received Neighbor Report response. + StaNeighborRep: c.wifi_event_neighbor_report_t, +}; + +/// Internal function. Called by osi layer. +pub fn on_event_post(id: i32, data: ?*anyopaque, data_size: usize) void { + log.info("esp_event_post {} {?} {}", .{ id, data, data_size }); + + const event_type: EventType = @enumFromInt(id); + log.info("received event: {t}", .{event_type}); + + update_sta_state(event_type); + + // const event: Event = switch (event_type) { + // inline else => |tag| blk: { + // const PayloadType = @FieldType(Event, @tagName(tag)); + // if (PayloadType == void) break :blk @unionInit(Event, @tagName(tag), {}); + // + // // Purposely skipping the assert if the type is void. There are + // // some todos where we use void for the payload instead of the + // // actual payload type. + // assert(data_size == @sizeOf(PayloadType)); + // + // break :blk @unionInit(Event, @tagName(tag), @as(*PayloadType, @ptrCast(@alignCast(data))).*); + // }, + // }; + // log.info("event: {any}", .{event}); + + if (wifi_options.on_event) |on_event| { + on_event(event_type); + } +} + +// TODO: Should we also add ApState? Should we remove StaState? +pub const StaState = enum(u32) { none, sta_started, sta_connected, @@ -481,9 +604,9 @@ pub const StaState = enum { sta_stopped, }; -var sta_state: StaState = .none; +var sta_state: std.atomic.Value(StaState) = .init(.none); -fn update_sta_state(event: Event) void { +fn update_sta_state(event: EventType) void { const new_sta_state: StaState = switch (event) { .StaStart => .sta_started, .StaConnected => .sta_connected, @@ -491,11 +614,11 @@ fn update_sta_state(event: Event) void { .StaStop => .sta_stopped, else => return, }; - @atomicStore(StaState, &sta_state, new_sta_state, .unordered); + sta_state.store(new_sta_state, .monotonic); } pub fn get_sta_state() StaState { - return @atomicLoad(StaState, &sta_state, .unordered); + return sta_state.load(.monotonic); } pub const Interface = enum(u32) { @@ -511,68 +634,29 @@ pub const Interface = enum(u32) { } }; -var packets_in_flight: usize = 0; - pub fn send_packet(iface: Interface, data: []const u8) (error{TooManyPacketsInFlight} || InternalError)!void { - const pkts_in_flight = @atomicLoad(usize, &packets_in_flight, .acquire); - if (pkts_in_flight >= wifi_options.tx_queue_len) { - log.warn("too many packets in flight", .{}); - return error.TooManyPacketsInFlight; - } - _ = @atomicRmw(usize, &packets_in_flight, .Add, 1, .monotonic); - errdefer _ = @atomicRmw(usize, &packets_in_flight, .Sub, 1, .monotonic); - try c_result(c.esp_wifi_internal_tx(@intFromEnum(iface), @ptrCast(@constCast(data.ptr)), @intCast(data.len))); } fn tx_done_cb( - _: u8, - _: [*c]u8, - _: [*c]u16, - _: bool, + iface_idx: u8, + data_ptr: [*c]u8, + data_len: [*c]u16, + status: bool, ) callconv(.c) void { log.debug("tx_done_cb", .{}); - if (packets_in_flight == 0) { - log.warn("ignoring tx_done_cb as there are no packets in flight", .{}); - return; - } - - _ = @atomicRmw(usize, &packets_in_flight, .Sub, 1, .acq_rel); -} - -/// Every packet buffer must be deinited by the user in the callback. -pub const ReceivedPacket = struct { - data: []const u8, - eb: ?*anyopaque, - - pub fn deinit(self: ReceivedPacket) void { - c.esp_wifi_internal_free_rx_buffer(self.eb); - } -}; - -var ap_rx_queue: SPSC_Queue(ReceivedPacket, wifi_options.rx_queue_len) = .empty; -var sta_rx_queue: SPSC_Queue(ReceivedPacket, wifi_options.rx_queue_len) = .empty; - -pub fn recv_packet(comptime iface: Interface) ?ReceivedPacket { - return switch (iface) { - .ap => ap_rx_queue.dequeue(), - .sta => sta_rx_queue.dequeue(), - }; + if (wifi_options.on_packet_transmitted) |on_packet_transmitted| + on_packet_transmitted(@intFromEnum(iface_idx), data_ptr[0..data_len], status); } fn recv_cb_ap(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_err_t { log.debug("recv_cb_ap {?} {} {?}", .{ buf, len, eb }); - const packet: ReceivedPacket = .{ - .data = @as([*]const u8, @ptrCast(buf))[0..len], - .eb = eb, - }; - - ap_rx_queue.enqueue(packet) catch { - log.warn("ap rx queue full. packet dropped.", .{}); - packet.deinit(); - }; + if (wifi_options.on_packet_received) |on_packet_received| { + on_packet_received(.ap, @as([*]const u8, @ptrCast(buf))[0..len]); + } + c.esp_wifi_internal_free_rx_buffer(eb); return c.ESP_OK; } @@ -580,19 +664,19 @@ fn recv_cb_ap(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_er fn recv_cb_sta(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_err_t { log.debug("recv_cb_sta {?} {} {?}", .{ buf, len, eb }); - const packet: ReceivedPacket = .{ - .data = @as([*]const u8, @ptrCast(buf))[0..len], - .eb = eb, - }; - - sta_rx_queue.enqueue(packet) catch { - log.warn("sta rx queue full. packet dropped.", .{}); - packet.deinit(); - }; + if (wifi_options.on_packet_received) |on_packet_received| { + on_packet_received(.sta, @as([*]const u8, @ptrCast(buf))[0..len]); + } + c.esp_wifi_internal_free_rx_buffer(eb); return c.ESP_OK; } +fn export_symbols() void { + @export(&g_wifi_feature_caps, .{ .name = "g_wifi_feature_caps" }); + @export(&g_wifi_osi_funcs, .{ .name = "g_wifi_osi_funcs" }); +} + // TODO: configurable var init_config: c.wifi_init_config_t = .{ .osi_funcs = &g_wifi_osi_funcs, @@ -631,9 +715,9 @@ const wifi_enable_enterprise: u64 = 1 << 7; // const wifi_enable_gmac: u64 = 1 << 5; // const wifi_enable_11r: u64 = 1 << 6; -export var g_wifi_feature_caps: u64 = wifi_enable_wpa3_sae | wifi_enable_enterprise; +var g_wifi_feature_caps: u64 = wifi_enable_wpa3_sae | wifi_enable_enterprise; -export var g_wifi_osi_funcs: c.wifi_osi_funcs_t = .{ +var g_wifi_osi_funcs: c.wifi_osi_funcs_t = .{ ._version = c.ESP_WIFI_OS_ADAPTER_VERSION, ._env_is_chip = osi.env_is_chip, ._set_intr = osi.set_intr, @@ -679,7 +763,7 @@ export var g_wifi_osi_funcs: c.wifi_osi_funcs_t = .{ ._task_get_max_priority = osi.task_get_max_priority, ._malloc = osi.malloc, ._free = osi.free, - ._event_post = esp_event_post, + ._event_post = osi.esp_event_post, ._get_free_heap_size = @ptrCast(&osi.get_free_heap_size), ._rand = osi.rand, ._dport_access_stall_other_cpu_start_wrap = osi.dport_access_stall_other_cpu_start_wrap, diff --git a/port/espressif/esp/src/hal/rtos.zig b/port/espressif/esp/src/hal/rtos.zig index 5cd6aa14f..74b76a0a3 100644 --- a/port/espressif/esp/src/hal/rtos.zig +++ b/port/espressif/esp/src/hal/rtos.zig @@ -22,13 +22,12 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; // and vice versa. Because of the forced yield, tasks are required to have a // minimum stack size available at all times. -// TODO: add identifier names to tasks -// TODO: stack usage report based on stack painting and overflow detection +// TODO: stack overflow detection // TODO: question: should idle do more stuff (like task garbage collection)? // TODO: implement task garbage collection and recycling // TODO: implement std.Io // TODO: use @stackUpperBound when implemented -// TODO: implement priority inheritance for respective sync primitives +// TODO: implement priority inheritance for sync primitives // TODO: better handling if timeout is in the past or very short // TODO: support SMP for other esp32 chips with multicore (far future) // NOTE: don't use anonymous structs for reinitializing state unions with @@ -54,6 +53,8 @@ pub const Options = struct { systimer_alarm: systimer.Alarm = .alarm0, cpu_interrupt: system.CpuInterrupt = .cpu_interrupt_0, yield_interrupt: microzig.cpu.Interrupt = .interrupt31, + + paint_stack: ?u8 = null, }; var main_task: Task = .{ @@ -69,7 +70,7 @@ var idle_task: Task = .{ .fp = null, }, .priority = .idle, - .stack = &.{}, + .stack = &idle_stack, }; var rtos_state: RTOS_State = undefined; @@ -99,6 +100,9 @@ pub fn init() void { rtos_state = .{ .current_task = &main_task, }; + if (rtos_options.paint_stack) |paint_byte| { + @memset(&idle_stack, paint_byte); + } make_ready(&idle_task); microzig.cpu.interrupt.map(rtos_options.cpu_interrupt.source(), rtos_options.yield_interrupt); @@ -124,9 +128,12 @@ pub fn init() void { // TODO: deinit fn idle() linksection(".ram_text") callconv(.naked) void { + // TODO: cpu would hang after a while. Enabling interrupts before wfi + // appears to have solved the problem but the root cause is still a + // mistery. asm volatile ( - \\csrsi mstatus, 8 # enable interrupts \\1: + \\csrsi mstatus, 8 # make sure interrupts are enabled \\wfi \\j 1b ); @@ -137,8 +144,8 @@ pub fn get_current_task() *Task { } pub const SpawnOptions = struct { + name: ?[]const u8 = null, stack_size: usize = 4096, - // TODO: should we ban idle priority? priority: Priority = .lowest, }; @@ -148,6 +155,8 @@ pub fn spawn( args: std.meta.ArgsTuple(@TypeOf(function)), options: SpawnOptions, ) !*Task { + assert(options.priority != .idle); + const Args = @TypeOf(args); const args_align: std.mem.Alignment = comptime .fromByteUnits(@alignOf(Args)); @@ -181,8 +190,12 @@ pub fn spawn( task_args.* = args; const stack: []u8 = raw_alloc[stack_start..stack_end]; + if (rtos_options.paint_stack) |paint_byte| { + @memset(stack, paint_byte); + } task.* = .{ + .name = options.name, .context = .{ .sp = stack[stack.len..].ptr, .pc = &TypeErased.call, @@ -336,6 +349,40 @@ pub fn is_a_higher_priority_task_ready() bool { false; } +pub const report_stack_usage = if (rtos_options.paint_stack) |paint_byte| struct { + fn report_stack_usage() void { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + const list: []const ?*std.DoublyLinkedList.Node = &.{ + rtos_state.ready_queue.inner.first, + rtos_state.timer_queue.first, + rtos_state.scheduled_for_deletion_list.first, + }; + for (list) |first| { + var it: ?*std.DoublyLinkedList.Node = first; + while (it) |node| : (it = node.next) { + const task: *Task = @alignCast(@fieldParentPtr("node", node)); + if (task == &main_task) continue; + log_stack_task_usage(task); + } + } + } + + fn log_stack_task_usage(task: *Task) void { + for (task.stack, 0..) |byte, i| { + if (byte != paint_byte) { + std.log.debug("task with name {?s} uses {} bytes out of {} for stack", .{ + task.name, + task.stack.len - i, + task.stack.len, + }); + break; + } + } + } +}.report_stack_usage else @compileError("please enable the paint_stack option to use this function"); + pub const yield_interrupt_handler: microzig.cpu.InterruptHandler = .{ .naked = struct { pub fn handler_fn() linksection(".ram_vectors") callconv(.naked) void { @@ -491,13 +538,13 @@ fn schedule_in_isr(context: *Context) linksection(".ram_vectors") callconv(.c) v pub const general_purpose_interrupt_handler: microzig.cpu.InterruptHandler = .{ .c = struct { pub fn handler_fn(_: *TrapFrame) linksection(".ram_text") callconv(.c) void { + const cs = enter_critical_section(); + defer cs.leave(); + var status: microzig.cpu.interrupt.Status = .init(); if (status.is_set(rtos_options.systimer_alarm.interrupt_source())) { rtos_options.systimer_alarm.clear_interrupt(); - const cs = enter_critical_section(); - defer cs.leave(); - sweep_timer_queue_for_timeouts(); } @@ -554,6 +601,8 @@ fn sweep_timer_queue_for_timeouts() void { } pub const Task = struct { + name: ?[]const u8 = null, + context: Context, stack: []u8, priority: Priority, @@ -755,7 +804,6 @@ pub const Mutex = struct { } }; -// TODO: maybe move inside radion/osi.zig since it is made specifically for it pub const RecursiveMutex = struct { recursive: bool, value: u32 = 0, From 3b4b9bc6265d3fb583ef19f59f5572f470c48064 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Wed, 14 Jan 2026 22:29:05 +0200 Subject: [PATCH 25/43] `rtos.Condition`, bigger debug wifi task stack size, blocking start and connect --- examples/espressif/esp/src/lwip/exports.zig | 2 +- examples/espressif/esp/src/wifi.zig | 19 ++-- port/espressif/esp/src/hal/radio/osi.zig | 4 +- port/espressif/esp/src/hal/radio/timer.zig | 4 +- port/espressif/esp/src/hal/radio/wifi.zig | 108 ++++++++++++++++---- port/espressif/esp/src/hal/rtos.zig | 32 +++++- 6 files changed, 130 insertions(+), 39 deletions(-) diff --git a/examples/espressif/esp/src/lwip/exports.zig b/examples/espressif/esp/src/lwip/exports.zig index 3b10a58d7..9047c17f8 100644 --- a/examples/espressif/esp/src/lwip/exports.zig +++ b/examples/espressif/esp/src/lwip/exports.zig @@ -140,7 +140,7 @@ export fn sys_thread_new(name: [*:0]u8, thread: c.lwip_thread_fn, arg: ?*anyopaq _ = prio; // autofix return rtos.spawn(gpa, task_wrapper, .{thread, arg}, .{ .name = std.mem.span(name), - .stack_size = 8192, + .stack_size = 4096, .priority = @enumFromInt(2), }) catch @panic("failed to allocate lwip task"); } diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig index 7555184db..b7db77e6c 100644 --- a/examples/espressif/esp/src/wifi.zig +++ b/examples/espressif/esp/src/wifi.zig @@ -46,10 +46,10 @@ pub const microzig_options: microzig.Options = .{ .hal = .{ .rtos = .{ .enable = true, + .paint_stack = 0xbb, }, .radio = .{ .wifi = .{ - .on_event = on_event, .on_packet_received = on_packet_received, }, }, @@ -74,23 +74,18 @@ pub fn main() !void { }, }); - try radio.wifi.start(); - try radio.wifi.connect(); + try radio.wifi.start_blocking(); + try radio.wifi.connect_blocking(); + _ = lwip.c.netifapi_netif_common(&netif, lwip.c.netif_set_link_up, null); var count: usize = 0; while (true) { const free_heap = heap_allocator.free_heap(); std.log.info("{} free memory: {}K ({})", .{ count, free_heap / 1024, free_heap }); + rtos.report_stack_usage(); count += 1; - rtos.sleep(.from_ms(1000)); - } -} -fn on_event(e: radio.wifi.EventType) void { - switch (e) { - .StaConnected => _ = lwip.c.netifapi_netif_common(&netif, lwip.c.netif_set_link_up, null), - .StaDisconnected => _ = lwip.c.netifapi_netif_common(&netif, lwip.c.netif_set_link_down, null), - else => {} + rtos.sleep(.from_ms(5000)); } } @@ -138,7 +133,7 @@ var packet_buf: [1600]u8 = undefined; fn netif_output(_: [*c]lwip.c.struct_netif, pbuf_c: [*c]lwip.c.struct_pbuf) callconv(.c) lwip.c.err_t { const pbuf: *lwip.c.struct_pbuf = pbuf_c; - std.debug.assert(pbuf.tot_len <= packet_buf.len); + std.debug.assert(pbuf.tot_len < packet_buf.len); var off: usize = 0; while (off < pbuf.tot_len) { diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 80ff329b2..818cd71f6 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -559,10 +559,12 @@ fn task_create_common( const task_entry: *const fn (param: ?*anyopaque) callconv(.c) noreturn = @ptrCast(@alignCast(task_func)); + // increase stack size if we are in debug mode + const stack_size: usize = stack_depth + if (builtin.mode == .Debug) 6000 else 0; const task: *rtos.Task = rtos.spawn(gpa, task_wrapper, .{ task_entry, param }, .{ .name = std.mem.span(name), .priority = @enumFromInt(prio), - .stack_size = stack_depth, + .stack_size = stack_size, }) catch { log.warn("failed to create task", .{}); return 0; diff --git a/port/espressif/esp/src/hal/radio/timer.zig b/port/espressif/esp/src/hal/radio/timer.zig index dc25a2e93..c4ff7e00a 100644 --- a/port/espressif/esp/src/hal/radio/timer.zig +++ b/port/espressif/esp/src/hal/radio/timer.zig @@ -27,8 +27,8 @@ var timer_list: std.SinglyLinkedList = .{}; pub fn init(gpa: Allocator) Allocator.Error!void { _ = try rtos.spawn(gpa, task_fn, .{}, .{ .name = "radio_timer", - .priority = .highest, // TODO: what should the priority be? - .stack_size = 8192, + .priority = .lowest, // TODO: what should the priority be? + .stack_size = 4096, }); } diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index 5882b96db..f167a01f2 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -16,7 +16,7 @@ pub const InternalError = error{InternalError}; var inited: bool = false; pub const Options = struct { - on_event: ?*const fn (e: EventType) void = null, + on_event: ?*const fn (e: Event) void = null, on_packet_received: ?*const fn (comptime interface: Interface, data: []const u8) void = null, on_packet_transmitted: ?*const fn (interface: Interface, data: []const u8, status: bool) void = null, }; @@ -351,6 +351,26 @@ pub fn disconnect() InternalError!void { try c_result(c.esp_wifi_disconnect_internal()); } +pub fn start_blocking() InternalError!void { + const mode = try get_mode(); + var events: EventSet = .initEmpty(); + if (mode.is_sta()) events.setPresent(.StaStart, true); + if (mode.is_ap()) events.setPresent(.ApStart, true); + clear_events(events); + try start(); + wait_for_all_events(events); +} + +pub const ConnectError = error{FailedToConnect} || InternalError; + +pub fn connect_blocking() ConnectError!void { + const events: EventSet = .initMany(&.{ .StaConnected, .StaDisconnected }); + clear_events(events); + try connect(); + if (wait_for_any_event(events).contains(.StaDisconnected)) + return error.FailedToConnect; +} + pub const EventType = enum(i32) { /// Wi-Fi is ready for operation. WifiReady = 0, @@ -454,9 +474,6 @@ pub const EventType = enum(i32) { StaNeighborRep, }; -// var event_state_mutex: rtos.Mutex = .{}; -// var event_state_condition: rtos.Condition = .{}; - pub const Event = union(EventType) { /// Wi-Fi is ready for operation. WifiReady, @@ -566,32 +583,79 @@ pub const Event = union(EventType) { StaNeighborRep: c.wifi_event_neighbor_report_t, }; +pub const EventSet = std.EnumSet(EventType); + +var event_mutex: rtos.Mutex = .{}; +var event_condition: rtos.Condition = .{}; +var active_events: EventSet = .{}; + +pub fn wait_for_any_event(events: EventSet) EventSet { + event_mutex.lock(); + defer event_mutex.unlock(); + + while (true) { + const intersection = active_events.intersectWith(events); + if (intersection.count() > 0) return intersection; + event_condition.wait(&event_mutex); + } +} + +pub fn wait_for_all_events(events: EventSet) void { + event_mutex.lock(); + defer event_mutex.unlock(); + + while (!active_events.supersetOf(events)) { + event_condition.wait(&event_mutex); + } +} + +pub fn wait_for_event(event: EventType) void { + event_mutex.lock(); + defer event_mutex.unlock(); + + while (!active_events.contains(event)) { + event_condition.wait(&event_mutex); + } +} + +pub fn clear_events(events: EventSet) void { + event_mutex.lock(); + defer event_mutex.unlock(); + + active_events = active_events.differenceWith(events); +} + /// Internal function. Called by osi layer. pub fn on_event_post(id: i32, data: ?*anyopaque, data_size: usize) void { - log.info("esp_event_post {} {?} {}", .{ id, data, data_size }); + log.debug("esp_event_post {} {?} {}", .{ id, data, data_size }); const event_type: EventType = @enumFromInt(id); - log.info("received event: {t}", .{event_type}); - update_sta_state(event_type); + log.info("event occurred: {t}", .{event_type}); + + { + event_mutex.lock(); + defer event_mutex.unlock(); + active_events.setPresent(event_type, true); + } + event_condition.broadcast(); - // const event: Event = switch (event_type) { - // inline else => |tag| blk: { - // const PayloadType = @FieldType(Event, @tagName(tag)); - // if (PayloadType == void) break :blk @unionInit(Event, @tagName(tag), {}); - // - // // Purposely skipping the assert if the type is void. There are - // // some todos where we use void for the payload instead of the - // // actual payload type. - // assert(data_size == @sizeOf(PayloadType)); - // - // break :blk @unionInit(Event, @tagName(tag), @as(*PayloadType, @ptrCast(@alignCast(data))).*); - // }, - // }; - // log.info("event: {any}", .{event}); + const event = switch (event_type) { + inline else => |tag| blk: { + const PayloadType = @FieldType(Event, @tagName(tag)); + if (PayloadType == void) break :blk @unionInit(Event, @tagName(tag), {}); + + // Purposely skipping the assert if the type is void. There are + // some todos where we use void for the payload instead of the + // actual payload type. + assert(data_size == @sizeOf(PayloadType)); + + break :blk @unionInit(Event, @tagName(tag), @as(*PayloadType, @ptrCast(@alignCast(data))).*); + }, + }; if (wifi_options.on_event) |on_event| { - on_event(event_type); + on_event(event); } } diff --git a/port/espressif/esp/src/hal/rtos.zig b/port/espressif/esp/src/hal/rtos.zig index 74b76a0a3..866f95bf8 100644 --- a/port/espressif/esp/src/hal/rtos.zig +++ b/port/espressif/esp/src/hal/rtos.zig @@ -34,7 +34,7 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; // previous union state to avoid compiler bug (0.15.2) const STACK_ALIGN: std.mem.Alignment = .@"16"; -const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 32 * @sizeOf(usize)); +const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 64 * @sizeOf(usize)); const rtos_options = microzig.options.hal.rtos; pub const Priority = rtos_options.Priority; @@ -64,6 +64,7 @@ var main_task: Task = .{ }; var idle_stack: [STACK_ALIGN.forward(EXTRA_STACK_SIZE)]u8 align(STACK_ALIGN.toByteUnits()) = undefined; var idle_task: Task = .{ + .name = "idle", .context = .{ .pc = &idle, .sp = idle_stack[idle_stack.len..].ptr, @@ -804,6 +805,35 @@ pub const Mutex = struct { } }; +pub const Condition = struct { + wait_queue: PriorityWaitQueue = .{}, + + pub fn wait(cond: *Condition, mutex: *Mutex) void { + { + const cs = enter_critical_section(); + defer cs.leave(); + + mutex.unlock(); + + cond.wait_queue.wait(get_current_task(), null); + } + + mutex.lock(); + } + + pub fn signal(cond: *Condition) void { + const cs = enter_critical_section(); + defer cs.leave(); + cond.wait_queue.wake_one(); + } + + pub fn broadcast(cond: *Condition) void { + const cs = enter_critical_section(); + defer cs.leave(); + cond.wait_queue.wake_all(); + } +}; + pub const RecursiveMutex = struct { recursive: bool, value: u32 = 0, From e6fb06d05a0f124dc34d492df96bccf115b166dd Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Thu, 15 Jan 2026 23:10:54 +0200 Subject: [PATCH 26/43] TCP server example and refactoring --- examples/espressif/esp/build.zig | 2 +- examples/espressif/esp/src/lwip/exports.zig | 64 ++++- examples/espressif/esp/src/lwip/include.zig | 58 +++++ examples/espressif/esp/src/tcp_server.zig | 226 +++++++++++++++++ examples/espressif/esp/src/wifi.zig | 169 ------------ port/espressif/esp/src/cpus/esp_riscv.zig | 3 + port/espressif/esp/src/hal/radio/osi.zig | 10 +- port/espressif/esp/src/hal/radio/wifi.zig | 268 ++++++++++++++++---- port/espressif/esp/src/hal/rtos.zig | 65 +++-- 9 files changed, 613 insertions(+), 252 deletions(-) create mode 100644 examples/espressif/esp/src/tcp_server.zig delete mode 100644 examples/espressif/esp/src/wifi.zig diff --git a/examples/espressif/esp/build.zig b/examples/espressif/esp/build.zig index 4463c65e4..e27471544 100644 --- a/examples/espressif/esp/build.zig +++ b/examples/espressif/esp/build.zig @@ -25,7 +25,7 @@ pub fn build(b: *std.Build) void { .{ .name = "systimer", .file = "src/systimer.zig" }, .{ .name = "ws2812_blinky", .file = "src/ws2812_blinky.zig" }, .{ .name = "rtos", .file = "src/rtos.zig" }, - .{ .name = "wifi", .file = "src/wifi.zig", .features = .{ + .{ .name = "tcp_server", .file = "src/tcp_server.zig", .features = .{ .flashless = false, .lwip = true, } }, diff --git a/examples/espressif/esp/src/lwip/exports.zig b/examples/espressif/esp/src/lwip/exports.zig index 9047c17f8..788370bf3 100644 --- a/examples/espressif/esp/src/lwip/exports.zig +++ b/examples/espressif/esp/src/lwip/exports.zig @@ -1,15 +1,15 @@ /// Platform dependent exports required by lwip /// const std = @import("std"); +const assert = std.debug.assert; + const microzig = @import("microzig"); const esp = microzig.hal; const rtos = esp.rtos; -const log = std.log.scoped(.lwip); -const assert = std.debug.assert; - const c = @import("include.zig").c; +const log = std.log.scoped(.lwip); // TODO: we use a different allocator here than the one in osi. maybe we can // have only one. // TODO: mutex and mailbox can be implemented without allocations if we define @@ -98,10 +98,14 @@ export fn sys_mbox_free(ptr: *c.sys_mbox_t) void { ptr.* = null; } -export fn sys_mbox_valid(ptr: *c.sys_mbox_t) i32 { +export fn sys_mbox_valid(ptr: *c.sys_mbox_t) c_int { return @intFromBool(ptr.* != null); } +export fn sys_mbox_set_invalid(ptr: *c.sys_mbox_t) void { + ptr.* = null; +} + export fn sys_mbox_post(ptr: *c.sys_mbox_t, element: MailboxElement) void { const mailbox: *Mailbox = @ptrCast(@alignCast(ptr.*)); mailbox.put_one(element, null) catch unreachable; @@ -122,10 +126,60 @@ comptime { export fn sys_arch_mbox_fetch(ptr: *c.sys_mbox_t, element_ptr: *MailboxElement, timeout: u32) u32 { const mailbox: *Mailbox = @ptrCast(@alignCast(ptr.*)); + const now = esp.time.get_time_since_boot(); element_ptr.* = mailbox.get_one(if (timeout != 0) .from_ms(timeout) else null) catch { return c.SYS_ARCH_TIMEOUT; }; - return 0; + // returns waiting time in ms + return @intCast(esp.time.get_time_since_boot().diff(now).to_us() / 1_000); +} + +export fn sys_arch_mbox_tryfetch(ptr: *c.sys_mbox_t, element_ptr: *MailboxElement) u32 { + const mailbox: *Mailbox = @ptrCast(@alignCast(ptr.*)); + if (mailbox.get_one_non_blocking()) |element| { + element_ptr.* = element; + return 0; + } else { + return c.SYS_MBOX_EMPTY; + } +} + +export fn sys_sem_new(ptr: *c.sys_sem_t, count: u8) c.err_t { + const sem = gpa.create(rtos.Semaphore) catch { + log.warn("failed to allocate semaphore", .{}); + return c.ERR_MEM; + }; + sem.* = .init(count, 1); + ptr.* = sem; + return c.ERR_OK; +} + +export fn sys_sem_free(ptr: *c.sys_sem_t) void { + const sem: *rtos.Semaphore = @ptrCast(@alignCast(ptr.*)); + gpa.destroy(sem); +} + +export fn sys_sem_signal(ptr: *c.sys_sem_t) void { + const sem: *rtos.Semaphore = @ptrCast(@alignCast(ptr.*)); + sem.give(); +} + +export fn sys_arch_sem_wait(ptr: *c.sys_sem_t, timeout: u32) u32 { + const sem: *rtos.Semaphore = @ptrCast(@alignCast(ptr.*)); + const now = esp.time.get_time_since_boot(); + sem.take_with_timeout(if (timeout != 0) .from_ms(timeout) else null) catch { + return c.SYS_ARCH_TIMEOUT; + }; + // returns waiting time in ms + return @intCast(esp.time.get_time_since_boot().diff(now).to_us() / 1_000); +} + +export fn sys_sem_valid(ptr: *c.sys_sem_t) c_int { + return @intFromBool(ptr.* != null); +} + +export fn sys_sem_set_invalid(ptr: *c.sys_sem_t) void { + ptr.* = null; } fn task_wrapper( diff --git a/examples/espressif/esp/src/lwip/include.zig b/examples/espressif/esp/src/lwip/include.zig index a5c25869e..3d7888c7c 100644 --- a/examples/espressif/esp/src/lwip/include.zig +++ b/examples/espressif/esp/src/lwip/include.zig @@ -10,3 +10,61 @@ pub const c = @cImport({ @cInclude("lwip/ethip6.h"); @cInclude("lwip/timeouts.h"); }); + +pub fn c_err(res: c.err_t) Error!void { + return switch (res) { + c.ERR_OK => {}, + c.ERR_MEM => error.OutOfMemory, + c.ERR_BUF => error.BufferError, + c.ERR_TIMEOUT => error.Timeout, + c.ERR_RTE => error.Routing, + c.ERR_INPROGRESS => error.InProgress, + c.ERR_VAL => error.IllegalValue, + c.ERR_WOULDBLOCK => error.WouldBlock, + c.ERR_USE => error.AddressInUse, + c.ERR_ALREADY => error.AlreadyConnecting, + c.ERR_ISCONN => error.AlreadyConnected, + c.ERR_CONN => error.NotConnected, + c.ERR_IF => error.LowlevelInterfaceError, + c.ERR_ABRT => error.ConnectionAborted, + c.ERR_RST => error.ConnectionReset, + c.ERR_CLSD => error.ConnectionClosed, + c.ERR_ARG => error.IllegalArgument, + else => @panic("unexpected character"), + }; +} + +pub const Error = error{ + /// Out of memory error. + OutOfMemory, + /// Buffer error. + BufferError, + /// Timeout. + Timeout, + /// Routing problem. + Routing, + /// Operation in progress + InProgress, + /// Illegal value. + IllegalValue, + /// Operation would block. + WouldBlock, + /// Address in use. + AddressInUse, + /// Already connecting. + AlreadyConnecting, + /// Conn already established. + AlreadyConnected, + /// Not connected. + NotConnected, + /// Low-level netif error + LowlevelInterfaceError, + /// Connection aborted. + ConnectionAborted, + /// Connection reset. + ConnectionReset, + /// Connection closed. + ConnectionClosed, + /// Illegal argument. + IllegalArgument, +}; diff --git a/examples/espressif/esp/src/tcp_server.zig b/examples/espressif/esp/src/tcp_server.zig new file mode 100644 index 000000000..465f248dc --- /dev/null +++ b/examples/espressif/esp/src/tcp_server.zig @@ -0,0 +1,226 @@ +const std = @import("std"); +const assert = std.debug.assert; + +const microzig = @import("microzig"); +const interrupt = microzig.cpu.interrupt; +const hal = microzig.hal; +const rtos = hal.rtos; +const radio = hal.radio; +const usb_serial_jtag = hal.usb_serial_jtag; + +const exports = @import("lwip/exports.zig"); +const lwip = @import("lwip/include.zig"); + +comptime { + _ = exports; +} + +pub const microzig_options: microzig.Options = .{ + .log_level = .debug, + .log_scope_levels = &.{ + .{ .scope = .esp_radio, .level = .info }, + .{ .scope = .esp_radio_wifi, .level = .info }, + .{ .scope = .esp_radio_osi, .level = .info }, + .{ .scope = .esp_wifi_driver_internal, .level = .err }, + }, + .logFn = usb_serial_jtag.logger.log, + .interrupts = .{ + .interrupt29 = radio.interrupt_handler, + .interrupt30 = rtos.general_purpose_interrupt_handler, + .interrupt31 = rtos.yield_interrupt_handler, + }, + .cpu = .{ + .interrupt_stack = .{ + .enable = true, + .size = 8192, + }, + }, + .hal = .{ + .rtos = .{ + .enable = true, + .paint_stack = 0xbb, + }, + .radio = .{ + .wifi = .{ + .on_packet_received = on_packet_received, + }, + }, + }, +}; + +const SERVER_PORT = 3333; + +var maybe_netif: ?*lwip.c.netif = null; + +var ip_ready_semaphore: rtos.Semaphore = .init(0, 1); +var ip: lwip.c.ip_addr_t = undefined; + +extern fn netconn_new_with_proto_and_callback(t: lwip.c.enum_netconn_type, proto: lwip.c.u8_t, callback: ?*const anyopaque) [*c]lwip.c.struct_netconn; +pub fn main() !void { + var heap_allocator: microzig.Allocator = .init_with_heap(16384); + const gpa = heap_allocator.allocator(); + + try radio.wifi.init(gpa, .{}); + defer radio.wifi.deinit(); + + exports.gpa = gpa; + lwip.c.tcpip_init(null, null); + + var netif: lwip.c.netif = undefined; + _ = lwip.c.netifapi_netif_add( + &netif, + @ptrCast(lwip.c.IP4_ADDR_ANY), + @ptrCast(lwip.c.IP4_ADDR_ANY), + @ptrCast(lwip.c.IP4_ADDR_ANY), + null, + netif_init, + lwip.c.tcpip_input, + ); + maybe_netif = &netif; + try lwip.c_err(lwip.c.netifapi_netif_common(&netif, null, lwip.c.dhcp_start)); + + try radio.wifi.apply(.{ + .sta = .{ + .ssid = "Internet", + }, + }); + + try radio.wifi.start_blocking(); + try radio.wifi.connect_blocking(); + try lwip.c_err(lwip.c.netifapi_netif_common(&netif, lwip.c.netif_set_link_up, null)); + + ip_ready_semaphore.take(); + std.log.info("Listening on {f}:{}", .{ IPFormatter.init(netif.ip_addr), SERVER_PORT }); + + const server_conn = netconn_new_with_proto_and_callback(lwip.c.NETCONN_TCP, 0, null) orelse { + std.log.err("Failed to create netconn", .{}); + return error.FailedToCreateNetconn; + }; + defer { + _ = lwip.c.netconn_close(server_conn); + _ = lwip.c.netconn_delete(server_conn); + } + + try lwip.c_err(lwip.c.netconn_bind(server_conn, null, SERVER_PORT)); + + try lwip.c_err(lwip.c.netconn_listen(server_conn)); + std.log.info("TCP Server listening...", .{}); + + while (true) : (rtos.sleep(.from_ms(200))) { + var client_conn: ?*lwip.c.netconn = null; + + lwip.c_err(lwip.c.netconn_accept(server_conn, &client_conn)) catch |err| { + std.log.err("failed to accept connection: {t}", .{err}); + continue; + }; + defer { + _ = lwip.c.netconn_close(client_conn); + _ = lwip.c.netconn_delete(client_conn); + } + + std.log.info("New client connected!", .{}); + defer std.log.info("Client disconnected.", .{}); + handle_client(client_conn) catch |err| { + std.log.err("error while connected to client: {t}", .{err}); + continue; + }; + } +} + +fn handle_client(conn: ?*lwip.c.netconn) !void { + var buf: ?*lwip.c.netbuf = null; + while (true) { + lwip.c_err(lwip.c.netconn_recv(conn, &buf)) catch |err| switch (err) { + error.ConnectionClosed => break, + else => return err, + }; + defer lwip.c.netbuf_delete(buf); + + var data_ptr: ?*anyopaque = null; + var len: u16 = 0; + try lwip.c_err(lwip.c.netbuf_data(buf, &data_ptr, &len)); + + const slice = @as([*]u8, @ptrCast(data_ptr))[0..len]; + std.log.info("Received {} bytes: {s}", .{ len, slice }); + + // Echo back to client + try lwip.c_err(lwip.c.netconn_write_partly(conn, data_ptr, len, lwip.c.NETCONN_COPY, null)); + } +} + +fn on_packet_received(comptime _: radio.wifi.Interface, data: []const u8) void { + const pbuf: *lwip.c.struct_pbuf = lwip.c.pbuf_alloc(lwip.c.PBUF_RAW, @intCast(data.len), lwip.c.PBUF_POOL) orelse { + std.log.err("failed to allocate receive pbuf", .{}); + return; + }; + lwip.c_err(lwip.c.pbuf_take(pbuf, data.ptr, @intCast(data.len))) catch unreachable; + if (maybe_netif) |netif| { + lwip.c_err(netif.input.?(pbuf, netif)) catch |err| { + _ = lwip.c.pbuf_free(pbuf); + std.log.warn("tcpip_input failed: {t}", .{err}); + }; + } else { + _ = lwip.c.pbuf_free(pbuf); + } +} + +fn netif_init(netif_ptr: [*c]lwip.c.struct_netif) callconv(.c) lwip.c.err_t { + const netif = &netif_ptr[0]; + @memcpy(&netif.name, "w0"); + netif.linkoutput = netif_output; + netif.output = lwip.c.etharp_output; + netif.output_ip6 = lwip.c.ethip6_output; + netif.mtu = 1500; + netif.flags = lwip.c.NETIF_FLAG_BROADCAST | lwip.c.NETIF_FLAG_ETHARP | lwip.c.NETIF_FLAG_ETHERNET | lwip.c.NETIF_FLAG_IGMP | lwip.c.NETIF_FLAG_MLD6; + @memcpy(&netif.hwaddr, &radio.read_mac(.sta)); + netif.hwaddr_len = 6; + lwip.c.netif_create_ip6_linklocal_address(netif, 1); + lwip.c.netif_set_status_callback(netif, netif_status_callback); + lwip.c.netif_set_default(netif); + lwip.c.netif_set_up(netif); + return lwip.c.ERR_OK; +} + +fn netif_status_callback(netif_ptr: [*c]lwip.c.netif) callconv(.c) void { + const netif = &netif_ptr[0]; + if (netif.ip_addr.u_addr.ip4.addr != 0) { + ip = netif.ip_addr; + ip_ready_semaphore.give(); + } +} + +var packet_buf: [1600]u8 = undefined; + +fn netif_output(_: [*c]lwip.c.struct_netif, pbuf_c: [*c]lwip.c.struct_pbuf) callconv(.c) lwip.c.err_t { + const pbuf: *lwip.c.struct_pbuf = pbuf_c; + std.debug.assert(pbuf.tot_len < packet_buf.len); + + var off: usize = 0; + while (off < pbuf.tot_len) { + const cnt = lwip.c.pbuf_copy_partial(pbuf, packet_buf[off..].ptr, @as(u15, @intCast(pbuf.tot_len - off)), @as(u15, @intCast(off))); + if (cnt == 0) { + std.log.err("failed to copy network packet", .{}); + return lwip.c.ERR_BUF; + } + off += cnt; + } + + radio.wifi.send_packet(.sta, packet_buf[0..pbuf.tot_len]) catch |err| { + std.log.err("failed to send packet: {}", .{err}); + return lwip.c.ERR_IF; + }; + + return lwip.c.ERR_OK; +} + +const IPFormatter = struct { + addr: lwip.c.ip_addr_t, + + pub fn init(addr: lwip.c.ip_addr_t) IPFormatter { + return .{ .addr = addr }; + } + + pub fn format(addr: IPFormatter, writer: *std.Io.Writer) !void { + try writer.writeAll(std.mem.sliceTo(lwip.c.ip4addr_ntoa(@as(*const lwip.c.ip4_addr_t, @ptrCast(&addr.addr))), 0)); + } +}; diff --git a/examples/espressif/esp/src/wifi.zig b/examples/espressif/esp/src/wifi.zig deleted file mode 100644 index b7db77e6c..000000000 --- a/examples/espressif/esp/src/wifi.zig +++ /dev/null @@ -1,169 +0,0 @@ -const std = @import("std"); -const microzig = @import("microzig"); -const interrupt = microzig.cpu.interrupt; -const hal = microzig.hal; -const rtos = hal.rtos; -const radio = hal.radio; -const usb_serial_jtag = hal.usb_serial_jtag; - -const lwip = @import("lwip/include.zig"); -const exports = @import("lwip/exports.zig"); -comptime { - _ = exports; -} - -pub const microzig_options: microzig.Options = .{ - .log_level = .debug, - .log_scope_levels = &.{ - .{ - .scope = .esp_radio, - .level = .info, - }, - .{ - .scope = .esp_radio_wifi, - .level = .info, - }, - .{ - .scope = .esp_radio_osi, - .level = .info, - }, - .{ - .scope = .esp_wifi_driver_internal, - .level = .err, - }, - }, - .logFn = usb_serial_jtag.logger.log, - .interrupts = .{ - .interrupt29 = radio.interrupt_handler, - .interrupt30 = rtos.general_purpose_interrupt_handler, - .interrupt31 = rtos.yield_interrupt_handler, - }, - .cpu = .{ - .interrupt_stack = .{ - .enable = true, - }, - }, - .hal = .{ - .rtos = .{ - .enable = true, - .paint_stack = 0xbb, - }, - .radio = .{ - .wifi = .{ - .on_packet_received = on_packet_received, - }, - }, - }, -}; - -var netif: lwip.c.netif = undefined; - -pub fn main() !void { - var heap_allocator: microzig.Allocator = .init_with_heap(8192); - const gpa = heap_allocator.allocator(); - - try radio.wifi.init(gpa); - defer radio.wifi.deinit(); - - exports.gpa = gpa; - _ = lwip.c.tcpip_init(init_done, null); - - try radio.wifi.apply(.{ - .sta = .{ - .ssid = "Internet", - }, - }); - - try radio.wifi.start_blocking(); - try radio.wifi.connect_blocking(); - _ = lwip.c.netifapi_netif_common(&netif, lwip.c.netif_set_link_up, null); - - var count: usize = 0; - while (true) { - const free_heap = heap_allocator.free_heap(); - std.log.info("{} free memory: {}K ({})", .{ count, free_heap / 1024, free_heap }); - rtos.report_stack_usage(); - count += 1; - - rtos.sleep(.from_ms(5000)); - } -} - -fn on_packet_received(comptime _: radio.wifi.Interface, data: []const u8) void { - const maybe_pbuf: ?*lwip.c.struct_pbuf = lwip.c.pbuf_alloc(lwip.c.PBUF_RAW, @intCast(data.len), lwip.c.PBUF_POOL); - if (maybe_pbuf) |pbuf| { - _ = lwip.c.pbuf_take(pbuf, data.ptr, @intCast(data.len)); - if (lwip.c.tcpip_input(pbuf, &netif) != lwip.c.ERR_OK) { - std.log.warn("lwip netif input failed", .{}); - } - } -} - -fn init_done(_: ?*anyopaque) callconv(.c) void { - _ = lwip.c.netif_add( - &netif, - @ptrCast(lwip.c.IP4_ADDR_ANY), - @ptrCast(lwip.c.IP4_ADDR_ANY), - @ptrCast(lwip.c.IP4_ADDR_ANY), - null, - netif_init, - lwip.c.tcpip_input, - ); - _ = lwip.c.dhcp_start(&netif); -} - -fn netif_init(_: [*c]lwip.c.struct_netif) callconv(.c) lwip.c.err_t { - @memcpy(&netif.name, "e0"); - netif.linkoutput = netif_output; - netif.output = lwip.c.etharp_output; - netif.output_ip6 = lwip.c.ethip6_output; - netif.mtu = 1500; - netif.flags = lwip.c.NETIF_FLAG_BROADCAST | lwip.c.NETIF_FLAG_ETHARP | lwip.c.NETIF_FLAG_ETHERNET | lwip.c.NETIF_FLAG_IGMP | lwip.c.NETIF_FLAG_MLD6; - @memcpy(&netif.hwaddr, &radio.read_mac(.sta)); - netif.hwaddr_len = 6; - lwip.c.netif_create_ip6_linklocal_address(&netif, 1); - lwip.c.netif_set_status_callback(&netif, netif_status_callback); - lwip.c.netif_set_default(&netif); - lwip.c.netif_set_up(&netif); - - return lwip.c.ERR_OK; -} - -var packet_buf: [1600]u8 = undefined; - -fn netif_output(_: [*c]lwip.c.struct_netif, pbuf_c: [*c]lwip.c.struct_pbuf) callconv(.c) lwip.c.err_t { - const pbuf: *lwip.c.struct_pbuf = pbuf_c; - std.debug.assert(pbuf.tot_len < packet_buf.len); - - var off: usize = 0; - while (off < pbuf.tot_len) { - const cnt = lwip.c.pbuf_copy_partial(pbuf, packet_buf[off..].ptr, @as(u15, @intCast(pbuf.tot_len - off)), @as(u15, @intCast(off))); - if (cnt == 0) { - std.log.err("failed to copy network packet", .{}); - return lwip.c.ERR_BUF; - } - off += cnt; - } - - radio.wifi.send_packet(.sta, packet_buf[0..pbuf.tot_len]) catch |err| { - std.log.err("failed to send packet: {}", .{err}); - }; - - return lwip.c.ERR_OK; -} - -const IPFormatter = struct { - addr: lwip.c.ip_addr_t, - - pub fn init(addr: lwip.c.ip_addr_t) IPFormatter { - return .{ .addr = addr }; - } - - pub fn format(addr: IPFormatter, writer: *std.Io.Writer) !void { - try writer.writeAll(std.mem.sliceTo(lwip.c.ip4addr_ntoa(@as(*const lwip.c.ip4_addr_t, @ptrCast(&addr.addr))), 0)); - } -}; - -fn netif_status_callback(_: [*c]lwip.c.netif) callconv(.c) void { - std.log.info("netif status changed ip to {f}", .{IPFormatter.init(netif.ip_addr)}); -} diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index e65c0adbf..b64fe9bbb 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -544,6 +544,7 @@ fn _handle_interrupt( if (@intFromEnum(priority) < 15) { const mepc = csr.mepc.read_raw(); const mstatus = csr.mstatus.read_raw(); + const mtval = csr.mtval.read_raw(); const prev_thresh = interrupt.get_priority_threshold(); interrupt.set_priority_threshold(@enumFromInt(@intFromEnum(priority) + 1)); @@ -558,6 +559,8 @@ fn _handle_interrupt( csr.mepc.write_raw(mepc); csr.mstatus.write_raw(mstatus); + csr.mtval.write_raw(mtval); + csr.mcause.write(mcause); } else { handler(trap_frame); } diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 818cd71f6..786a40e37 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -31,12 +31,10 @@ pub var wifi_interrupt_handler: ?struct { extern fn vsnprintf(buffer: [*c]u8, len: usize, fmt: [*c]const u8, va_list: std.builtin.VaList) callconv(.c) void; -const log_esp_wifi_driver_internal = std.log.scoped(.esp_wifi_driver_internal); - fn syslog(fmt: ?[*:0]const u8, va_list: std.builtin.VaList) callconv(.c) void { var buf: [512:0]u8 = undefined; vsnprintf(&buf, 512, fmt, va_list); - log_esp_wifi_driver_internal.debug("{s}", .{std.mem.span((&buf).ptr)}); + log.debug("{s}", .{std.mem.span((&buf).ptr)}); } // ----- exports ----- @@ -112,10 +110,6 @@ pub fn calloc(number: usize, size: usize) callconv(.c) ?*anyopaque { } pub fn free(ptr: ?*anyopaque) callconv(.c) void { - // Avoid multiple frees at the same time as it causes a panic - microzig.cpu.interrupt.disable_interrupts(); - defer microzig.cpu.interrupt.enable_interrupts(); - log.debug("free {?}", .{ptr}); if (ptr == null) { @@ -130,7 +124,7 @@ pub fn free(ptr: ?*anyopaque) callconv(.c) void { pub fn puts(ptr: ?*anyopaque) callconv(.c) void { const s: []const u8 = std.mem.span(@as([*:0]const u8, @ptrCast(ptr))); - log_esp_wifi_driver_internal.debug("{s}", .{s}); + log.debug("{s}", .{s}); } pub fn gettimeofday(tv: ?*c.timeval, _: ?*anyopaque) callconv(.c) i32 { diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index f167a01f2..908839b41 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -11,10 +11,6 @@ const osi = @import("osi.zig"); const log = std.log.scoped(.esp_radio_wifi); -pub const InternalError = error{InternalError}; - -var inited: bool = false; - pub const Options = struct { on_event: ?*const fn (e: Event) void = null, on_packet_received: ?*const fn (comptime interface: Interface, data: []const u8) void = null, @@ -23,17 +19,149 @@ pub const Options = struct { const InitError = std.mem.Allocator.Error || InternalError; -pub fn init(gpa: std.mem.Allocator) InitError!void { - comptime export_symbols(); +pub const InitConfig = struct { + /// Power save mode. + power_save_mode: PowerSaveMode = .none, + + /// Country info. If .auto the country info of the AP to which the station + /// is connected is used. + country_info: CountryInfo = .auto, + /// Set maximum transmitting power after WiFi start. + /// TODO: explain what values are valid + /// TODO: should it be part of country info? + max_tx_power: i8 = 20, + + /// Max number of WiFi static RX buffers. + /// + /// Each buffer takes approximately 1.6KB of RAM. The static rx buffers are + /// allocated when esp_wifi_init is called, they are not freed until + /// esp_wifi_deinit is called. + /// + /// WiFi hardware use these buffers to receive all 802.11 frames. A higher + /// number may allow higher throughput but increases memory use. If + /// [`Self::ampdu_rx_enable`] is enabled, this value is recommended to set + /// equal or bigger than [`Self::rx_ba_win`] in order to achieve better + /// throughput and compatibility with both stations and APs. + static_rx_buf_num: u8 = 10, + + /// Max number of WiFi dynamic RX buffers + /// + /// Set the number of WiFi dynamic RX buffers, 0 means unlimited RX buffers + /// will be allocated (provided sufficient free RAM). The size of each + /// dynamic RX buffer depends on the size of the received data frame. + /// + /// For each received data frame, the WiFi driver makes a copy to an RX + /// buffer and then delivers it to the high layer TCP/IP stack. The dynamic + /// RX buffer is freed after the higher layer has successfully received the + /// data frame. + /// + /// For some applications, WiFi data frames may be received faster than the + /// application can process them. In these cases we may run out of memory + /// if RX buffer number is unlimited (0). + /// + /// If a dynamic RX buffer limit is set, it should be at least the number + /// of static RX buffers. + dynamic_rx_buf_num: u16 = 32, + + /// Set the number of WiFi static TX buffers. + /// + /// Each buffer takes approximately 1.6KB of RAM. The static RX buffers are + /// allocated when esp_wifi_init() is called, they are not released until + /// esp_wifi_deinit() is called. + /// + /// For each transmitted data frame from the higher layer TCP/IP stack, the + /// WiFi driver makes a copy of it in a TX buffer. + /// + /// For some applications especially UDP applications, the upper layer can + /// deliver frames faster than WiFi layer can transmit. In these cases, we + /// may run out of TX buffers. + static_tx_buf_num: u8 = 0, + + /// Set the number of WiFi dynamic TX buffers. + /// + /// The size of each dynamic TX buffer is not fixed, it depends on the size + /// of each transmitted data frame. + /// + /// For each transmitted frame from the higher layer TCP/IP stack, the WiFi + /// driver makes a copy of it in a TX buffer. + /// + /// For some applications, especially UDP applications, the upper layer can + /// deliver frames faster than WiFi layer can transmit. In these cases, we + /// may run out of TX buffers. + dynamic_tx_buf_num: u16 = 32, + + /// Select this option to enable AMPDU RX feature. + ampdu_rx_enable: bool = true, + + /// Select this option to enable AMPDU TX feature. + ampdu_tx_enable: bool = true, + + /// Select this option to enable AMSDU TX feature. + amsdu_tx_enable: bool = false, + + /// Set the size of WiFi Block Ack RX window. + /// + /// Generally a bigger value means higher throughput and better + /// compatibility but more memory. Most of time we should NOT change the + /// default value unless special reason, e.g. test the maximum UDP RX + /// throughput with iperf etc. For iperf test in shieldbox, the recommended + /// value is 9~12. + /// + /// If PSRAM is used and WiFi memory is preferred to allocate in PSRAM + /// first, the default and minimum value should be 16 to achieve better + /// throughput and compatibility with both stations and APs. + rx_ba_win: u8 = 6, + + pub const CountryInfo = union(enum) { + auto, + manual: struct { + /// Country code. + country_code: *const [2]u8 = "01", + /// Start channel of the allowed 2.4GHz Wi-Fi channels. + start_channel: u8 = 1, + /// Total channel number of the allowed 2.4GHz Wi-Fi channels. + total_channel_number: u8 = 11, + }, - if (inited) { - @panic("wifi already initialized"); - } + fn get_wifi_country_t(info: CountryInfo, max_tx_power: i8) c.wifi_country_t { + return switch (info) { + .auto => .{ + .cc = .{'0', '1', 0}, + .schan = 1, + .nchan = 11, + .max_tx_power = max_tx_power, + .policy = c.WIFI_COUNTRY_POLICY_AUTO + + }, + .manual => |manual_info| .{ + .cc = manual_info.country_code.* ++ .{0}, + .schan = manual_info.start_channel, + .nchan = manual_info.total_channel_number, + .max_tx_power = max_tx_power, + .policy = c.WIFI_COUNTRY_POLICY_MANUAL, + }, + }; + } + }; +}; + +pub fn init(gpa: std.mem.Allocator, config: InitConfig) InitError!void { + comptime export_symbols(); try radio.init(gpa); - init_config.wpa_crypto_funcs = c.g_wifi_default_wpa_crypto_funcs; - init_config.feature_caps = g_wifi_feature_caps; + { + init_config.wpa_crypto_funcs = c.g_wifi_default_wpa_crypto_funcs; + init_config.feature_caps = g_wifi_feature_caps; + init_config.static_rx_buf_num = config.static_rx_buf_num; + init_config.dynamic_rx_buf_num = config.dynamic_rx_buf_num; + init_config.static_tx_buf_num = config.static_tx_buf_num; + init_config.dynamic_tx_buf_num = config.dynamic_tx_buf_num; + init_config.ampdu_rx_enable = @intFromBool(config.ampdu_rx_enable); + init_config.ampdu_tx_enable = @intFromBool(config.ampdu_tx_enable); + init_config.amsdu_tx_enable = @intFromBool(config.amsdu_tx_enable); + init_config.rx_ba_win = config.rx_ba_win; + } // TODO: if coex enabled if (false) try c_result(c.coex_init()); @@ -49,35 +177,19 @@ pub fn init(gpa: std.mem.Allocator) InitError!void { try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_STA, recv_cb_sta)); { - // TODO: config - const country_code: [3]u8 = .{ 'C', 'N', 0 }; - const country: c.wifi_country_t = .{ - .cc = country_code, - .schan = 1, - .nchan = 13, - .max_tx_power = 20, - .policy = c.WIFI_COUNTRY_POLICY_MANUAL, - }; - try c_result(c.esp_wifi_set_country(&country)); + const country_info = config.country_info.get_wifi_country_t(config.max_tx_power); + try c_result(c.esp_wifi_set_country(&country_info)); } - try set_power_save_mode(.none); - - inited = true; + try set_power_save_mode(config.power_save_mode); } pub fn deinit() void { - if (!inited) { - @panic("trying to deinit the wifi controller but it isn't initialized"); - } - _ = c.esp_wifi_stop(); _ = c.esp_wifi_deinit_internal(); _ = c.esp_supplicant_deinit(); radio.deinit(); - - inited = false; } pub const ApplyConfigError = InternalError || error{ @@ -334,6 +446,22 @@ pub fn set_protocol(protocols: []const Config.AccessPoint.Protocol) InternalErro } } +/// Set the inactive time in seconds of the STA or AP. +/// +/// 1. For Station, if the station does not receive a beacon frame from the +/// connected SoftAP during the inactive time, disconnect from SoftAP. Default +/// 6s. Must be at least 3s. +/// +/// 2. For SoftAP, if the softAP doesn't receive any data from the connected +/// STA during inactive time, the softAP will force deauth the STA. Default is +/// 300s. Must be at least 10s. +pub fn set_inactive_time(interface: Interface, inactive_time: u32) InternalError!void { + try c_result(c.esp_wifi_set_inactive_time( + @intFromEnum(interface), + inactive_time, + )); +} + pub fn start() InternalError!void { try c_result(c.esp_wifi_start()); } @@ -659,23 +787,29 @@ pub fn on_event_post(id: i32, data: ?*anyopaque, data_size: usize) void { } } -// TODO: Should we also add ApState? Should we remove StaState? pub const StaState = enum(u32) { none, - sta_started, - sta_connected, - sta_disconnected, - sta_stopped, + started, + connected, + disconnected, + stopped, + + pub fn is_started(state: StaState) bool { + return switch (state) { + .started, .connected, .disconnected => true, + else => false, + }; + } }; var sta_state: std.atomic.Value(StaState) = .init(.none); fn update_sta_state(event: EventType) void { const new_sta_state: StaState = switch (event) { - .StaStart => .sta_started, - .StaConnected => .sta_connected, - .StaDisconnected => .sta_disconnected, - .StaStop => .sta_stopped, + .StaStart => .started, + .StaConnected => .connected, + .StaDisconnected => .disconnected, + .StaStop => .stopped, else => return, }; sta_state.store(new_sta_state, .monotonic); @@ -745,21 +879,21 @@ fn export_symbols() void { var init_config: c.wifi_init_config_t = .{ .osi_funcs = &g_wifi_osi_funcs, // .wpa_crypto_funcs = c.g_wifi_default_wpa_crypto_funcs, - .static_rx_buf_num = 10, - .dynamic_rx_buf_num = 32, + .static_rx_buf_num = 10, // overwritten by init + .dynamic_rx_buf_num = 32, // overwritten by init .tx_buf_type = c.CONFIG_ESP_WIFI_TX_BUFFER_TYPE, - .static_tx_buf_num = 0, - .dynamic_tx_buf_num = 32, + .static_tx_buf_num = 0, // overwritten by init + .dynamic_tx_buf_num = 32, // overwritten by init .rx_mgmt_buf_type = c.CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER, .rx_mgmt_buf_num = c.CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF, .cache_tx_buf_num = c.WIFI_CACHE_TX_BUFFER_NUM, .csi_enable = 0, // TODO: WiFi channel state information enable flag. - .ampdu_rx_enable = 1, - .ampdu_tx_enable = 1, - .amsdu_tx_enable = 0, + .ampdu_rx_enable = 1, // overwritten by init + .ampdu_tx_enable = 1, // overwritten by init + .amsdu_tx_enable = 0, // overwritten by init .nvs_enable = 0, .nano_enable = 0, - .rx_ba_win = 6, + .rx_ba_win = 6, // overwritten by init .wifi_task_core_id = 0, .beacon_max_len = c.WIFI_SOFTAP_BEACON_MAX_LEN, .mgmt_sbuf_num = c.WIFI_MGMT_SBUF_NUM, @@ -903,8 +1037,23 @@ var g_wifi_osi_funcs: c.wifi_osi_funcs_t = .{ ._magic = @bitCast(c.ESP_WIFI_OS_ADAPTER_MAGIC), }; -pub fn c_result(err_code: i32) InternalError!void { - const InternalWifiError = enum(i32) { +pub const InternalError = error { + OutOfMemory, + InvalidArg, + Timeout, + WouldBlock, + WifiNotInitialized, + WifiNotStarted, + WifiNotStopped, + WifiNotConnected, + WifiInvalid_MAC, + WifiInvalid_SSID, + WifiInvalidPassword, + WifiOther, +}; + +pub fn c_result(raw_err_code: i32) InternalError!void { + const InternalWifiErrorCode = enum(i32) { /// Out of memory esp_err_no_mem = 0x101, @@ -972,10 +1121,23 @@ pub fn c_result(err_code: i32) InternalError!void { esp_err_wifi_tx_disallow = 0x3016, }; - if (err_code != c.ESP_OK) { - const err: InternalWifiError = @enumFromInt(err_code); - log.err("internal wifi error occurred: {s}", .{@tagName(err)}); - return error.InternalError; + if (raw_err_code != c.ESP_OK) { + const err_code = std.enums.fromInt(InternalWifiErrorCode, raw_err_code) orelse + @panic("esp wifi returned unexpected error code"); + log.debug("internal wifi error occurred: {t}", .{err_code}); + return switch (err_code) { + .esp_err_no_mem => error.OutOfMemory, + .esp_err_wifi_timeout => error.Timeout, + .esp_err_wifi_would_block => error.WouldBlock, + .esp_err_wifi_not_init => error.WifiNotInitialized, + .esp_err_wifi_not_started => error.WifiNotStarted, + .esp_err_wifi_not_connect => error.WifiNotConnected, + .esp_err_wifi_not_stopped => error.WifiNotStopped, + .esp_err_wifi_mac => error.WifiInvalid_MAC, + .esp_err_wifi_ssid => error.WifiInvalid_SSID, + .esp_err_wifi_password => error.WifiInvalidPassword, + else => error.WifiOther, + }; } } diff --git a/port/espressif/esp/src/hal/rtos.zig b/port/espressif/esp/src/hal/rtos.zig index 866f95bf8..86518da72 100644 --- a/port/espressif/esp/src/hal/rtos.zig +++ b/port/espressif/esp/src/hal/rtos.zig @@ -30,11 +30,9 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; // TODO: implement priority inheritance for sync primitives // TODO: better handling if timeout is in the past or very short // TODO: support SMP for other esp32 chips with multicore (far future) -// NOTE: don't use anonymous structs for reinitializing state unions with -// previous union state to avoid compiler bug (0.15.2) const STACK_ALIGN: std.mem.Alignment = .@"16"; -const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 64 * @sizeOf(usize)); +const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 32 * @sizeOf(usize)); const rtos_options = microzig.options.hal.rtos; pub const Priority = rtos_options.Priority; @@ -133,13 +131,38 @@ fn idle() linksection(".ram_text") callconv(.naked) void { // appears to have solved the problem but the root cause is still a // mistery. asm volatile ( - \\1: \\csrsi mstatus, 8 # make sure interrupts are enabled + \\1: \\wfi \\j 1b ); } +// fn idle() linksection(".ram_text") callconv(.c) void { +// microzig.cpu.interrupt.enable_interrupts(); +// +// while (true) { +// { +// const cs = microzig.interrupt.enter_critical_section(); +// defer cs.leave(); +// +// std.log.info("idle with interrupts enabled = {}", .{cs.enable_on_leave}); +// } +// +// microzig.cpu.wfi(); +// } +// +// // TODO: cpu would hang after a while. Enabling interrupts before wfi +// // appears to have solved the problem but the root cause is still a +// // mistery. +// asm volatile ( +// \\csrsi mstatus, 8 # make sure interrupts are enabled +// \\1: +// \\wfi +// \\j 1b +// ); +// } + pub fn get_current_task() *Task { return rtos_state.current_task; } @@ -411,6 +434,7 @@ pub const yield_interrupt_handler: microzig.cpu.InterruptHandler = .{ \\sw a5, 13*4(sp) \\sw a6, 14*4(sp) \\sw a7, 15*4(sp) + // s0 is saved in context \\sw s1, 16*4(sp) \\sw s2, 17*4(sp) \\sw s3, 18*4(sp) @@ -425,11 +449,11 @@ pub const yield_interrupt_handler: microzig.cpu.InterruptHandler = .{ \\sw gp, 27*4(sp) \\sw tp, 28*4(sp) \\ - \\csrr a1, mepc - \\sw a1, 29*4(sp) + \\csrr a0, mepc + \\sw a0, 29*4(sp) \\ - \\csrr a1, mstatus - \\sw a1, 30*4(sp) + \\csrr a0, mstatus + \\sw a0, 30*4(sp) \\ // save sp for later \\mv a2, sp @@ -448,7 +472,6 @@ pub const yield_interrupt_handler: microzig.cpu.InterruptHandler = .{ \\ // first parameter is a pointer to context \\mv a0, sp - \\mv s1, a1 \\jal %[schedule_in_isr] \\ // load next task context @@ -460,12 +483,13 @@ pub const yield_interrupt_handler: microzig.cpu.InterruptHandler = .{ \\ // if the next task program counter is equal to 1f's location // just jump to it (ie. the task forcefully yielded). - \\beq a1, s1, 1f + \\la a0, 1f + \\beq a1, a0, 1f \\ // ensure interrupts are disabled after mret (when a normal // context switch occured) - \\li t0, 0x80 - \\csrc mstatus, t0 + \\li a0, 0x80 + \\csrc mstatus, a0 \\ // jump to new task \\csrw mepc, a1 @@ -473,11 +497,11 @@ pub const yield_interrupt_handler: microzig.cpu.InterruptHandler = .{ \\ \\1: \\ - \\lw t1, 30*4(sp) - \\csrw mstatus, t1 + \\lw a0, 30*4(sp) + \\csrw mstatus, a0 \\ - \\lw t0, 29*4(sp) - \\csrw mepc, t0 + \\lw a0, 29*4(sp) + \\csrw mepc, a0 \\ \\lw ra, 0*4(sp) \\lw t0, 1*4(sp) @@ -1120,6 +1144,15 @@ pub fn Queue(Elem: type) type { return buf[0]; } + pub fn get_one_non_blocking(q: *Self) ?Elem { + var buf: [1]Elem = undefined; + if (q.type_erased.get_non_blocking(@ptrCast(&buf)) == 1) { + return buf[0]; + } else { + return null; + } + } + pub fn capacity(q: *const Self) usize { return @divExact(q.type_erased.buffer.len, @sizeOf(Elem)); } From dc52ce1a41bdb1bfca0d106c38c4312b9c988b5a Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Fri, 16 Jan 2026 10:20:50 +0200 Subject: [PATCH 27/43] Cleanup --- examples/espressif/esp/src/lwip/exports.zig | 3 +-- examples/espressif/esp/src/tcp_server.zig | 2 -- port/espressif/esp/src/hal/radio/wifi.zig | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/espressif/esp/src/lwip/exports.zig b/examples/espressif/esp/src/lwip/exports.zig index 788370bf3..67892076b 100644 --- a/examples/espressif/esp/src/lwip/exports.zig +++ b/examples/espressif/esp/src/lwip/exports.zig @@ -10,8 +10,7 @@ const rtos = esp.rtos; const c = @import("include.zig").c; const log = std.log.scoped(.lwip); -// TODO: we use a different allocator here than the one in osi. maybe we can -// have only one. + // TODO: mutex and mailbox can be implemented without allocations if we define // the correct types in sys_arch.h diff --git a/examples/espressif/esp/src/tcp_server.zig b/examples/espressif/esp/src/tcp_server.zig index 465f248dc..c2c40a70d 100644 --- a/examples/espressif/esp/src/tcp_server.zig +++ b/examples/espressif/esp/src/tcp_server.zig @@ -21,7 +21,6 @@ pub const microzig_options: microzig.Options = .{ .{ .scope = .esp_radio, .level = .info }, .{ .scope = .esp_radio_wifi, .level = .info }, .{ .scope = .esp_radio_osi, .level = .info }, - .{ .scope = .esp_wifi_driver_internal, .level = .err }, }, .logFn = usb_serial_jtag.logger.log, .interrupts = .{ @@ -38,7 +37,6 @@ pub const microzig_options: microzig.Options = .{ .hal = .{ .rtos = .{ .enable = true, - .paint_stack = 0xbb, }, .radio = .{ .wifi = .{ diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index 908839b41..869a6ddca 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -759,7 +759,6 @@ pub fn on_event_post(id: i32, data: ?*anyopaque, data_size: usize) void { const event_type: EventType = @enumFromInt(id); update_sta_state(event_type); - log.info("event occurred: {t}", .{event_type}); { event_mutex.lock(); From 604f769d370ac0d2c8f1e4df0ff0f49362f24a6f Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Fri, 16 Jan 2026 10:25:17 +0200 Subject: [PATCH 28/43] Remove SPSC_Queue --- core/src/concurrency.zig | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/core/src/concurrency.zig b/core/src/concurrency.zig index 24b4fc403..0e7369acd 100644 --- a/core/src/concurrency.zig +++ b/core/src/concurrency.zig @@ -159,41 +159,3 @@ test "AtomicStaticBitSet set_first_available" { const err = bs.set_first_available(); try std.testing.expectError(AtomicStaticBitSetError.NoAvailableBit, err); } - -// TODO: docs + tests + someone please verify it -pub fn SPSC_Queue(Item: type, capacity: usize) type { - return struct { - const Self = @This(); - - pub const empty: Self = .{ - .buf = undefined, - .read_index = 0, - .write_index = 0, - .len = .init(0), - }; - - buf: [capacity]Item, - read_index: usize, - write_index: usize, - len: std.atomic.Value(usize), - - pub fn enqueue(self: *Self, item: Item) error{NoSpace}!void { - if (self.len.load(.acquire) < capacity) { - self.buf[self.write_index] = item; - self.write_index = (self.write_index + 1) % capacity; - _ = self.len.fetchAdd(1, .acq_rel); - } else { - return error.NoSpace; - } - } - - pub fn dequeue(self: *Self) ?Item { - if (self.len.load(.acquire) == 0) return null; - - const item = self.buf[self.read_index]; - self.read_index = (self.read_index + 1) % capacity; - _ = self.len.fetchSub(1, .acq_rel); - return item; - } - }; -} From f9ecffda93cdba99c3ab317a09d778097bb1baab Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Fri, 16 Jan 2026 10:46:26 +0200 Subject: [PATCH 29/43] More cleanup --- .../espressif/esp/src/lwip/include/arch/sys_arch.h | 4 ++++ modules/foundation-libc/include/stdlib.h | 3 --- modules/riscv32-common/src/riscv32_common.zig | 2 -- port/espressif/esp/build.zig | 8 ++++---- port/espressif/esp/src/cpus/esp_riscv.zig | 13 +++++++++++-- port/espressif/esp/src/hal.zig | 2 +- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/examples/espressif/esp/src/lwip/include/arch/sys_arch.h b/examples/espressif/esp/src/lwip/include/arch/sys_arch.h index 554399ace..106005cab 100644 --- a/examples/espressif/esp/src/lwip/include/arch/sys_arch.h +++ b/examples/espressif/esp/src/lwip/include/arch/sys_arch.h @@ -14,4 +14,8 @@ typedef uint32_t sys_prot_t; #define SYS_SEM_NULL NULL #define SYS_MUTEX_NULL NULL +void* malloc(size_t size); +void* calloc(size_t count, size_t size); +void free(void* ptr); + #endif /* __SYS_ARCH_H__ */ diff --git a/modules/foundation-libc/include/stdlib.h b/modules/foundation-libc/include/stdlib.h index 3d1ceeb6c..a3fb8ee33 100644 --- a/modules/foundation-libc/include/stdlib.h +++ b/modules/foundation-libc/include/stdlib.h @@ -2,8 +2,5 @@ #define _FOUNDATION_LIBC_STDLIB_H_ int atoi(char const * str); -void* malloc(size_t size); -void* calloc(size_t count, size_t size); -void free(void* ptr); #endif diff --git a/modules/riscv32-common/src/riscv32_common.zig b/modules/riscv32-common/src/riscv32_common.zig index 4687dd7e1..72a1da7be 100644 --- a/modules/riscv32-common/src/riscv32_common.zig +++ b/modules/riscv32-common/src/riscv32_common.zig @@ -33,13 +33,11 @@ pub const interrupt = struct { } pub fn enable_interrupts() void { - fence(); csr.mstatus.set(.{ .mie = 1 }); } pub fn disable_interrupts() void { csr.mstatus.clear(.{ .mie = 1 }); - fence(); } pub const core = utilities.interrupt.CoreImpl(CoreInterrupt); diff --git a/port/espressif/esp/build.zig b/port/espressif/esp/build.zig index 61d959bfd..9f9519ee0 100644 --- a/port/espressif/esp/build.zig +++ b/port/espressif/esp/build.zig @@ -34,7 +34,7 @@ pub fn init(dep: *std.Build.Dependency) Self { const esp_wifi_driver_mod = make_esp_wifi_driver_module(b, "esp32c3", esp32_c3_zig_target); - const chip_esp32c3: microzig.Target = .{ + const chip_esp32_c3: microzig.Target = .{ .dep = dep, .preferred_binary_format = .{ .esp = .{ .chip_id = .esp32_c3, @@ -95,8 +95,8 @@ pub fn init(dep: *std.Build.Dependency) Self { return .{ .chips = .{ - .esp32_c3 = chip_esp32c3.derive(.{}), - .esp32_c3_direct_boot = chip_esp32c3.derive(.{ + .esp32_c3 = chip_esp32_c3.derive(.{}), + .esp32_c3_direct_boot = chip_esp32_c3.derive(.{ .preferred_binary_format = .bin, .cpu = .{ .name = "esp_riscv", @@ -122,7 +122,7 @@ pub fn init(dep: *std.Build.Dependency) Self { ), }, }), - .esp32_c3_flashless = chip_esp32c3.derive(.{ + .esp32_c3_flashless = chip_esp32_c3.derive(.{ .ram_image = true, .linker_script = .{ .generate = .memory_regions, diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index b64fe9bbb..f7a7e9169 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -78,8 +78,17 @@ pub const InterruptOptions = microzig.utilities.GenerateInterruptOptions(&.{ pub const interrupt = struct { pub const globally_enabled = riscv32_common.interrupt.globally_enabled; - pub const enable_interrupts = riscv32_common.interrupt.enable_interrupts; - pub const disable_interrupts = riscv32_common.interrupt.disable_interrupts; + + pub fn enable_interrupts() void { + fence(); + csr.mstatus.set(.{ .mie = 1 }); + } + + pub fn disable_interrupts() void { + csr.mstatus.clear(.{ .mie = 1 }); + fence(); + } + const INTERRUPT_CORE0 = microzig.chip.peripherals.INTERRUPT_CORE0; diff --git a/port/espressif/esp/src/hal.zig b/port/espressif/esp/src/hal.zig index 2575189b5..da6b6e679 100644 --- a/port/espressif/esp/src/hal.zig +++ b/port/espressif/esp/src/hal.zig @@ -38,7 +38,7 @@ pub const HAL_Options = struct { }; /// Clock config applied by the default `init()` function of the hal. -pub const clock_config: clocks.Config = .init_comptime(80_000_000); +pub const clock_config: clocks.Config = .default; pub fn init() void { init_sequence(clock_config); From c44c87360428290eade9177b0816b59f8a7dd804 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Fri, 16 Jan 2026 11:20:52 +0200 Subject: [PATCH 30/43] Update network module and fix tbss workaround --- examples/espressif/esp/build.zig | 1 + modules/network/build.zig | 68 ++++++++----------- .../esp/ld/esp32_c3/direct_boot_sections.ld | 6 -- .../esp/ld/esp32_c3/flashless_sections.ld | 5 -- .../esp/ld/esp32_c3/image_boot_sections.ld | 8 --- 5 files changed, 29 insertions(+), 59 deletions(-) diff --git a/examples/espressif/esp/build.zig b/examples/espressif/esp/build.zig index e27471544..f902ca26a 100644 --- a/examples/espressif/esp/build.zig +++ b/examples/espressif/esp/build.zig @@ -61,6 +61,7 @@ pub fn build(b: *std.Build) void { const foundation_dep = b.dependency("foundation_libc", .{ .target = target, .optimize = optimize, + .single_threaded = true, }); const lwip_dep = b.dependency("lwip", .{ diff --git a/modules/network/build.zig b/modules/network/build.zig index b777f7321..77e4594b6 100644 --- a/modules/network/build.zig +++ b/modules/network/build.zig @@ -37,37 +37,6 @@ pub fn build(b: *std.Build) void { @panic("Invalid lwip packet buffer length"); } - const lwip_mod = brk: { - var buf: [32]u8 = undefined; - - const foundation_dep = b.dependency("foundationlibc", .{ - .target = target, - .optimize = optimize, - .single_threaded = true, - }); - const lwip_dep = b.dependency("lwip", .{ - .target = target, - .optimize = optimize, - }); - const lwip_mod = lwip_dep.module("lwip"); - // Link libc - lwip_mod.linkLibrary(foundation_dep.artifact("foundation")); - - // MEM_ALIGNMENT of 4 bytes allows us to use packet buffers in u32 dma. - lwip_mod.addCMacro("MEM_ALIGNMENT", "4"); - lwip_mod.addCMacro("MEM_SIZE", to_s(&buf, mem_size)); - lwip_mod.addCMacro("PBUF_POOL_SIZE", to_s(&buf, pbuf_pool_size)); - lwip_mod.addCMacro("PBUF_LINK_HLEN", to_s(&buf, ethernet_header)); - lwip_mod.addCMacro("PBUF_POOL_BUFSIZE", to_s(&buf, pbuf_length)); - lwip_mod.addCMacro("PBUF_LINK_ENCAPSULATION_HLEN", to_s(&buf, pbuf_header_length)); - // 40 bytes IPv6 header, 20 bytes TCP header - lwip_mod.addCMacro("TCP_MSS", to_s(&buf, mtu - 40 - 20)); - - // Path to lwipopts.h - lwip_mod.addIncludePath(b.path("src/include")); - break :brk lwip_mod; - }; - const net_mod = b.addModule("net", .{ .target = target, .optimize = optimize, @@ -77,15 +46,34 @@ pub fn build(b: *std.Build) void { .module = b.dependency("link", .{}).module("link"), }}, }); - net_mod.addImport("lwip", lwip_mod); - // Copy macros and include dirs from lwip to net, so we have same values - // when calling translate-c from cImport. - for (lwip_mod.c_macros.items) |m| { - net_mod.c_macros.append(b.allocator, m) catch @panic("out of memory"); - } - for (lwip_mod.include_dirs.items) |dir| { - net_mod.include_dirs.append(b.allocator, dir) catch @panic("out of memory"); - } + + var buf: [32]u8 = undefined; + + const foundation_dep = b.dependency("foundationlibc", .{ + .target = target, + .optimize = optimize, + .single_threaded = true, + }); + const lwip_dep = b.dependency("lwip", .{ + .target = target, + .optimize = optimize, + .include_dir = b.path("src/include"), + }); + const lwip_lib = lwip_dep.artifact("lwip"); + + lwip_lib.root_module.linkLibrary(foundation_dep.artifact("foundation")); + net_mod.linkLibrary(lwip_lib); + + // MEM_ALIGNMENT of 4 bytes allows us to use packet buffers in u32 dma. + net_mod.addCMacro("MEM_ALIGNMENT", "4"); + net_mod.addCMacro("MEM_SIZE", to_s(&buf, mem_size)); + net_mod.addCMacro("PBUF_POOL_SIZE", to_s(&buf, pbuf_pool_size)); + net_mod.addCMacro("PBUF_LINK_HLEN", to_s(&buf, ethernet_header)); + net_mod.addCMacro("PBUF_POOL_BUFSIZE", to_s(&buf, pbuf_length)); + net_mod.addCMacro("PBUF_LINK_ENCAPSULATION_HLEN", to_s(&buf, pbuf_header_length)); + // 40 bytes IPv6 header, 20 bytes TCP header + net_mod.addCMacro("TCP_MSS", to_s(&buf, mtu - 40 - 20)); + const options = b.addOptions(); options.addOption(u16, "mtu", mtu); options.addOption(u16, "pbuf_length", pbuf_length); diff --git a/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld index 89882a9c5..770c6f2ba 100644 --- a/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/direct_boot_sections.ld @@ -74,12 +74,6 @@ SECTIONS microzig_bss_end = .; } > DRAM - .tbss (NOLOAD) : - { - . = ALIGN(16); - *(.tbss*) - } > DRAM - .heap (NOLOAD) : { microzig_heap_start = .; diff --git a/port/espressif/esp/ld/esp32_c3/flashless_sections.ld b/port/espressif/esp/ld/esp32_c3/flashless_sections.ld index 44e3e9fc9..833eaf141 100644 --- a/port/espressif/esp/ld/esp32_c3/flashless_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/flashless_sections.ld @@ -50,11 +50,6 @@ SECTIONS microzig_bss_end = .; } > DRAM - .tbss (NOLOAD) : - { - *(.tbss*) - } > DRAM - .heap (NOLOAD) : { microzig_heap_start = .; diff --git a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld index 10fb529a9..bcc305a03 100644 --- a/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld +++ b/port/espressif/esp/ld/esp32_c3/image_boot_sections.ld @@ -107,14 +107,6 @@ SECTIONS microzig_bss_end = .; } > DRAM - /* FIXME: this is a workaround for the threadlocal variable that may be - * created by foundation libc, because otherwise DRAM overflows (if .tbss is - * not explicitly added, it is placed after .heap) */ - .tbss (NOLOAD) : - { - *(.tbss*) - } > DRAM - .heap (NOLOAD) : { microzig_heap_start = .; From 9fd611ed47347bb93e94955c7c9888fb2d26ea25 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Fri, 16 Jan 2026 11:28:36 +0200 Subject: [PATCH 31/43] Refactor example and fix CI --- build.zig | 4 +++- examples/espressif/esp/src/tcp_server.zig | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/build.zig b/build.zig index 006315e11..f37abaefb 100644 --- a/build.zig +++ b/build.zig @@ -45,10 +45,12 @@ const exe_targets: []const std.Target.Query = &.{ }; pub fn build(b: *Build) void { + const optimize = b.standardOptimizeOption(.{}); + const generate_linker_script_mod = b.createModule(.{ .root_source_file = b.path("tools/generate_linker_script.zig"), .target = b.graph.host, - .optimize = .ReleaseSafe, + .optimize = optimize, }); const generate_linker_script_exe = b.addExecutable(.{ diff --git a/examples/espressif/esp/src/tcp_server.zig b/examples/espressif/esp/src/tcp_server.zig index c2c40a70d..61f7b0cbc 100644 --- a/examples/espressif/esp/src/tcp_server.zig +++ b/examples/espressif/esp/src/tcp_server.zig @@ -46,6 +46,8 @@ pub const microzig_options: microzig.Options = .{ }, }; +const SSID = "SSID"; +const AUTH: ?radio.wifi.Config.Auth = null; const SERVER_PORT = 3333; var maybe_netif: ?*lwip.c.netif = null; @@ -79,7 +81,8 @@ pub fn main() !void { try radio.wifi.apply(.{ .sta = .{ - .ssid = "Internet", + .ssid = SSID, + .auth = AUTH, }, }); From 84a5b4f2620fbc3c7f9134d53bd7115002ab828f Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Fri, 16 Jan 2026 11:32:44 +0200 Subject: [PATCH 32/43] Take care of lints --- examples/espressif/esp/src/tcp_server.zig | 14 +++++++------- port/espressif/esp/src/hal/rtos.zig | 2 +- port/espressif/esp/src/hal/system.zig | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/espressif/esp/src/tcp_server.zig b/examples/espressif/esp/src/tcp_server.zig index 61f7b0cbc..c4119d839 100644 --- a/examples/espressif/esp/src/tcp_server.zig +++ b/examples/espressif/esp/src/tcp_server.zig @@ -18,9 +18,9 @@ comptime { pub const microzig_options: microzig.Options = .{ .log_level = .debug, .log_scope_levels = &.{ - .{ .scope = .esp_radio, .level = .info }, - .{ .scope = .esp_radio_wifi, .level = .info }, - .{ .scope = .esp_radio_osi, .level = .info }, + .{ .scope = .esp_radio, .level = .err }, + .{ .scope = .esp_radio_wifi, .level = .err }, + .{ .scope = .esp_radio_osi, .level = .err }, }, .logFn = usb_serial_jtag.logger.log, .interrupts = .{ @@ -91,7 +91,7 @@ pub fn main() !void { try lwip.c_err(lwip.c.netifapi_netif_common(&netif, lwip.c.netif_set_link_up, null)); ip_ready_semaphore.take(); - std.log.info("Listening on {f}:{}", .{ IPFormatter.init(netif.ip_addr), SERVER_PORT }); + std.log.info("Listening on {f}:{}", .{ IP_Formatter.init(netif.ip_addr), SERVER_PORT }); const server_conn = netconn_new_with_proto_and_callback(lwip.c.NETCONN_TCP, 0, null) orelse { std.log.err("Failed to create netconn", .{}); @@ -214,14 +214,14 @@ fn netif_output(_: [*c]lwip.c.struct_netif, pbuf_c: [*c]lwip.c.struct_pbuf) call return lwip.c.ERR_OK; } -const IPFormatter = struct { +const IP_Formatter = struct { addr: lwip.c.ip_addr_t, - pub fn init(addr: lwip.c.ip_addr_t) IPFormatter { + pub fn init(addr: lwip.c.ip_addr_t) IP_Formatter { return .{ .addr = addr }; } - pub fn format(addr: IPFormatter, writer: *std.Io.Writer) !void { + pub fn format(addr: IP_Formatter, writer: *std.Io.Writer) !void { try writer.writeAll(std.mem.sliceTo(lwip.c.ip4addr_ntoa(@as(*const lwip.c.ip4_addr_t, @ptrCast(&addr.addr))), 0)); } }; diff --git a/port/espressif/esp/src/hal/rtos.zig b/port/espressif/esp/src/hal/rtos.zig index 86518da72..2d0f6d698 100644 --- a/port/espressif/esp/src/hal/rtos.zig +++ b/port/espressif/esp/src/hal/rtos.zig @@ -49,7 +49,7 @@ pub const Options = struct { general_purpose_interrupt: microzig.cpu.Interrupt = .interrupt30, systimer_unit: systimer.Unit = .unit0, systimer_alarm: systimer.Alarm = .alarm0, - cpu_interrupt: system.CpuInterrupt = .cpu_interrupt_0, + cpu_interrupt: system.CPU_Interrupt = .cpu_interrupt_0, yield_interrupt: microzig.cpu.Interrupt = .interrupt31, paint_stack: ?u8 = null, diff --git a/port/espressif/esp/src/hal/system.zig b/port/espressif/esp/src/hal/system.zig index af0019cac..a1643d8ca 100644 --- a/port/espressif/esp/src/hal/system.zig +++ b/port/espressif/esp/src/hal/system.zig @@ -125,13 +125,13 @@ pub fn enable_clocks_and_release_reset(mask: PeripheralMask) void { peripheral_reset_clear(mask); } -pub const CpuInterrupt = enum { +pub const CPU_Interrupt = enum { cpu_interrupt_0, cpu_interrupt_1, cpu_interrupt_2, cpu_interrupt_3, - pub fn source(cpu_interrupt: CpuInterrupt) microzig.cpu.interrupt.Source { + pub fn source(cpu_interrupt: CPU_Interrupt) microzig.cpu.interrupt.Source { return switch (cpu_interrupt) { .cpu_interrupt_0 => .from_cpu_intr0, .cpu_interrupt_1 => .from_cpu_intr1, @@ -140,7 +140,7 @@ pub const CpuInterrupt = enum { }; } - pub fn set_pending(cpu_interrupt: CpuInterrupt, enabled: bool) void { + pub fn set_pending(cpu_interrupt: CPU_Interrupt, enabled: bool) void { const regs: @TypeOf(&SYSTEM.CPU_INTR_FROM_CPU_0) = switch (cpu_interrupt) { .cpu_interrupt_0 => @ptrCast(&SYSTEM.CPU_INTR_FROM_CPU_0), .cpu_interrupt_1 => @ptrCast(&SYSTEM.CPU_INTR_FROM_CPU_1), From 02db8ed035dccdc291ef155f6f95bde8d14b5ab4 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Fri, 16 Jan 2026 11:40:00 +0200 Subject: [PATCH 33/43] Fix some of the formatting (that does not crash zig) --- examples/espressif/esp/src/lwip/exports.zig | 7 ++++--- port/espressif/esp/src/hal/radio/wifi.zig | 7 +++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/espressif/esp/src/lwip/exports.zig b/examples/espressif/esp/src/lwip/exports.zig index 67892076b..93f38dffe 100644 --- a/examples/espressif/esp/src/lwip/exports.zig +++ b/examples/espressif/esp/src/lwip/exports.zig @@ -189,9 +189,10 @@ fn task_wrapper( } export fn sys_thread_new(name: [*:0]u8, thread: c.lwip_thread_fn, arg: ?*anyopaque, stacksize: c_int, prio: c_int) c.sys_thread_t { - _ = stacksize; // autofix - _ = prio; // autofix - return rtos.spawn(gpa, task_wrapper, .{thread, arg}, .{ + _ = stacksize; + _ = prio; + + return rtos.spawn(gpa, task_wrapper, .{ thread, arg }, .{ .name = std.mem.span(name), .stack_size = 4096, .priority = @enumFromInt(2), diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index 869a6ddca..0ebe409eb 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -126,12 +126,11 @@ pub const InitConfig = struct { fn get_wifi_country_t(info: CountryInfo, max_tx_power: i8) c.wifi_country_t { return switch (info) { .auto => .{ - .cc = .{'0', '1', 0}, + .cc = .{ '0', '1', 0 }, .schan = 1, .nchan = 11, .max_tx_power = max_tx_power, - .policy = c.WIFI_COUNTRY_POLICY_AUTO - + .policy = c.WIFI_COUNTRY_POLICY_AUTO, }, .manual => |manual_info| .{ .cc = manual_info.country_code.* ++ .{0}, @@ -1036,7 +1035,7 @@ var g_wifi_osi_funcs: c.wifi_osi_funcs_t = .{ ._magic = @bitCast(c.ESP_WIFI_OS_ADAPTER_MAGIC), }; -pub const InternalError = error { +pub const InternalError = error{ OutOfMemory, InvalidArg, Timeout, From 42c5abdcd541b0ae86034af7b05c138ae6a5c44d Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Fri, 16 Jan 2026 11:48:55 +0200 Subject: [PATCH 34/43] Hide warnings --- examples/espressif/esp/src/tcp_server.zig | 1 + port/espressif/esp/src/hal/radio/timer.zig | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/espressif/esp/src/tcp_server.zig b/examples/espressif/esp/src/tcp_server.zig index c4119d839..102abce94 100644 --- a/examples/espressif/esp/src/tcp_server.zig +++ b/examples/espressif/esp/src/tcp_server.zig @@ -19,6 +19,7 @@ pub const microzig_options: microzig.Options = .{ .log_level = .debug, .log_scope_levels = &.{ .{ .scope = .esp_radio, .level = .err }, + .{ .scope = .esp_radio_timer, .level = .err }, .{ .scope = .esp_radio_wifi, .level = .err }, .{ .scope = .esp_radio_osi, .level = .err }, }, diff --git a/port/espressif/esp/src/hal/radio/timer.zig b/port/espressif/esp/src/hal/radio/timer.zig index c4ff7e00a..e04ee2b3f 100644 --- a/port/espressif/esp/src/hal/radio/timer.zig +++ b/port/espressif/esp/src/hal/radio/timer.zig @@ -1,13 +1,14 @@ const std = @import("std"); -const log = std.log.scoped(.radio_timer); const Allocator = std.mem.Allocator; +const c = @import("esp-wifi-driver"); const microzig = @import("microzig"); const time = microzig.drivers.time; + const rtos = @import("../rtos.zig"); const get_time_since_boot = @import("../time.zig").get_time_since_boot; -const c = @import("esp-wifi-driver"); +const log = std.log.scoped(.esp_radio_timer); pub const CallbackFn = *const fn (?*anyopaque) callconv(.c) void; @@ -79,7 +80,7 @@ pub fn arm( tim.deadline = .init_relative(get_time_since_boot(), duration); tim.periodic = if (repeat) duration else null; } else { - std.log.warn("timer not found based on ets_timer", .{}); + log.warn("timer not found based on ets_timer", .{}); } } @@ -93,7 +94,7 @@ pub fn disarm(ets_timer: *c.ets_timer) void { if (find(ets_timer)) |tim| { tim.deadline = .no_deadline; } else { - std.log.warn("timer not found based on ets_timer", .{}); + log.warn("timer not found based on ets_timer", .{}); } } @@ -105,7 +106,7 @@ pub fn done(gpa: std.mem.Allocator, ets_timer: *c.ets_timer) void { timer_list.remove(&tim.node); gpa.destroy(tim); } else { - std.log.warn("timer not found based on ets_timer", .{}); + log.warn("timer not found based on ets_timer", .{}); } } From 21c06be085c00dd6680ab378e600beee5b5c495c Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Sat, 17 Jan 2026 09:06:02 +0200 Subject: [PATCH 35/43] Cleanup RTOS --- port/espressif/esp/src/hal/rtos.zig | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/port/espressif/esp/src/hal/rtos.zig b/port/espressif/esp/src/hal/rtos.zig index 2d0f6d698..06c5bfa1d 100644 --- a/port/espressif/esp/src/hal/rtos.zig +++ b/port/espressif/esp/src/hal/rtos.zig @@ -127,9 +127,6 @@ pub fn init() void { // TODO: deinit fn idle() linksection(".ram_text") callconv(.naked) void { - // TODO: cpu would hang after a while. Enabling interrupts before wfi - // appears to have solved the problem but the root cause is still a - // mistery. asm volatile ( \\csrsi mstatus, 8 # make sure interrupts are enabled \\1: @@ -138,31 +135,6 @@ fn idle() linksection(".ram_text") callconv(.naked) void { ); } -// fn idle() linksection(".ram_text") callconv(.c) void { -// microzig.cpu.interrupt.enable_interrupts(); -// -// while (true) { -// { -// const cs = microzig.interrupt.enter_critical_section(); -// defer cs.leave(); -// -// std.log.info("idle with interrupts enabled = {}", .{cs.enable_on_leave}); -// } -// -// microzig.cpu.wfi(); -// } -// -// // TODO: cpu would hang after a while. Enabling interrupts before wfi -// // appears to have solved the problem but the root cause is still a -// // mistery. -// asm volatile ( -// \\csrsi mstatus, 8 # make sure interrupts are enabled -// \\1: -// \\wfi -// \\j 1b -// ); -// } - pub fn get_current_task() *Task { return rtos_state.current_task; } From 0af62f7e5609a591ecbfea76786fef161aacbfb8 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Sat, 17 Jan 2026 10:46:09 +0200 Subject: [PATCH 36/43] Implement mutex priority inheritance --- port/espressif/esp/src/hal/radio/osi.zig | 80 +++++++++++++++--- port/espressif/esp/src/hal/rtos.zig | 103 +++++++++-------------- 2 files changed, 107 insertions(+), 76 deletions(-) diff --git a/port/espressif/esp/src/hal/radio/osi.zig b/port/espressif/esp/src/hal/radio/osi.zig index 786a40e37..8ad674170 100644 --- a/port/espressif/esp/src/hal/radio/osi.zig +++ b/port/espressif/esp/src/hal/radio/osi.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const assert = std.debug.assert; const builtin = @import("builtin"); const c = @import("esp-wifi-driver"); @@ -345,16 +346,75 @@ pub fn wifi_thread_semphr_get() callconv(.c) ?*anyopaque { return &rtos.get_current_task().semaphore; } +const RecursiveMutex = struct { + recursive: bool, + value: u32 = 0, + owning_task: ?*rtos.Task = null, + prev_priority: ?rtos.Priority = null, + wait_queue: rtos.PriorityWaitQueue = .{}, + + pub fn lock(mutex: *RecursiveMutex) void { + const cs = enter_critical_section(); + defer cs.leave(); + + const current_task = rtos.get_current_task(); + + if (mutex.owning_task == current_task) { + assert(mutex.recursive); + mutex.value += 1; + return; + } + + while (mutex.owning_task) |owning_task| { + // Owning task inherits the priority of the current task if it the + // current task has a bigger priority. + if (@intFromEnum(current_task.priority) > @intFromEnum(owning_task.priority)) { + mutex.prev_priority = owning_task.priority; + owning_task.priority = current_task.priority; + rtos.make_ready(owning_task); + } + + mutex.wait_queue.wait(current_task, null); + } + + assert(mutex.value == 0); + mutex.value += 1; + mutex.owning_task = current_task; + } + + pub fn unlock(mutex: *RecursiveMutex) bool { + const cs = enter_critical_section(); + defer cs.leave(); + + assert(mutex.owning_task == rtos.get_current_task()); + + assert(mutex.value > 0); + mutex.value -= 1; + if (mutex.value <= 0) { + const owning_task = mutex.owning_task.?; + + // Restore the priority of the task + if (mutex.prev_priority) |prev_priority| { + owning_task.priority = prev_priority; + mutex.prev_priority = null; + } + mutex.owning_task = null; + mutex.wait_queue.wake_one(); + return true; + } else { + return false; + } + } +}; + pub fn mutex_create() callconv(.c) ?*anyopaque { log.debug("mutex_create", .{}); - const mutex = gpa.create(rtos.RecursiveMutex) catch { + const mutex = gpa.create(RecursiveMutex) catch { log.warn("failed to allocate recursive mutex", .{}); return null; }; - mutex.* = .{ - .recursive = false, - }; + mutex.* = .{ .recursive = false }; log.debug(">>>> mutex create: {*}", .{mutex}); @@ -364,13 +424,11 @@ pub fn mutex_create() callconv(.c) ?*anyopaque { pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { log.debug("recursive_mutex_create", .{}); - const mutex = gpa.create(rtos.RecursiveMutex) catch { + const mutex = gpa.create(RecursiveMutex) catch { log.warn("failed to allocate recursive mutex", .{}); return null; }; - mutex.* = .{ - .recursive = true, - }; + mutex.* = .{ .recursive = true }; log.debug(">>>> mutex create: {*}", .{mutex}); @@ -380,14 +438,14 @@ pub fn recursive_mutex_create() callconv(.c) ?*anyopaque { pub fn mutex_delete(ptr: ?*anyopaque) callconv(.c) void { log.debug("mutex_delete {?}", .{ptr}); - const mutex: *rtos.RecursiveMutex = @ptrCast(@alignCast(ptr)); + const mutex: *RecursiveMutex = @ptrCast(@alignCast(ptr)); gpa.destroy(mutex); } pub fn mutex_lock(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("mutex lock {?}", .{ptr}); - const mutex: *rtos.RecursiveMutex = @ptrCast(@alignCast(ptr)); + const mutex: *RecursiveMutex = @ptrCast(@alignCast(ptr)); mutex.lock(); return 1; @@ -396,7 +454,7 @@ pub fn mutex_lock(ptr: ?*anyopaque) callconv(.c) i32 { pub fn mutex_unlock(ptr: ?*anyopaque) callconv(.c) i32 { log.debug("mutex unlock {?}", .{ptr}); - const mutex: *rtos.RecursiveMutex = @ptrCast(@alignCast(ptr)); + const mutex: *RecursiveMutex = @ptrCast(@alignCast(ptr)); return @intFromBool(mutex.unlock()); } diff --git a/port/espressif/esp/src/hal/rtos.zig b/port/espressif/esp/src/hal/rtos.zig index 06c5bfa1d..a6249c817 100644 --- a/port/espressif/esp/src/hal/rtos.zig +++ b/port/espressif/esp/src/hal/rtos.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; @@ -27,7 +28,6 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; // TODO: implement task garbage collection and recycling // TODO: implement std.Io // TODO: use @stackUpperBound when implemented -// TODO: implement priority inheritance for sync primitives // TODO: better handling if timeout is in the past or very short // TODO: support SMP for other esp32 chips with multicore (far future) @@ -758,45 +758,62 @@ pub const PriorityWaitQueue = struct { }; pub const Mutex = struct { - state: State = .unlocked, + locked: ?*Task = null, + prev_priority: ?Priority = null, wait_queue: PriorityWaitQueue = .{}, - pub const State = enum(u32) { - locked, - unlocked, - }; - pub fn lock(mutex: *Mutex) void { - lock_with_timeout(mutex, null) catch unreachable; + mutex.lock_with_timeout(null) catch unreachable; } pub fn lock_with_timeout(mutex: *Mutex, maybe_timeout: ?time.Duration) TimeoutError!void { const cs = enter_critical_section(); defer cs.leave(); + const current_task = get_current_task(); + const maybe_timeout_ticks: ?TimerTicks = if (maybe_timeout) |timeout| .after(timeout) else null; - while (mutex.state != .unlocked) { + while (mutex.locked) |owning_task| { if (maybe_timeout_ticks) |timeout_ticks| if (timeout_ticks.is_reached()) return error.Timeout; - mutex.wait_queue.wait(rtos_state.current_task, maybe_timeout_ticks); + // Owning task inherits the priority of the current task if it the + // current task has a bigger priority. + if (@intFromEnum(current_task.priority) > @intFromEnum(owning_task.priority)) { + if (mutex.prev_priority == null) + mutex.prev_priority = owning_task.priority; + owning_task.priority = current_task.priority; + make_ready(owning_task); + } + + mutex.wait_queue.wait(current_task, maybe_timeout_ticks); } - mutex.state = .locked; + mutex.locked = current_task; } pub fn unlock(mutex: *Mutex) void { const cs = enter_critical_section(); defer cs.leave(); - assert(mutex.state == .locked); - mutex.state = .unlocked; + mutex.unlock_impl(); + } + + fn unlock_impl(mutex: *Mutex) void { + const owning_task = mutex.locked.?; + + // Restore the priority of the task + if (mutex.prev_priority) |prev_priority| { + owning_task.priority = prev_priority; + mutex.prev_priority = null; + } + mutex.locked = null; mutex.wait_queue.wake_one(); } }; @@ -808,9 +825,7 @@ pub const Condition = struct { { const cs = enter_critical_section(); defer cs.leave(); - - mutex.unlock(); - + mutex.unlock_impl(); cond.wait_queue.wait(get_current_task(), null); } @@ -830,55 +845,6 @@ pub const Condition = struct { } }; -pub const RecursiveMutex = struct { - recursive: bool, - value: u32 = 0, - owning_task: ?*Task = null, - wait_queue: PriorityWaitQueue = .{}, - - pub fn lock(mutex: *RecursiveMutex) void { - const cs = enter_critical_section(); - defer cs.leave(); - - const current_task = rtos_state.current_task; - - if (mutex.owning_task == current_task) { - assert(mutex.recursive); - mutex.value += 1; - return; - } - - // if (mutex.owning_task) |owning_task| { - // // if (current_task.priority.is_greater(owning_task.priority)) { - // // rtos.change_task_priority_from_cs(owning_task, current_task.priority, cs); - // // } - // - while (mutex.owning_task != null) { - mutex.wait_queue.wait(current_task, null); - } - // } - - assert(mutex.value == 0); - mutex.value += 1; - mutex.owning_task = current_task; - } - - pub fn unlock(mutex: *RecursiveMutex) bool { - const cs = enter_critical_section(); - defer cs.leave(); - - assert(mutex.value > 0); - mutex.value -= 1; - if (mutex.value <= 0) { - mutex.owning_task = null; - mutex.wait_queue.wake_one(); - return true; - } else { - return false; - } - } -}; - pub const Semaphore = struct { current_value: u32, max_value: u32, @@ -1130,3 +1096,10 @@ pub fn Queue(Elem: type) type { } }; } + +fn with_safety() bool { + return switch (builtin.mode) { + .Debug, .ReleaseSafe => true, + .ReleaseFast, .ReleaseSmall => false, + }; +} From fbb10d1a474616b48ddea1e31c6ff626d5c3f49b Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Sat, 17 Jan 2026 11:03:31 +0200 Subject: [PATCH 37/43] rtos: Scoped log and log all tasks state fn --- port/espressif/esp/src/hal/rtos.zig | 110 +++++++++++++++------------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/port/espressif/esp/src/hal/rtos.zig b/port/espressif/esp/src/hal/rtos.zig index a6249c817..b84580872 100644 --- a/port/espressif/esp/src/hal/rtos.zig +++ b/port/espressif/esp/src/hal/rtos.zig @@ -1,7 +1,8 @@ -const builtin = @import("builtin"); const std = @import("std"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const log = std.log.scoped(.rtos); +const builtin = @import("builtin"); const microzig = @import("microzig"); const CriticalSection = microzig.interrupt.CriticalSection; @@ -9,10 +10,12 @@ const enter_critical_section = microzig.interrupt.enter_critical_section; const TrapFrame = microzig.cpu.TrapFrame; const SYSTEM = microzig.chip.peripherals.SYSTEM; const time = microzig.drivers.time; +const rtos_options = microzig.options.hal.rtos; +pub const Priority = rtos_options.Priority; +const get_time_since_boot = @import("time.zig").get_time_since_boot; const system = @import("system.zig"); const systimer = @import("systimer.zig"); -const get_time_since_boot = @import("time.zig").get_time_since_boot; // How it works? // For simple task to task context switches, only necessary registers are @@ -34,9 +37,6 @@ const get_time_since_boot = @import("time.zig").get_time_since_boot; const STACK_ALIGN: std.mem.Alignment = .@"16"; const EXTRA_STACK_SIZE = @max(@sizeOf(TrapFrame), 32 * @sizeOf(usize)); -const rtos_options = microzig.options.hal.rtos; -pub const Priority = rtos_options.Priority; - pub const Options = struct { enable: bool = false, Priority: type = enum(u8) { @@ -52,10 +52,11 @@ pub const Options = struct { cpu_interrupt: system.CPU_Interrupt = .cpu_interrupt_0, yield_interrupt: microzig.cpu.Interrupt = .interrupt31, - paint_stack: ?u8 = null, + paint_stack_byte: ?u8 = null, }; var main_task: Task = .{ + .name = "main", .context = undefined, .priority = .lowest, .stack = &.{}, @@ -76,6 +77,7 @@ var rtos_state: RTOS_State = undefined; pub const RTOS_State = struct { ready_queue: ReadyPriorityQueue = .{}, timer_queue: std.DoublyLinkedList = .{}, + suspended_list: std.DoublyLinkedList = .{}, scheduled_for_deletion_list: std.DoublyLinkedList = .{}, /// The task in .running state. Safe to access outside of critical section @@ -99,7 +101,7 @@ pub fn init() void { rtos_state = .{ .current_task = &main_task, }; - if (rtos_options.paint_stack) |paint_byte| { + if (rtos_options.paint_stack_byte) |paint_byte| { @memset(&idle_stack, paint_byte); } make_ready(&idle_task); @@ -186,7 +188,7 @@ pub fn spawn( task_args.* = args; const stack: []u8 = raw_alloc[stack_start..stack_end]; - if (rtos_options.paint_stack) |paint_byte| { + if (rtos_options.paint_stack_byte) |paint_byte| { @memset(stack, paint_byte); } @@ -213,10 +215,13 @@ pub fn spawn( pub fn make_ready(task: *Task) void { switch (task.state) { .ready, .running, .scheduled_for_deletion => return, - .none, .suspended => {}, + .none => {}, .alarm_set => |_| { rtos_state.timer_queue.remove(&task.node); }, + .suspended => { + rtos_state.suspended_list.remove(&task.node); + }, } task.state = .ready; @@ -255,6 +260,7 @@ fn yield_inner(action: YieldAction) linksection(".ram_text") struct { *Task, *Ta schedule_wake_at(current_task, timeout); } else { current_task.state = .suspended; + rtos_state.suspended_list.append(¤t_task.node); } }, .delete => { @@ -345,40 +351,6 @@ pub fn is_a_higher_priority_task_ready() bool { false; } -pub const report_stack_usage = if (rtos_options.paint_stack) |paint_byte| struct { - fn report_stack_usage() void { - const cs = microzig.interrupt.enter_critical_section(); - defer cs.leave(); - - const list: []const ?*std.DoublyLinkedList.Node = &.{ - rtos_state.ready_queue.inner.first, - rtos_state.timer_queue.first, - rtos_state.scheduled_for_deletion_list.first, - }; - for (list) |first| { - var it: ?*std.DoublyLinkedList.Node = first; - while (it) |node| : (it = node.next) { - const task: *Task = @alignCast(@fieldParentPtr("node", node)); - if (task == &main_task) continue; - log_stack_task_usage(task); - } - } - } - - fn log_stack_task_usage(task: *Task) void { - for (task.stack, 0..) |byte, i| { - if (byte != paint_byte) { - std.log.debug("task with name {?s} uses {} bytes out of {} for stack", .{ - task.name, - task.stack.len - i, - task.stack.len, - }); - break; - } - } - } -}.report_stack_usage else @compileError("please enable the paint_stack option to use this function"); - pub const yield_interrupt_handler: microzig.cpu.InterruptHandler = .{ .naked = struct { pub fn handler_fn() linksection(".ram_vectors") callconv(.naked) void { @@ -597,6 +569,51 @@ fn sweep_timer_queue_for_timeouts() void { } } +pub fn log_tasks_info() void { + const cs = microzig.interrupt.enter_critical_section(); + defer cs.leave(); + + log_task_info(get_current_task()); + + const list: []const ?*std.DoublyLinkedList.Node = &.{ + rtos_state.ready_queue.inner.first, + rtos_state.timer_queue.first, + rtos_state.suspended_list.first, + rtos_state.scheduled_for_deletion_list.first, + }; + for (list) |first| { + var it: ?*std.DoublyLinkedList.Node = first; + while (it) |node| : (it = node.next) { + const task: *Task = @alignCast(@fieldParentPtr("node", node)); + log_task_info(task); + } + } +} + +fn log_task_info(task: *Task) void { + if (rtos_options.paint_stack_byte) |paint_byte| { + const stack_usage = for (task.stack, 0..) |byte, i| { + if (byte != paint_byte) { + break task.stack.len - i; + } + } else task.stack.len; + + log.debug("task {?s} with prio {} in state {t} uses {} bytes out of {} for stack", .{ + task.name, + @intFromEnum(task.priority), + task.state, + stack_usage, + task.stack.len, + }); + } else { + log.debug("task {?s} with prio {} in state {t}", .{ + task.name, + @intFromEnum(task.priority), + task.state, + }); + } +} + pub const Task = struct { name: ?[]const u8 = null, @@ -1096,10 +1113,3 @@ pub fn Queue(Elem: type) type { } }; } - -fn with_safety() bool { - return switch (builtin.mode) { - .Debug, .ReleaseSafe => true, - .ReleaseFast, .ReleaseSmall => false, - }; -} From bae30a108acbb7fc5f18349251a5d0f658d9abff Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Sat, 17 Jan 2026 11:39:23 +0200 Subject: [PATCH 38/43] Cleanup --- port/espressif/esp/src/cpus/esp_riscv.zig | 1 - port/espressif/esp/src/hal/rtos.zig | 10 ++++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index f7a7e9169..42ffe04ed 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -89,7 +89,6 @@ pub const interrupt = struct { fence(); } - const INTERRUPT_CORE0 = microzig.chip.peripherals.INTERRUPT_CORE0; pub fn is_enabled(int: Interrupt) bool { diff --git a/port/espressif/esp/src/hal/rtos.zig b/port/espressif/esp/src/hal/rtos.zig index b84580872..aa2ff79b0 100644 --- a/port/espressif/esp/src/hal/rtos.zig +++ b/port/espressif/esp/src/hal/rtos.zig @@ -27,8 +27,9 @@ const systimer = @import("systimer.zig"); // minimum stack size available at all times. // TODO: stack overflow detection -// TODO: question: should idle do more stuff (like task garbage collection)? -// TODO: implement task garbage collection and recycling +// TODO: task joining and deletion +// - the idea is that tasks must return before they can be freed +// TODO: direct task signaling // TODO: implement std.Io // TODO: use @stackUpperBound when implemented // TODO: better handling if timeout is in the past or very short @@ -129,8 +130,9 @@ pub fn init() void { // TODO: deinit fn idle() linksection(".ram_text") callconv(.naked) void { + // interrupts are initially disabled in new tasks asm volatile ( - \\csrsi mstatus, 8 # make sure interrupts are enabled + \\csrsi mstatus, 8 \\1: \\wfi \\j 1b @@ -160,7 +162,7 @@ pub fn spawn( const TypeErased = struct { fn call() callconv(.c) void { - // interrupts are initially disabled in newly created tasks + // interrupts are initially disabled in new tasks microzig.cpu.interrupt.enable_interrupts(); const context_ptr: *const Args = From ddc69b270a8dcf6e56dd1866e18ce82b10192066 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Sat, 17 Jan 2026 11:40:00 +0200 Subject: [PATCH 39/43] Fix zig fmt crashing --- port/espressif/esp/src/cpus/esp_riscv.zig | 95 +++++++++++------------ 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index 42ffe04ed..50bee6fc8 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -448,29 +448,27 @@ fn _vector_table() align(256) linksection(".ram_vectors") callconv(.naked) void }; // adapted from https://github.com/esp-rs/esp-hal/blob/main/esp-riscv-rt/src/lib.rs - asm volatile ( - \\ - ++ interrupt_jump_asm ++ interrupt_c_stubs_asm ++ - \\trap_common: - \\ sw t0, 1*4(sp) - \\ sw t1, 2*4(sp) - \\ sw t2, 3*4(sp) - \\ sw t3, 4*4(sp) - \\ sw t4, 5*4(sp) - \\ sw t5, 6*4(sp) - \\ sw t6, 7*4(sp) - \\ sw a0, 8*4(sp) - \\ sw a1, 9*4(sp) - \\ sw a2, 10*4(sp) - \\ sw a3, 11*4(sp) - \\ sw a4, 12*4(sp) - \\ sw a5, 13*4(sp) - \\ sw a6, 14*4(sp) - \\ sw a7, 15*4(sp) - \\ - \\ mv a0, sp # Pass a pointer to TrapFrame to the handler function - \\ mv a1, ra # Save address of handler function - \\ + asm volatile (interrupt_jump_asm ++ interrupt_c_stubs_asm ++ + \\trap_common: + \\ sw t0, 1*4(sp) + \\ sw t1, 2*4(sp) + \\ sw t2, 3*4(sp) + \\ sw t3, 4*4(sp) + \\ sw t4, 5*4(sp) + \\ sw t5, 6*4(sp) + \\ sw t6, 7*4(sp) + \\ sw a0, 8*4(sp) + \\ sw a1, 9*4(sp) + \\ sw a2, 10*4(sp) + \\ sw a3, 11*4(sp) + \\ sw a4, 12*4(sp) + \\ sw a5, 13*4(sp) + \\ sw a6, 14*4(sp) + \\ sw a7, 15*4(sp) + \\ + \\ mv a0, sp # Pass a pointer to TrapFrame to the handler function + \\ mv a1, ra # Save address of handler function + \\ ++ (if (interrupt_stack_options.enable) // switch to interrupt stack if not nested \\ csrr t0, mscratch @@ -488,32 +486,31 @@ fn _vector_table() align(256) linksection(".ram_vectors") callconv(.naked) void \\ jal ra, _handle_interrupt \\ ) ++ - \\ - \\ lw ra, 0*4(sp) - \\ lw t0, 1*4(sp) - \\ lw t1, 2*4(sp) - \\ lw t2, 3*4(sp) - \\ lw t3, 4*4(sp) - \\ lw t4, 5*4(sp) - \\ lw t5, 6*4(sp) - \\ lw t6, 7*4(sp) - \\ lw a0, 8*4(sp) - \\ lw a1, 9*4(sp) - \\ lw a2, 10*4(sp) - \\ lw a3, 11*4(sp) - \\ lw a4, 12*4(sp) - \\ lw a5, 13*4(sp) - \\ lw a6, 14*4(sp) - \\ lw a7, 15*4(sp) - \\ - \\ addi sp, sp, 16*4 # This removes the frame we allocated from the stack - \\ - \\ mret - - : - : [interrupt_stack_top] "i" (if (interrupt_stack_options.enable) - interrupt_stack[interrupt_stack.len..].ptr - else {}), + \\ + \\ lw ra, 0*4(sp) + \\ lw t0, 1*4(sp) + \\ lw t1, 2*4(sp) + \\ lw t2, 3*4(sp) + \\ lw t3, 4*4(sp) + \\ lw t4, 5*4(sp) + \\ lw t5, 6*4(sp) + \\ lw t6, 7*4(sp) + \\ lw a0, 8*4(sp) + \\ lw a1, 9*4(sp) + \\ lw a2, 10*4(sp) + \\ lw a3, 11*4(sp) + \\ lw a4, 12*4(sp) + \\ lw a5, 13*4(sp) + \\ lw a6, 14*4(sp) + \\ lw a7, 15*4(sp) + \\ + \\ addi sp, sp, 16*4 # This removes the frame we allocated from the stack + \\ + \\ mret + : + : [interrupt_stack_top] "i" (if (interrupt_stack_options.enable) + interrupt_stack[interrupt_stack.len..].ptr + else {}), ); } From 0e7fb8caea3bfd804222e9bcf184e341030fa9f6 Mon Sep 17 00:00:00 2001 From: tact1m4n3 Date: Tue, 20 Jan 2026 08:13:32 +0200 Subject: [PATCH 40/43] Fix wifi sta config --- port/espressif/esp/src/hal/radio/timer.zig | 4 +- port/espressif/esp/src/hal/radio/wifi.zig | 56 +++++++++++++++------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/port/espressif/esp/src/hal/radio/timer.zig b/port/espressif/esp/src/hal/radio/timer.zig index e04ee2b3f..479191360 100644 --- a/port/espressif/esp/src/hal/radio/timer.zig +++ b/port/espressif/esp/src/hal/radio/timer.zig @@ -80,7 +80,7 @@ pub fn arm( tim.deadline = .init_relative(get_time_since_boot(), duration); tim.periodic = if (repeat) duration else null; } else { - log.warn("timer not found based on ets_timer", .{}); + log.warn("arm: timer not found based on ets_timer", .{}); } } @@ -94,7 +94,7 @@ pub fn disarm(ets_timer: *c.ets_timer) void { if (find(ets_timer)) |tim| { tim.deadline = .no_deadline; } else { - log.warn("timer not found based on ets_timer", .{}); + log.warn("disarm: timer not found based on ets_timer", .{}); } } diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index 0ebe409eb..15729918b 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -383,11 +383,13 @@ fn apply_station_config(config: Config.Station) ApplyConfigError!void { .threshold = .{ .rssi = -99, .authmode = if (config.auth) |auth| @intFromEnum(auth.method) else c.WIFI_AUTH_OPEN, + .rssi_5g_adjustment = 0, }, .pmf_cfg = .{ .capable = true, .required = false, }, + .sae_pwe_h2e = 3, .failure_retry_cnt = config.failure_retry_cnt, }; @@ -1158,6 +1160,23 @@ pub const c_patched = struct { }; pub const wifi_sta_config_t = extern struct { + ssid: [32]u8 = std.mem.zeroes([32]u8), + password: [64]u8 = std.mem.zeroes([64]u8), + scan_method: c.wifi_scan_method_t = std.mem.zeroes(c.wifi_scan_method_t), + bssid_set: bool = std.mem.zeroes(bool), + bssid: [6]u8 = std.mem.zeroes([6]u8), + channel: u8 = std.mem.zeroes(u8), + listen_interval: u16 = std.mem.zeroes(u16), + sort_method: c.wifi_sort_method_t = std.mem.zeroes(c.wifi_sort_method_t), + threshold: c.wifi_scan_threshold_t = std.mem.zeroes(c.wifi_scan_threshold_t), + pmf_cfg: c.wifi_pmf_config_t = std.mem.zeroes(c.wifi_pmf_config_t), + packed1: Packed1 = std.mem.zeroes(Packed1), + sae_pwe_h2e: c.wifi_sae_pwe_method_t = std.mem.zeroes(c.wifi_sae_pwe_method_t), + sae_pk_mode: c.wifi_sae_pk_mode_t = std.mem.zeroes(c.wifi_sae_pk_mode_t), + failure_retry_cnt: u8 = std.mem.zeroes(u8), + packed2: Packed2 = std.mem.zeroes(Packed2), + sae_h2e_identifier: [32]u8 = std.mem.zeroes([32]u8), + // NOTE: maybe a little more imagination pub const Packed1 = packed struct { rm_enabled: bool, @@ -1180,23 +1199,6 @@ pub const c_patched = struct { he_trig_cqi_feedback_disable: u1, he_reserved: u22, }; - - ssid: [32]u8 = std.mem.zeroes([32]u8), - password: [64]u8 = std.mem.zeroes([64]u8), - scan_method: c.wifi_scan_method_t = std.mem.zeroes(c.wifi_scan_method_t), - bssid_set: bool = std.mem.zeroes(bool), - bssid: [6]u8 = std.mem.zeroes([6]u8), - channel: u8 = std.mem.zeroes(u8), - listen_interval: u16 = std.mem.zeroes(u16), - sort_method: c.wifi_sort_method_t = std.mem.zeroes(c.wifi_sort_method_t), - threshold: c.wifi_scan_threshold_t = std.mem.zeroes(c.wifi_scan_threshold_t), - pmf_cfg: c.wifi_pmf_config_t = std.mem.zeroes(c.wifi_pmf_config_t), - packed1: Packed1 = std.mem.zeroes(Packed1), - sae_pwe_h2e: c.wifi_sae_pwe_method_t = std.mem.zeroes(c.wifi_sae_pwe_method_t), - sae_pk_mode: c.wifi_sae_pk_mode_t = std.mem.zeroes(c.wifi_sae_pk_mode_t), - failure_retry_cnt: u8 = std.mem.zeroes(u8), - packed2: Packed2 = std.mem.zeroes(Packed2), - sae_h2e_identifier: [32]u8 = std.mem.zeroes([32]u8), }; pub const wifi_nan_config_t = extern struct { @@ -1213,4 +1215,24 @@ pub const c_patched = struct { }; extern fn esp_wifi_set_config(interface: c.wifi_interface_t, conf: ?*wifi_config_t) c.esp_err_t; + + pub const wifi_ap_record_t = extern struct { + bssid: [6]u8 = std.mem.zeroes([6]u8), + ssid: [33]u8 = std.mem.zeroes([33]u8), + primary: u8 = std.mem.zeroes(u8), + second: c.wifi_second_chan_t = std.mem.zeroes(c.wifi_second_chan_t), + rssi: i8 = std.mem.zeroes(i8), + authmode: c.wifi_auth_mode_t = std.mem.zeroes(c.wifi_auth_mode_t), + pairwise_cipher: c.wifi_cipher_type_t = std.mem.zeroes(c.wifi_cipher_type_t), + group_cipher: c.wifi_cipher_type_t = std.mem.zeroes(c.wifi_cipher_type_t), + ant: c.wifi_ant_t = std.mem.zeroes(c.wifi_ant_t), + // TODO + packed1: u32 = std.mem.zeroes(u32), + country: c.wifi_country_t = std.mem.zeroes(c.wifi_country_t), + // TODO + he_ap: u8 = std.mem.zeroes(u8), + bandwidth: c.wifi_bandwidth_t = std.mem.zeroes(c.wifi_bandwidth_t), + vht_ch_freq1: u8 = std.mem.zeroes(u8), + vht_ch_freq2: u8 = std.mem.zeroes(u8), + }; }; From 1ba653199033c3fb86576f41d1a992b0135fcff6 Mon Sep 17 00:00:00 2001 From: tact1m4n3 Date: Tue, 20 Jan 2026 19:46:59 +0200 Subject: [PATCH 41/43] Refactor and scanning support --- examples/espressif/esp/src/tcp_server.zig | 23 +- port/espressif/esp/src/hal/radio.zig | 2 +- port/espressif/esp/src/hal/radio/wifi.zig | 311 ++++++++++++++++------ 3 files changed, 249 insertions(+), 87 deletions(-) diff --git a/examples/espressif/esp/src/tcp_server.zig b/examples/espressif/esp/src/tcp_server.zig index 102abce94..dba09b963 100644 --- a/examples/espressif/esp/src/tcp_server.zig +++ b/examples/espressif/esp/src/tcp_server.zig @@ -47,8 +47,10 @@ pub const microzig_options: microzig.Options = .{ }, }; +// NOTE: Change these to match your setup. const SSID = "SSID"; -const AUTH: ?radio.wifi.Config.Auth = null; +const PASSWORD: []const u8 = ""; +const AUTH_METHOD: radio.wifi.AuthMethod = .none; const SERVER_PORT = 3333; var maybe_netif: ?*lwip.c.netif = null; @@ -83,11 +85,26 @@ pub fn main() !void { try radio.wifi.apply(.{ .sta = .{ .ssid = SSID, - .auth = AUTH, + .password = PASSWORD, + .auth_method = AUTH_METHOD, }, }); - try radio.wifi.start_blocking(); + + { + std.log.info("Scanning for access points...", .{}); + var scan_iter = try radio.wifi.scan(.{.ssid = "Internet"}); + defer scan_iter.deinit(); + while (try scan_iter.next()) |record| { + std.log.info("Found ap `{s}` RSSI: {} Channel: {}, Auth: {?t}", .{ + record.ssid, + record.rssi, + record.primary_channel, + record.auth_mode, + }); + } + } + try radio.wifi.connect_blocking(); try lwip.c_err(lwip.c.netifapi_netif_common(&netif, lwip.c.netif_set_link_up, null)); diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index c57531f2b..b9918035e 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -66,7 +66,7 @@ pub fn init(gpa: Allocator) Allocator.Error!void { .Debug => c.WIFI_LOG_VERBOSE, else => c.WIFI_LOG_NONE, }; - wifi.c_result(c.esp_wifi_internal_set_log_level(internal_wifi_log_level)) catch { + wifi.c_err(c.esp_wifi_internal_set_log_level(internal_wifi_log_level)) catch { log.warn("failed to set wifi internal log level", .{}); }; } diff --git a/port/espressif/esp/src/hal/radio/wifi.zig b/port/espressif/esp/src/hal/radio/wifi.zig index 15729918b..6aa4d79e6 100644 --- a/port/espressif/esp/src/hal/radio/wifi.zig +++ b/port/espressif/esp/src/hal/radio/wifi.zig @@ -4,6 +4,7 @@ const assert = std.debug.assert; pub const c = @import("esp-wifi-driver"); const microzig = @import("microzig"); const wifi_options = microzig.options.hal.radio.wifi; +const time = microzig.drivers.time; const radio = @import("../radio.zig"); const rtos = @import("../rtos.zig"); @@ -163,21 +164,21 @@ pub fn init(gpa: std.mem.Allocator, config: InitConfig) InitError!void { } // TODO: if coex enabled - if (false) try c_result(c.coex_init()); + if (false) try c_err(c.coex_init()); - try c_result(c.esp_wifi_init_internal(&init_config)); + try c_err(c.esp_wifi_init_internal(&init_config)); - try c_result(c.esp_wifi_set_mode(c.WIFI_MODE_NULL)); + try c_err(c.esp_wifi_set_mode(c.WIFI_MODE_NULL)); - try c_result(c.esp_supplicant_init()); + try c_err(c.esp_supplicant_init()); - try c_result(c.esp_wifi_set_tx_done_cb(tx_done_cb)); - try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_AP, recv_cb_ap)); - try c_result(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_STA, recv_cb_sta)); + try c_err(c.esp_wifi_set_tx_done_cb(tx_done_cb)); + try c_err(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_AP, recv_cb_ap)); + try c_err(c.esp_wifi_internal_reg_rxcb(c.ESP_IF_WIFI_STA, recv_cb_sta)); { const country_info = config.country_info.get_wifi_country_t(config.max_tx_power); - try c_result(c.esp_wifi_set_country(&country_info)); + try c_err(c.esp_wifi_set_country(&country_info)); } try set_power_save_mode(config.power_save_mode); @@ -191,7 +192,7 @@ pub fn deinit() void { radio.deinit(); } -pub const ApplyConfigError = InternalError || error{ +pub const ConfigError = InternalError || error{ InvalidConfig, }; @@ -208,6 +209,9 @@ pub const Config = union(enum) { /// The SSID of the access point. ssid: []const u8, + /// The password of the access point. + password: []const u8 = "", + /// Whether the SSID is hidden or visible. ssid_hidden: bool = false, @@ -219,7 +223,7 @@ pub const Config = union(enum) { secondary_channel: ?u8 = null, /// Authentication config to be used by the access point. - auth: ?Auth = null, + auth_method: AuthMethod = .none, /// The maximum number of connections allowed on the access point. max_connections: u8, @@ -229,11 +233,14 @@ pub const Config = union(enum) { /// The SSID of the Wi-Fi network. ssid: []const u8, + /// The password of the Wi-Fi network. + password: []const u8 = "", + /// The BSSID (MAC address) of the client. bssid: ?[6]u8 = null, /// Authentication config for the Wi-Fi connection. - auth: ?Auth = null, + auth_method: AuthMethod = .none, /// The Wi-Fi channel to connect to. channel: u8 = 0, @@ -249,40 +256,9 @@ pub const Config = union(enum) { all_channel = c.WIFI_ALL_CHANNEL_SCAN, }; }; - - pub const Auth = struct { - password: []const u8, - method: Method, - - pub const Method = enum(u32) { - /// Wired Equivalent Privacy (WEP) authentication. - wep = c.WIFI_AUTH_WEP, - - /// Wi-Fi Protected Access (WPA) authentication. - wpa = c.WIFI_AUTH_WPA_PSK, - - /// Wi-Fi Protected Access 2 (WPA2) Personal authentication (default). - wpa2_personal = c.WIFI_AUTH_WPA2_PSK, - - /// WPA/WPA2 Personal authentication (supports both). - wpa_wpa2_personal = c.WIFI_AUTH_WPA_WPA2_PSK, - - /// WPA2 Enterprise authentication. - wpa2_enterprise = c.WIFI_AUTH_WPA2_ENTERPRISE, - - /// WPA3 Personal authentication. - wpa3_personal = c.WIFI_AUTH_WPA3_PSK, - - /// WPA2/WPA3 Personal authentication (supports both). - wpa2_wpa3_personal = c.WIFI_AUTH_WPA2_WPA3_PSK, - - /// WLAN Authentication and Privacy Infrastructure (WAPI). - wapi_personal = c.WIFI_AUTH_WAPI_PSK, - }; - }; }; -pub fn apply(config: Config) ApplyConfigError!void { +pub fn apply(config: Config) ConfigError!void { switch (config) { .ap => |ap_config| { try set_mode(.ap); @@ -316,29 +292,26 @@ pub const WifiMode = enum(u32) { pub fn get_mode() InternalError!WifiMode { var mode: c.wifi_mode_t = undefined; - try c_result(c.esp_wifi_get_mode(&mode)); + try c_err(c.esp_wifi_get_mode(&mode)); return @enumFromInt(mode); } -fn set_mode(mode: WifiMode) InternalError!void { - try c_result(c.esp_wifi_set_mode(@intFromEnum(mode))); +pub fn set_mode(mode: WifiMode) InternalError!void { + try c_err(c.esp_wifi_set_mode(@intFromEnum(mode))); } -fn apply_access_point_config(config: Config.AccessPoint) ApplyConfigError!void { +fn apply_access_point_config(config: Config.AccessPoint) ConfigError!void { if (config.ssid.len > 32) { return error.InvalidConfig; } - - if (config.auth) |auth| { - if (auth.password.len > 64) { - return error.InvalidConfig; - } + if (config.password.len > 64) { + return error.InvalidConfig; } var ap_cfg: c_patched.wifi_ap_config_t = .{ .ssid_len = @intCast(config.ssid.len), .channel = config.channel, - .authmode = if (config.auth) |auth| @intFromEnum(auth.method) else c.WIFI_AUTH_OPEN, + .authmode = @intFromEnum(config.auth_method), .ssid_hidden = @intFromBool(config.ssid_hidden), .max_connection = config.max_connections, .beacon_interval = 100, @@ -354,23 +327,18 @@ fn apply_access_point_config(config: Config.AccessPoint) ApplyConfigError!void { }; @memcpy(ap_cfg.ssid[0..config.ssid.len], config.ssid); - if (config.auth) |auth| { - @memcpy(ap_cfg.password[0..auth.password.len], auth.password); - } + @memcpy(ap_cfg.password[0..config.password.len], config.password); var tmp: c_patched.wifi_config_t = .{ .ap = ap_cfg }; - try c_result(c_patched.esp_wifi_set_config(c.WIFI_IF_AP, &tmp)); + try c_err(c_patched.esp_wifi_set_config(c.WIFI_IF_AP, &tmp)); } -fn apply_station_config(config: Config.Station) ApplyConfigError!void { +fn apply_station_config(config: Config.Station) ConfigError!void { if (config.ssid.len > 32) { return error.InvalidConfig; } - - if (config.auth) |auth| { - if (auth.password.len > 64) { - return error.InvalidConfig; - } + if (config.password.len > 64) { + return error.InvalidConfig; } var sta_cfg: c_patched.wifi_sta_config_t = .{ @@ -382,7 +350,7 @@ fn apply_station_config(config: Config.Station) ApplyConfigError!void { .sort_method = c.WIFI_CONNECT_AP_BY_SIGNAL, .threshold = .{ .rssi = -99, - .authmode = if (config.auth) |auth| @intFromEnum(auth.method) else c.WIFI_AUTH_OPEN, + .authmode = @intFromEnum(config.auth_method), .rssi_5g_adjustment = 0, }, .pmf_cfg = .{ @@ -394,12 +362,10 @@ fn apply_station_config(config: Config.Station) ApplyConfigError!void { }; @memcpy(sta_cfg.ssid[0..config.ssid.len], config.ssid); - if (config.auth) |auth| { - @memcpy(sta_cfg.password[0..auth.password.len], auth.password); - } + @memcpy(sta_cfg.password[0..config.password.len], config.password); var tmp: c_patched.wifi_config_t = .{ .sta = sta_cfg }; - try c_result(c_patched.esp_wifi_set_config(c.WIFI_IF_STA, &tmp)); + try c_err(c_patched.esp_wifi_set_config(c.WIFI_IF_STA, &tmp)); } pub const PowerSaveMode = enum(u32) { @@ -409,7 +375,7 @@ pub const PowerSaveMode = enum(u32) { }; pub fn set_power_save_mode(mode: PowerSaveMode) InternalError!void { - try c_result(c.esp_wifi_set_ps(@intFromEnum(mode))); + try c_err(c.esp_wifi_set_ps(@intFromEnum(mode))); } pub const Protocol = enum(u8) { @@ -440,10 +406,10 @@ pub fn set_protocol(protocols: []const Config.AccessPoint.Protocol) InternalErro const mode = try get_mode(); if (mode.is_sta()) { - try c_result(c.esp_wifi_set_protocol(c.WIFI_IF_STA, combined)); + try c_err(c.esp_wifi_set_protocol(c.WIFI_IF_STA, combined)); } if (mode.is_ap()) { - try c_result(c.esp_wifi_set_protocol(c.WIFI_IF_AP, combined)); + try c_err(c.esp_wifi_set_protocol(c.WIFI_IF_AP, combined)); } } @@ -457,27 +423,27 @@ pub fn set_protocol(protocols: []const Config.AccessPoint.Protocol) InternalErro /// STA during inactive time, the softAP will force deauth the STA. Default is /// 300s. Must be at least 10s. pub fn set_inactive_time(interface: Interface, inactive_time: u32) InternalError!void { - try c_result(c.esp_wifi_set_inactive_time( + try c_err(c.esp_wifi_set_inactive_time( @intFromEnum(interface), inactive_time, )); } pub fn start() InternalError!void { - try c_result(c.esp_wifi_start()); + try c_err(c.esp_wifi_start()); } pub fn stop() InternalError!void { - try c_result(c.esp_wifi_stop()); + try c_err(c.esp_wifi_stop()); } /// Non-blocking. pub fn connect() InternalError!void { - try c_result(c.esp_wifi_connect_internal()); + try c_err(c.esp_wifi_connect_internal()); } pub fn disconnect() InternalError!void { - try c_result(c.esp_wifi_disconnect_internal()); + try c_err(c.esp_wifi_disconnect_internal()); } pub fn start_blocking() InternalError!void { @@ -833,7 +799,7 @@ pub const Interface = enum(u32) { }; pub fn send_packet(iface: Interface, data: []const u8) (error{TooManyPacketsInFlight} || InternalError)!void { - try c_result(c.esp_wifi_internal_tx(@intFromEnum(iface), @ptrCast(@constCast(data.ptr)), @intCast(data.len))); + try c_err(c.esp_wifi_internal_tx(@intFromEnum(iface), @ptrCast(@constCast(data.ptr)), @intCast(data.len))); } fn tx_done_cb( @@ -870,6 +836,160 @@ fn recv_cb_sta(buf: ?*anyopaque, len: u16, eb: ?*anyopaque) callconv(.c) c.esp_e return c.ESP_OK; } +pub const ScanConfig = struct { + ssid: ?[:0]const u8 = null, + bssid: ?[:0]const u8 = null, + channel: ?u8 = null, + show_hidden: bool = true, + type: Type = .{ .active = .{ + .min = .from_ms(100), + .max = .from_ms(200), + } }, + + pub const Type = union(enum) { + /// Active scan with min and max scan time per channel. This is the default + /// and recommended if you are unsure. + /// + /// # Procedure + /// 1. Send probe request on each channel. + /// 2. Wait for probe response. Wait at least `min` time, but if no response is received, wait + /// up to `max` time. + /// 3. Switch channel. + /// 4. Repeat from 1. + active: struct { + /// Minimum scan time per channel. + min: time.Duration, + /// Maximum scan time per channel. + max: time.Duration, + }, + /// Passive scan. + /// + /// # Procedure + /// 1. Wait for beacon for given duration. + /// 2. Switch channel. + /// 3. Repeat from 1. + passive: time.Duration, + }; +}; + +/// Blocks until the scan is finished. The returned iterator must be deinited +/// or consumed before another call to scan. +pub fn scan(config: ScanConfig) ConfigError!ScanResultsIterator { + const scan_time: c.wifi_scan_time_t = switch (config.type) { + .active => |active_time| .{ + .active = .{ + .min = @intCast(active_time.min.to_us() / 1000), + .max = @intCast(active_time.max.to_us() / 1000), + }, + }, + .passive => |passive_time| blk: { + if (!passive_time.less_than(.from_ms(1_500))) + return error.InvalidConfig; + + break :blk .{ .passive = @intCast(passive_time.to_us() / 1000) }; + }, + }; + + var scan_config: c.wifi_scan_config_t = .{ + .ssid = if (config.ssid) |ssid| @constCast(ssid.ptr) else null, + .bssid = if (config.bssid) |bssid| @constCast(bssid.ptr) else null, + .channel = config.channel orelse 0, + .show_hidden = config.show_hidden, + .scan_type = switch (config.type) { + .active => c.WIFI_SCAN_TYPE_ACTIVE, + .passive => c.WIFI_SCAN_TYPE_PASSIVE, + }, + .scan_time = scan_time, + .home_chan_dwell_time = 0, + .channel_bitmap = .{ + .ghz_2_channels = 0, + .ghz_5_channels = 0, + }, + .coex_background_scan = false, + }; + try c_err(c.esp_wifi_scan_start(&scan_config, true)); + return try .init(); +} + +/// SAFETY: There can be only one instance of `ScanIterator` at any given time. +pub const ScanResultsIterator = struct { + remaining: u16, + ap_record: c_patched.wifi_ap_record_t = undefined, + + /// Blocks the current thread until the scan finishes and returns an + /// iterator for the scan results. + pub fn init() InternalError!ScanResultsIterator { + var ap_count: u16 = undefined; + try c_err(c.esp_wifi_scan_get_ap_num(&ap_count)); + return .{ .remaining = ap_count }; + } + + pub fn deinit(_: ScanResultsIterator) void { + // free any ap records that were not returned + c_err(c.esp_wifi_clear_ap_list()) catch |err| { + std.log.warn("failed to clear ap list: {t}", .{err}); + }; + } + + /// SAFETY: AP_Record strings last until the next call to next. + pub fn next(self: *ScanResultsIterator) InternalError!?AP_Record { + if (self.remaining == 0) { + return null; + } + self.remaining -= 1; + + try c_err(c_patched.esp_wifi_scan_get_ap_record(&self.ap_record)); + + const ssid = std.mem.sliceTo(&self.ap_record.ssid, 0); + return .{ + .ssid = ssid, + .primary_channel = self.ap_record.primary, + .secondary_channel = self.ap_record.second, + .rssi = self.ap_record.rssi, + .auth_mode = std.enums.fromInt(AuthMethod, self.ap_record.authmode), + }; + } + + /// Strings (eg: ssid) have the same lifetime as the iterator object. + pub const AP_Record = struct { + ssid: []const u8, + primary_channel: u8, + secondary_channel: u32, + rssi: i8, + // TODO: this doesn't contain possibilities + auth_mode: ?AuthMethod, + // TODO: add the rest + }; +}; + +pub const AuthMethod = enum(u32) { + none = c.WIFI_AUTH_OPEN, + + /// Wired Equivalent Privacy (WEP) authentication. + wep = c.WIFI_AUTH_WEP, + + /// Wi-Fi Protected Access (WPA) authentication. + wpa = c.WIFI_AUTH_WPA_PSK, + + /// Wi-Fi Protected Access 2 (WPA2) Personal authentication (default). + wpa2_personal = c.WIFI_AUTH_WPA2_PSK, + + /// WPA/WPA2 Personal authentication (supports both). + wpa_wpa2_personal = c.WIFI_AUTH_WPA_WPA2_PSK, + + /// WPA2 Enterprise authentication. + wpa2_enterprise = c.WIFI_AUTH_WPA2_ENTERPRISE, + + /// WPA3 Personal authentication. + wpa3_personal = c.WIFI_AUTH_WPA3_PSK, + + /// WPA2/WPA3 Personal authentication (supports both). + wpa2_wpa3_personal = c.WIFI_AUTH_WPA2_WPA3_PSK, + + /// WLAN Authentication and Privacy Infrastructure (WAPI). + wapi_personal = c.WIFI_AUTH_WAPI_PSK, +}; + fn export_symbols() void { @export(&g_wifi_feature_caps, .{ .name = "g_wifi_feature_caps" }); @export(&g_wifi_osi_funcs, .{ .name = "g_wifi_osi_funcs" }); @@ -1052,7 +1172,7 @@ pub const InternalError = error{ WifiOther, }; -pub fn c_result(raw_err_code: i32) InternalError!void { +pub fn c_err(raw_err_code: i32) InternalError!void { const InternalWifiErrorCode = enum(i32) { /// Out of memory esp_err_no_mem = 0x101, @@ -1214,7 +1334,7 @@ pub const c_patched = struct { nan: wifi_nan_config_t, }; - extern fn esp_wifi_set_config(interface: c.wifi_interface_t, conf: ?*wifi_config_t) c.esp_err_t; + pub extern fn esp_wifi_set_config(interface: c.wifi_interface_t, conf: ?*wifi_config_t) c.esp_err_t; pub const wifi_ap_record_t = extern struct { bssid: [6]u8 = std.mem.zeroes([6]u8), @@ -1226,13 +1346,38 @@ pub const c_patched = struct { pairwise_cipher: c.wifi_cipher_type_t = std.mem.zeroes(c.wifi_cipher_type_t), group_cipher: c.wifi_cipher_type_t = std.mem.zeroes(c.wifi_cipher_type_t), ant: c.wifi_ant_t = std.mem.zeroes(c.wifi_ant_t), - // TODO - packed1: u32 = std.mem.zeroes(u32), + packed1: Packed1 = std.mem.zeroes(Packed1), country: c.wifi_country_t = std.mem.zeroes(c.wifi_country_t), - // TODO - he_ap: u8 = std.mem.zeroes(u8), + he_ap: wifi_he_ap_info_t = std.mem.zeroes(wifi_he_ap_info_t), bandwidth: c.wifi_bandwidth_t = std.mem.zeroes(c.wifi_bandwidth_t), vht_ch_freq1: u8 = std.mem.zeroes(u8), vht_ch_freq2: u8 = std.mem.zeroes(u8), + + pub const Packed1 = packed struct(u32) { + phy_11b: u1, + phy_11g: u1, + phy_11n: u1, + phy_lr: u1, + phy_11a: u1, + phy_11ac: u1, + phy_11ax: u1, + wps: u1, + ftm_responder: u1, + ftm_initiator: u1, + reserved: u22, + }; + }; + + pub const wifi_he_ap_info_t = extern struct { + packed1: Packed1, + bssid_index: u8, + + pub const Packed1 = packed struct(u8) { + bss_color: u6, + partial_bss_color: u1, + bss_color_disabled: u1, + }; }; + + pub extern fn esp_wifi_scan_get_ap_record(ap_record: ?*wifi_ap_record_t) c.esp_err_t; }; From ea369378b5b7ecb39ca6b374fdcaf46748004df5 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Tue, 20 Jan 2026 20:15:33 +0200 Subject: [PATCH 42/43] Commentary --- examples/espressif/esp/src/rtos.zig | 1 + port/espressif/esp/src/cpus/esp_riscv.zig | 1 - port/espressif/esp/src/hal/radio.zig | 4 ++++ port/espressif/esp/src/hal/rtos.zig | 27 +++++++++++++---------- 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/examples/espressif/esp/src/rtos.zig b/examples/espressif/esp/src/rtos.zig index d61b0a924..872673e58 100644 --- a/examples/espressif/esp/src/rtos.zig +++ b/examples/espressif/esp/src/rtos.zig @@ -19,6 +19,7 @@ pub const microzig_options: microzig.Options = .{ .cpu = .{ .interrupt_stack = .{ .enable = true, + .size = 4096, }, }, .hal = .{ diff --git a/port/espressif/esp/src/cpus/esp_riscv.zig b/port/espressif/esp/src/cpus/esp_riscv.zig index 50bee6fc8..dd578576b 100644 --- a/port/espressif/esp/src/cpus/esp_riscv.zig +++ b/port/espressif/esp/src/cpus/esp_riscv.zig @@ -447,7 +447,6 @@ fn _vector_table() align(256) linksection(".ram_vectors") callconv(.naked) void break :blk .{ interrupt_jump_asm, interrupt_c_stubs_asm }; }; - // adapted from https://github.com/esp-rs/esp-hal/blob/main/esp-riscv-rt/src/lib.rs asm volatile (interrupt_jump_asm ++ interrupt_c_stubs_asm ++ \\trap_common: \\ sw t0, 1*4(sp) diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index b9918035e..5b1c55b9b 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -1,3 +1,7 @@ +/// ESP Wi-Fi drivers integration. +/// +/// Based on https://github.com/esp-rs/esp-hal/tree/main/esp-radio. + const builtin = @import("builtin"); const std = @import("std"); const assert = std.debug.assert; diff --git a/port/espressif/esp/src/hal/rtos.zig b/port/espressif/esp/src/hal/rtos.zig index aa2ff79b0..2ce93e1fb 100644 --- a/port/espressif/esp/src/hal/rtos.zig +++ b/port/espressif/esp/src/hal/rtos.zig @@ -18,13 +18,15 @@ const system = @import("system.zig"); const systimer = @import("systimer.zig"); // How it works? -// For simple task to task context switches, only necessary registers are -// saved. But if a higher priority task becomes available during the handling -// of an interrupt, a task switch is forced by saving the entire state of the -// task on the stack. What is interresting is that the two context switches are -// compatible. Voluntary yield can resume a task that was interrupted by force -// and vice versa. Because of the forced yield, tasks are required to have a -// minimum stack size available at all times. +// +// For task to task context switches, only required registers are +// saved through the use of inline assembly clobbers. If a higher priority task +// becomes ready during the handling of an interrupt, a task switch is forced +// by saving the entire state of the task on the stack. What is interesting is +// that the two context switches are compatible. Voluntary yield can resume a +// task that was interrupted by force and vice versa. Because of the forced +// yield, tasks are required to have a minimum stack size available at all +// times. // TODO: stack overflow detection // TODO: task joining and deletion @@ -283,14 +285,13 @@ fn yield_inner(action: YieldAction) linksection(".ram_text") struct { *Task, *Ta pub fn sleep(duration: time.Duration) void { const timeout: TimerTicks = .after(duration); - while (!timeout.is_reached()) { - yield(.{ .wait = .{ - .timeout = timeout, - } }); - } + while (!timeout.is_reached()) + yield(.{ .wait = .{ .timeout = timeout } }); } inline fn context_switch(prev_context: *Context, next_context: *Context) void { + // Clobber all registers (except sp) to restore them after the context + // switch. asm volatile ( \\la a2, 1f \\sw a2, 0(a0) # save return pc @@ -796,6 +797,8 @@ pub const Mutex = struct { else null; + assert(mutex.locked != current_task); + while (mutex.locked) |owning_task| { if (maybe_timeout_ticks) |timeout_ticks| if (timeout_ticks.is_reached()) From 599e2c484bc5d7cb4a49657a0ed5096f862dab05 Mon Sep 17 00:00:00 2001 From: Tudor Andrei Dicu Date: Tue, 20 Jan 2026 20:23:54 +0200 Subject: [PATCH 43/43] oopsie --- examples/espressif/esp/src/tcp_server.zig | 2 +- port/espressif/esp/src/hal/radio.zig | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/espressif/esp/src/tcp_server.zig b/examples/espressif/esp/src/tcp_server.zig index dba09b963..8f2729794 100644 --- a/examples/espressif/esp/src/tcp_server.zig +++ b/examples/espressif/esp/src/tcp_server.zig @@ -93,7 +93,7 @@ pub fn main() !void { { std.log.info("Scanning for access points...", .{}); - var scan_iter = try radio.wifi.scan(.{.ssid = "Internet"}); + var scan_iter = try radio.wifi.scan(.{}); defer scan_iter.deinit(); while (try scan_iter.next()) |record| { std.log.info("Found ap `{s}` RSSI: {} Channel: {}, Auth: {?t}", .{ diff --git a/port/espressif/esp/src/hal/radio.zig b/port/espressif/esp/src/hal/radio.zig index 5b1c55b9b..6540fbb9f 100644 --- a/port/espressif/esp/src/hal/radio.zig +++ b/port/espressif/esp/src/hal/radio.zig @@ -1,7 +1,6 @@ /// ESP Wi-Fi drivers integration. /// /// Based on https://github.com/esp-rs/esp-hal/tree/main/esp-radio. - const builtin = @import("builtin"); const std = @import("std"); const assert = std.debug.assert;