diff options
-rw-r--r-- | CMakeLists.txt | 4 | ||||
-rwxr-xr-x | build-aux/lint-bin | 40 | ||||
-rwxr-xr-x | build-aux/stack.c.gen | 248 | ||||
-rw-r--r-- | cmd/sbc_harness/CMakeLists.txt | 1 | ||||
-rw-r--r-- | libcr_ipc/CMakeLists.txt | 4 | ||||
-rw-r--r-- | libcr_ipc/chan.c | 40 | ||||
-rw-r--r-- | libcr_ipc/include/libcr_ipc/chan.h | 36 | ||||
-rw-r--r-- | libcr_ipc/include/libcr_ipc/select.h | 98 | ||||
-rw-r--r-- | libcr_ipc/select.c | 102 |
9 files changed, 397 insertions, 176 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index f13226b..386b13c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.30) if (NOT PICO_PLATFORM) - message(FATAL_ERROR "PICO_PLATFORM must be specified; use the GNUmakefile to set this") + message(FATAL_ERROR "PICO_PLATFORM must be specified; use the GNUmakefile to set this") endif() set(PICO_SDK_PATH "${CMAKE_SOURCE_DIR}/3rd-party/pico-sdk") @@ -32,7 +32,7 @@ if ("-DNDEBUG" IN_LIST _build_type_flags) endif() function(_suppress_tinyusb_warnings) - __suppress_tinyusb_warnings() + __suppress_tinyusb_warnings() set_source_files_properties( ${PICO_TINYUSB_PATH}/src/device/usbd.c PROPERTIES diff --git a/build-aux/lint-bin b/build-aux/lint-bin index ffc2a12..0b955de 100755 --- a/build-aux/lint-bin +++ b/build-aux/lint-bin @@ -18,6 +18,15 @@ shopt -s extglob # Textual info: # - ${elf%.elf}.dis : `objdump --section-headers ${elf}; objdump --disassemble ${elf}; picotool coprodis --quiet ${elf}` # - ${elf}.map : `ld --print-map` info +# - ${elf%.elf}_stack.c : `stack.c.gen` + +RED=$(tput setaf 1) +RESET=$(tput sgr0) + +err() { + printf "${RED}%s${RESET}: %s\n" "$1" "$2" >&2 + #r=1 +} # Input is `ld --print-map` format. # @@ -27,6 +36,14 @@ objdump_globals() { sed -E -n '/^ \.t?(data|bss)\./{ / 0x/{ p; D; }; N; s/\n/ /; p; }' <"$1" } +readelf_funcs() { + local in_elffile + in_elffile=$1 + + readelf --syms --wide -- "$in_elffile" | + awk '$4 == "FUNC" { print $8 }' +} + lint_globals() { local in_mapfile in_mapfile=$1 @@ -72,14 +89,37 @@ lint_globals() { } | column -t } +lint_stack() { + local in_elffile + in_elffile=$1 + + IFS='' + while read -r line; do + func=${line#$'\t'} + if [[ $line == $'\t'* ]]; then + err "$in_elffile" "function in binary but not _stack.c: ${func}" + else + err "$in_elffile" "function in _stack.c but not binary: ${func}" + fi + done < <( + comm -3 \ + <(sed -En 's/^included: (.*:)?//p' "${in_elffile%.elf}_stack.c" | sort -u) \ + <(readelf_funcs "$in_elffile" | sed 's/\.part\.[0-9]*$//' | sort -u)) +} + main() { + r=0 + local elf for elf in "$@"; do { echo 'Global variables:' lint_globals "${elf}.map" | sed 's/^/ /' } > "${elf%.elf}.lint.globals" + lint_stack "$elf" &> "${elf%.elf}.lint.stack" done + + return $r } main "$@" diff --git a/build-aux/stack.c.gen b/build-aux/stack.c.gen index 2acce60..edc7bae 100755 --- a/build-aux/stack.c.gen +++ b/build-aux/stack.c.gen @@ -142,6 +142,8 @@ class AnalyzeResult(typing.NamedTuple): missing: set[str] dynamic: set[str] + included_funcs: set[str] + class Application(typing.Protocol): def extra_nodes(self) -> typing.Collection[Node]: ... @@ -162,7 +164,7 @@ def analyze( + r"(?P<location>[^\n]+:[0-9]+:[0-9]+)\n" + r"(?P<nstatic>[0-9]+) bytes \((?P<usage_kind>static|dynamic|dynamic,bounded)\)\n" + r"(?P<ndynamic>[0-9]+) dynamic objects" - + r"(?:\n.*)?", + + r"(?:\n.*)*", flags=re.MULTILINE, ) @@ -246,6 +248,7 @@ def analyze( missing: set[str] = set() dynamic: set[str] = set() + included_funcs: set[str] = set() dbg = False @@ -288,6 +291,7 @@ def analyze( print(f"//dbg: {funcname}\t{node.nstatic}") if node.usage_kind == "dynamic" or node.ndynamic > 0: dynamic.add(app.location_xform(funcname)) + included_funcs.add(funcname) return node.nstatic + max( [ 0, @@ -312,7 +316,9 @@ def analyze( nsum += cnt * n groups[grp_name] = AnalyzeResultGroup(rows=rows, nmax=nmax, nsum=nsum) - return AnalyzeResult(groups=groups, missing=missing, dynamic=dynamic) + return AnalyzeResult( + groups=groups, missing=missing, dynamic=dynamic, included_funcs=included_funcs + ) ################################################################################ @@ -345,6 +351,7 @@ re_call_other = re.compile(r"(?P<func>[^(]+)\(.*") class Plugin(typing.Protocol): + def is_intrhandler(self, name: str) -> bool: ... def extra_nodes(self) -> typing.Collection[Node]: ... def indirect_callees( self, loc: str, line: str @@ -398,6 +405,14 @@ class PluginApplication: class AppPlugin: + def is_intrhandler(self, name: str) -> bool: + return name in [ + "rp2040_hwtimer_intrhandler", + "_cr_gdb_intrhandler", + "hostclock_handle_sig_alarm", + "hostnet_handle_sig_io", + ] + def extra_nodes(self) -> typing.Collection[Node]: return [] @@ -460,6 +475,9 @@ class LibObjPlugin: objcalls[method_name].add(impl_name + "_" + method_name) self.objcalls = objcalls + def is_intrhandler(self, name: str) -> bool: + return False + def extra_nodes(self) -> typing.Collection[Node]: return [] @@ -470,7 +488,7 @@ class LibObjPlugin: return None if m := re_call_objcall.fullmatch(line): if m.group("meth") in self.objcalls: - return sorted(self.objcalls[m.group("meth")]), True + return sorted(self.objcalls[m.group("meth")]), False return [ f"__indirect_call:{m.group('obj')}.vtable->{m.group('meth')}" ], False @@ -481,6 +499,14 @@ class LibObjPlugin: class LibHWPlugin: + pico_platform: str + + def __init__(self, arg_pico_platform: str) -> None: + self.pico_platform = arg_pico_platform + + def is_intrhandler(self, name: str) -> bool: + return False + def extra_nodes(self) -> typing.Collection[Node]: return [] @@ -488,11 +514,15 @@ class LibHWPlugin: if "/3rd-party/" in loc: return None if "trigger->cb(trigger->cb_arg)" in line: - return [ + ret = [ "alarmclock_sleep_intrhandler", - "w5500_tcp_alarm_handler", - "w5500_udp_alarm_handler", - ], True + ] + if self.pico_platform == "rp2040": + ret += [ + "w5500_tcp_alarm_handler", + "w5500_udp_alarm_handler", + ] + return ret, False return None def skip_call(self, chain: list[str], call: str) -> bool: @@ -500,17 +530,20 @@ class LibHWPlugin: class LibCRIPCPlugin: + def is_intrhandler(self, name: str) -> bool: + return False + def extra_nodes(self) -> typing.Collection[Node]: return [] def indirect_callees(self, loc: str, line: str) -> tuple[list[str], bool] | None: if "/3rd-party/" in loc: return None - if "/chan.h:" in loc and "front->dequeue(" in line: + if "/chan.c:" in loc and "front->dequeue(" in line: return [ "_cr_chan_dequeue", "_cr_select_dequeue", - ], True + ], False return None def skip_call(self, chain: list[str], call: str) -> bool: @@ -597,6 +630,9 @@ class Lib9PPlugin: return self._CONFIG_9P_NUM_SOCKS * self.CONFIG_9P_SRV_MAX_REQS return 1 + def is_intrhandler(self, name: str) -> bool: + return False + def extra_nodes(self) -> typing.Collection[Node]: return [] @@ -625,10 +661,23 @@ class Lib9PPlugin: for c in chain[-self.CONFIG_9P_SRV_MAX_DEPTH :] ): return True + re_msg_meth = re.compile( + r"^lib9p_(?P<grp>[TR])msg_(?P<meth>validate|unmarshal|marshal)$" + ) + wrapper = next((c for c in chain if re_msg_meth.match(c)), None) + if wrapper: + m = re_msg_meth.match(wrapper) + assert m + deny = ":" + m.group("meth") + "_" + ("R" if m.group("grp") == "T" else "T") + if deny in call: + return True return False class LibMiscPlugin: + def is_intrhandler(self, name: str) -> bool: + return False + def extra_nodes(self) -> typing.Collection[Node]: return [] @@ -648,9 +697,57 @@ class LibMiscPlugin: class PicoSDKPlugin: app_gpio_handlers: typing.Collection[str] + app_init_array: typing.Collection[str] + app_preinit_array: typing.Collection[str] - def __init__(self, app_gpio_handlers: typing.Collection[str]) -> None: + def __init__( + self, + *, + app_gpio_handlers: typing.Collection[str], + app_init_array: typing.Collection[str], + ) -> None: self.app_gpio_handlers = app_gpio_handlers + self.app_init_array = app_init_array + + # git grep '^PICO_RUNTIME_INIT_FUNC\S*(' + self.app_preinit_array = [ + # "runtime_init_mutex", # pico_mutex + # "runtime_init_default_alarm_pool", # pico_time + # "runtime_init_boot_locks_reset", # hardware_boot_lock + "runtime_init_per_core_irq_priorities", # hardware_irq + # "spinlock_set_extexclall", # hardware_sync_spin_lock + "__aeabi_bits_init", # pico_bit_ops + # "runtime_init_bootrom_locking_enable", # pico_bootrom, rp2350-only + # "runtime_init_pre_core_tls_setup", # pico_clib_interface, picolibc-only + # "__aeabi_double_init", # pico_double + # "__aeabi_float_init", # pico_float + "__aeabi_mem_init", # pico_mem_ops + "first_per_core_initializer", # pico_runtime + # pico_runtime_init + # "runtime_init_bootrom_reset", # rp2350-only + # "runtime_init_per_core_bootrom_reset", # rp2350-only + # "runtime_init_per_core_h3_irq_registers", # rp2350-only + "runtime_init_early_resets", + "runtime_init_usb_power_down", + # "runtime_init_per_core_enable_coprocessors", # PICO_RUNTIME_SKIP_INIT_PER_CORE_ENABLE_COPROCESSORS + "runtime_init_clocks", + "runtime_init_post_clock_resets", + "runtime_init_rp2040_gpio_ie_disable", + "runtime_init_spin_locks_reset", + "runtime_init_install_ram_vector_table", + ] + + def is_intrhandler(self, name: str) -> bool: + return name in [ + "gpio_default_irq_handler", + "isr_invalid", + "isr_nmi", + "isr_hardfault", + "isr_svcall", + "isr_pendsv", + "isr_systick", + *[f"isr_irq{n}" for n in range(32)], + ] def indirect_callees(self, loc: str, line: str) -> tuple[list[str], bool] | None: if "/3rd-party/pico-sdk/" not in loc or "/3rd-party/pico-sdk/lib/" in loc: @@ -672,7 +769,7 @@ class PicoSDKPlugin: if "/flash.c:" in loc and "boot2_copyout" in line: return ["_stage2_boot"], False if "/gpio.c:" in loc and call == "callback": - return sorted(self.app_gpio_handlers), True + return sorted(self.app_gpio_handlers), False if "/printf.c:" in loc: if call == "out": return [ @@ -697,6 +794,11 @@ class PicoSDKPlugin: return ["stdio_uart_out_flush"], False case "in_chars": return ["stdio_uart_in_chars"], False + if "/newlib_interface.c:" in loc: + if line == "*p)();": + return sorted(self.app_init_array), False + if "/pico_runtime/runtime.c:" in loc: + return sorted(self.app_preinit_array), False return None def skip_call(self, chain: list[str], call: str) -> bool: @@ -717,18 +819,35 @@ class PicoSDKPlugin: return False def extra_nodes(self) -> typing.Collection[Node]: + ret = [] + # src/rp2_common/hardware_divider/include/hardware/divider_helper.S save_div_state_and_lr = 5 * 4 # src/rp2_common/pico_divider/divider_hardware.S save_div_state_and_lr_64 = 5 * 4 - return [ + + # src/src/rp2_common/pico_crt0/crt0.S + for n in range(32): + ret += [synthetic_node(f"isr_irq{n}", 0, {"__unhandled_user_irq"})] + ret += [ + synthetic_node("isr_invalid", 0, {"__unhandled_user_irq"}), + synthetic_node("isr_nmi", 0, {"__unhandled_user_irq"}), + synthetic_node("isr_hardfault", 0, {"__unhandled_user_irq"}), + synthetic_node("isr_svcall", 0, {"__unhandled_user_irq"}), + synthetic_node("isr_pendsv", 0, {"__unhandled_user_irq"}), + synthetic_node("isr_systick", 0, {"__unhandled_user_irq"}), + synthetic_node(f"__unhandled_user_irq", 0), + synthetic_node("_reset_handler", 0, {"runtime_init", "main", "exit"}), + ] + + ret += [ # src/rp2_common/pico_int64_ops/pico_int64_ops_aeabi.S - synthetic_node("__aeabi_lmul", 4), + synthetic_node("__wrap___aeabi_lmul", 4), # src/rp2_common/pico_divider/divider_hardware.S # s32 aliases synthetic_node("div_s32s32", 0, {"divmod_s32s32"}), - synthetic_node("__aeabi_idiv", 0, {"divmod_s32s32"}), - synthetic_node("__aeabi_idivmod", 0, {"divmod_s32s32"}), + synthetic_node("__wrap___aeabi_idiv", 0, {"divmod_s32s32"}), + synthetic_node("__wrap___aeabi_idivmod", 0, {"divmod_s32s32"}), # s32 impl synthetic_node("divmod_s32s32", 0, {"divmod_s32s32_savestate"}), synthetic_node( @@ -739,8 +858,8 @@ class PicoSDKPlugin: synthetic_node("divmod_s32s32_unsafe", 2 * 4, {"__aeabi_idiv0"}), # u32 aliases synthetic_node("div_u32u32", 0, {"divmod_u32u32"}), - synthetic_node("__aeabi_uidiv", 0, {"divmod_u32u32"}), - synthetic_node("__aeabi_uidivmod", 0, {"divmod_u32u32"}), + synthetic_node("__wrap___aeabi_uidiv", 0, {"divmod_u32u32"}), + synthetic_node("__wrap___aeabi_uidivmod", 0, {"divmod_u32u32"}), # u32 impl synthetic_node("divmod_u32u32", 0, {"divmod_u32u32_savestate"}), synthetic_node( @@ -751,8 +870,8 @@ class PicoSDKPlugin: synthetic_node("divmod_u32u32_unsafe", 2 * 4, {"__aeabi_idiv0"}), # s64 aliases synthetic_node("div_s64s64", 0, {"divmod_s64s64"}), - synthetic_node("__aeabi_ldiv", 0, {"divmod_s64s64"}), - synthetic_node("__aeabi_ldivmod", 0, {"divmod_s64s64"}), + synthetic_node("__wrap___aeabi_ldiv", 0, {"divmod_s64s64"}), + synthetic_node("__wrap___aeabi_ldivmod", 0, {"divmod_s64s64"}), # s64 impl synthetic_node("divmod_s64s64", 0, {"divmod_s64s64_savestate"}), synthetic_node( @@ -765,8 +884,7 @@ class PicoSDKPlugin: ), # u64 aliases synthetic_node("div_u64u64", 0, {"divmod_u64u64"}), - synthetic_node("__aeabi_uldiv", 0, {"divmod_u64u64"}), - synthetic_node("__aeabi_uldivmod", 0, {"divmod_u64u64"}), + synthetic_node("__wrap___aeabi_uldivmod", 0, {"divmod_u64u64"}), # u64 impl synthetic_node("divmod_u64u64", 0, {"divmod_u64u64_savestate"}), synthetic_node( @@ -780,6 +898,42 @@ class PicoSDKPlugin: # *_rem synthetic_node("divod_s64s64_rem", 2 * 4, {"divmod_s64s64"}), synthetic_node("divod_u64u64_rem", 2 * 4, {"divmod_u64u64"}), + # src/rp2_common/pico_mem_ops/mem_ops_aeabi.S + synthetic_node("__aeabi_mem_init", 0, {"rom_funcs_lookup"}), + synthetic_node( + "__wrap___aeabi_memset", 0, {"rom_func_lookup(ROM_FUNC_MEMSET)"} + ), + synthetic_node("__wrap___aeabi_memset4", 0, {"__wrap___aeabi_memset8"}), + synthetic_node( + "__wrap___aeabi_memset8", 0, {"rom_func_lookup(ROM_FUNC_MEMSET4)"} + ), + synthetic_node("__wrap___aeabi_memcpy4", 0, {"__wrap___aeabi_memcpy8"}), + synthetic_node( + "__wrap___aeabi_memcpy7", 0, {"rom_func_lookup(ROM_FUNC_MEMCPY4)"} + ), + synthetic_node("__wrap_memset", 0, {"rom_func_lookup(ROM_FUNC_MEMSET)"}), + synthetic_node("__wrap___aeabi_memcpy", 0, {"__wrap_memcpy"}), + synthetic_node("__wrap_memcpy", 0, {"rom_func_lookup(ROM_FUNC_MEMCPY)"}), + # src/rp2_common/pico_bit_ops/bit_ops_aeabi.S + synthetic_node("__aeabi_bits_init", 0, {"rom_funcs_lookup"}), + synthetic_node("__wrap___clz", 0, {"__wrap___clzsi2"}), + synthetic_node("__wrap___clzl", 0, {"__wrap___clzsi2"}), + synthetic_node("__wrap___clzsi2", 0, {"rom_func_lookup(ROM_FUNC_CLZ32"}), + synthetic_node("__wrap___ctzsi2", 0, {"rom_func_lookup(ROM_FUNC_CTZ32"}), + synthetic_node( + "__wrap___popcountsi2", 0, {"rom_func_lookup(ROM_FUNC_POPCOUNT32"} + ), + synthetic_node("__wrap___clzll", 0, {"__wrap___clzdi2"}), + synthetic_node("__wrap___clzdi2", 4, {"rom_func_lookup(ROM_FUNC_CLZ32"}), + synthetic_node("__wrap___ctzdi2", 4, {"rom_func_lookup(ROM_FUNC_CTZ32"}), + synthetic_node( + "__wrap___popcountdi2", 3 * 4, {"rom_func_lookup(ROM_FUNC_POPCOUNT32"} + ), + synthetic_node("__rev", 0, {"reverse32"}), + synthetic_node("__revl", 0, {"reverse32"}), + synthetic_node("reverse32", 0, {"rom_func_lookup(ROM_FUNC_REVERSE32"}), + synthetic_node("__revll", 0, {"reverse64"}), + synthetic_node("reverse64", 3 * 4, {"rom_func_lookup(ROM_FUNC_REVERSE32"}), # src/rp2040/boot_stage2/boot2_${name,,}.S for name=W25Q080, # controlled by `#define PICO_BOOT_STAGE2_{name} 1` in # src/boards/include/boards/pico.h @@ -790,6 +944,7 @@ class PicoSDKPlugin: # synthetic_node("rom_func_lookup(ROM_FUNC_FLASH_FLUSH_CACHE)", 0), # TODO # synthetic_node("rom_hword_as_ptr(BOOTROM_TABLE_LOOKUP_OFFSET)", 0), # TODO ] + return ret class TinyUSBDevicePlugin: @@ -856,6 +1011,9 @@ class TinyUSBDevicePlugin: in_table = True self.tud_drivers = tud_drivers + def is_intrhandler(self, name: str) -> bool: + return False + def extra_nodes(self) -> typing.Collection[Node]: return [] @@ -884,6 +1042,9 @@ class TinyUSBDevicePlugin: class NewlibPlugin: + def is_intrhandler(self, name: str) -> bool: + return False + def extra_nodes(self) -> typing.Collection[Node]: # This is accurate to # /usr/arm-none-eabi/lib/thumb/v6-m/nofp/libg.a as of @@ -916,6 +1077,7 @@ class NewlibPlugin: synthetic_node("__errno", 0), synthetic_node("_getpid_r", 8, {"_getpid"}), synthetic_node("random", 8), + synthetic_node("register_fini", 8, {"atexit"}), ] def indirect_callees(self, loc: str, line: str) -> tuple[list[str], bool] | None: @@ -926,6 +1088,9 @@ class NewlibPlugin: class LibGCCPlugin: + def is_intrhandler(self, name: str) -> bool: + return False + def extra_nodes(self) -> typing.Collection[Node]: # This is accurate to # /usr/lib/gcc/arm-none-eabi/14.2.0/thumb/v6-m/nofp/libgcc.a @@ -952,7 +1117,6 @@ def main( ) -> None: plugins: list[Plugin] = [] - hooks_is_intrhandler: list[typing.Callable[[str], bool]] = [] # sbc-harness #################################################### @@ -967,24 +1131,14 @@ def main( if "9p" in name: return lib9p_plugin.thread_count(name) return 1 - if name == "main": + if name == ("_reset_handler" if arg_pico_platform == "rp2040" else "main"): return 1 return 0 - def sbc_is_intrhandler(name: str) -> bool: - return name in [ - "rp2040_hwtimer_intrhandler", - "_cr_gdb_intrhandler", - "hostclock_handle_sig_alarm", - "hostnet_handle_sig_io", - ] - - hooks_is_intrhandler += [sbc_is_intrhandler] - plugins += [ AppPlugin(), LibObjPlugin(arg_c_fnames), - LibHWPlugin(), + LibHWPlugin(arg_pico_platform), LibCRIPCPlugin(), lib9p_plugin, LibMiscPlugin(), @@ -993,16 +1147,11 @@ def main( # pico-sdk ####################################################### if arg_pico_platform == "rp2040": - - def pico_is_intrhandler(name: str) -> bool: - return name in [ - "gpio_default_irq_handler", - ] - - hooks_is_intrhandler += [pico_is_intrhandler] - plugins += [ - PicoSDKPlugin(sbc_gpio_handlers), + PicoSDKPlugin( + app_gpio_handlers=sbc_gpio_handlers, + app_init_array=["register_fini"], + ), TinyUSBDevicePlugin(arg_c_fnames), NewlibPlugin(), LibGCCPlugin(), @@ -1015,8 +1164,8 @@ def main( def intrhandler_filter(name: str) -> int: name = name.rsplit(":", 1)[-1] - for hook in hooks_is_intrhandler: - if hook(name): + for plugin in plugins: + if plugin.is_intrhandler(name): return 1 return 0 @@ -1055,6 +1204,8 @@ def main( # Print. print("= " + grp_name + " " + sep1[len(grp_name) + 3 :]) for name, num in sorted(grp.rows.items()): + if num == 0: + continue print(f"{name.ljust(namelen)} {str(num).rjust(numlen)}") print(sep2) print(f"{'Total'.ljust(namelen)} {str(grp.nsum).rjust(numlen)}") @@ -1078,7 +1229,7 @@ def main( baselen = max(len(str(r[1])) for r in rows) sizelen = max(len(str(r[2])) for r in rows) for row in sorted(rows): - if row[0] == "main": + if row[0] in ("main", "_reset_handler"): continue print("const size_t CONFIG_COROUTINE_STACK_SIZE_", end="") print(f"{row[0].ljust(namelen)} =", end="") @@ -1094,6 +1245,11 @@ def main( print(f"warning: dynamic-stack-usage: {funcname}") print("*/") + print("") + print("/*") + for funcname in sorted(result.included_funcs): + print(f"included: {funcname}") + print("*/") if __name__ == "__main__": diff --git a/cmd/sbc_harness/CMakeLists.txt b/cmd/sbc_harness/CMakeLists.txt index 9025447..081a5fc 100644 --- a/cmd/sbc_harness/CMakeLists.txt +++ b/cmd/sbc_harness/CMakeLists.txt @@ -48,6 +48,7 @@ target_sources(sbc_harness PRIVATE target_link_libraries(sbc_harness pico_standard_link ) +target_link_options(sbc_harness PRIVATE "$<TARGET_PROPERTY:sbc_harness_objs,LINK_OPTIONS>") pico_add_extra_outputs(sbc_harness) # create .map/.bin/.hex/.uf2 files in addition to .elf pico_set_program_url(sbc_harness "https://git.lukeshu.com/sbc-harness") diff --git a/libcr_ipc/CMakeLists.txt b/libcr_ipc/CMakeLists.txt index 0c3bd57..4590bdd 100644 --- a/libcr_ipc/CMakeLists.txt +++ b/libcr_ipc/CMakeLists.txt @@ -5,6 +5,10 @@ add_library(libcr_ipc INTERFACE) target_include_directories(libcr_ipc SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) +target_sources(libcr_ipc INTERFACE + chan.c + select.c +) target_link_libraries(libcr_ipc INTERFACE libcr ) diff --git a/libcr_ipc/chan.c b/libcr_ipc/chan.c new file mode 100644 index 0000000..7dd1132 --- /dev/null +++ b/libcr_ipc/chan.c @@ -0,0 +1,40 @@ +/* libcr_ipc/chan.c - Simple channels for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr_ipc/chan.h> + +void _cr_chan_dequeue(void *_ch, size_t) { + struct _cr_chan *ch = _ch; + _cr_ipc_dll_pop_from_front(&ch->waiters); +} + +void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void *val_ptr, size_t val_size) { + assert(ch); + assert(val_ptr); + + if (ch->waiters.front && ch->waiter_typ != self_typ) { /* non-blocking fast-path */ + /* Copy. */ + struct _cr_chan_waiter *front = _cr_ipc_dll_node_cast(struct _cr_chan_waiter, ch->waiters.front); + if (self_typ == _CR_CHAN_SENDER) + memcpy(front->val_ptr, val_ptr, val_size); + else + memcpy(val_ptr, front->val_ptr, val_size); + cr_unpause(front->cid); + front->dequeue(front->dequeue_arg1, + front->dequeue_arg2); + cr_yield(); + } else { /* blocking slow-path */ + struct _cr_chan_waiter self = { + .cid = cr_getcid(), + .val_ptr = val_ptr, + .dequeue = _cr_chan_dequeue, + .dequeue_arg1 = ch, + }; + _cr_ipc_dll_push_to_rear(&ch->waiters, &self); + ch->waiter_typ = self_typ; + cr_pause_and_yield(); + } +} diff --git a/libcr_ipc/include/libcr_ipc/chan.h b/libcr_ipc/include/libcr_ipc/chan.h index da4f08e..5b1e583 100644 --- a/libcr_ipc/include/libcr_ipc/chan.h +++ b/libcr_ipc/include/libcr_ipc/chan.h @@ -1,6 +1,6 @@ /* libcr_ipc/chan.h - Simple channels for libcr * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -122,37 +122,7 @@ struct _cr_chan { _cr_ipc_dll_root waiters; }; -static void _cr_chan_dequeue(void *_ch, size_t) { - struct _cr_chan *ch = _ch; - _cr_ipc_dll_pop_from_front(&ch->waiters); -} - -static inline void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void *val_ptr, size_t val_size) { - assert(ch); - assert(val_ptr); - - if (ch->waiters.front && ch->waiter_typ != self_typ) { /* non-blocking fast-path */ - /* Copy. */ - struct _cr_chan_waiter *front = _cr_ipc_dll_node_cast(struct _cr_chan_waiter, ch->waiters.front); - if (self_typ == _CR_CHAN_SENDER) - memcpy(front->val_ptr, val_ptr, val_size); - else - memcpy(val_ptr, front->val_ptr, val_size); - cr_unpause(front->cid); - front->dequeue(front->dequeue_arg1, - front->dequeue_arg2); - cr_yield(); - } else { /* blocking slow-path */ - struct _cr_chan_waiter self = { - .cid = cr_getcid(), - .val_ptr = val_ptr, - .dequeue = _cr_chan_dequeue, - .dequeue_arg1 = ch, - }; - _cr_ipc_dll_push_to_rear(&ch->waiters, &self); - ch->waiter_typ = self_typ; - cr_pause_and_yield(); - } -} +void _cr_chan_dequeue(void *_ch, size_t); +void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void *val_ptr, size_t val_size); #endif /* _LIBCR_IPC_CHAN_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/select.h b/libcr_ipc/include/libcr_ipc/select.h index ee49cca..b845082 100644 --- a/libcr_ipc/include/libcr_ipc/select.h +++ b/libcr_ipc/include/libcr_ipc/select.h @@ -1,6 +1,6 @@ /* libcr_ipc/select.h - Select between channels * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -69,106 +69,14 @@ enum _cr_select_class { _CR_SELECT_CLASS_NONBLOCK, }; -static inline enum _cr_select_class _cr_select_getclass(struct cr_select_arg arg) { - switch (arg.op) { - case _CR_SELECT_OP_RECV: - if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_SENDER) - return _CR_SELECT_CLASS_NONBLOCK; - else - return _CR_SELECT_CLASS_BLOCKING; - case _CR_SELECT_OP_SEND: - if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_RECVER) - return _CR_SELECT_CLASS_NONBLOCK; - else - return _CR_SELECT_CLASS_BLOCKING; - case _CR_SELECT_OP_DEFAULT: - return _CR_SELECT_CLASS_DEFAULT; - default: - assert_notreached("invalid arg.op"); - } -} - struct _cr_select_waiters { size_t cnt; struct cr_select_arg *args; struct _cr_chan_waiter *nodes; }; -static void _cr_select_dequeue(void *_waiters, size_t idx) { - struct _cr_select_waiters *waiters = waiters; - for (size_t i = 0; i < waiters->cnt; i++) - _cr_ipc_dll_remove(&(waiters->args[i].ch->waiters), - &(waiters->nodes[i])); - waiters->cnt = idx; -} - -static size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) { - size_t cnt_blocking = 0; - size_t cnt_nonblock = 0; - size_t cnt_default = 0; - - assert(arg_cnt); - assert(arg_vec); - cr_assert_in_coroutine(); - - for (size_t i = 0; i < arg_cnt; i++) { - switch (_cr_select_getclass(arg_vec[i])) { - case _CR_SELECT_CLASS_BLOCKING: - cnt_blocking++; - break; - case _CR_SELECT_CLASS_NONBLOCK: - cnt_nonblock++; - break; - case _CR_SELECT_CLASS_DEFAULT: - cnt_default++; - break; - } - } - - if (cnt_nonblock) { - size_t choice = rand_uint63n(cnt_nonblock); - for (size_t i = 0, seen = 0; i < arg_cnt; i++) { - if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_NONBLOCK) { - if (seen == choice) { - _cr_chan_xfer(arg_vec[i].op == _CR_SELECT_OP_RECV - ? _CR_CHAN_RECVER - : _CR_CHAN_SENDER, - arg_vec[i].ch, - arg_vec[i].val_ptr, - arg_vec[i].val_siz); - return i; - } - seen++; - } - } - assert_notreached("should have returned from inside for() loop"); - } - - if (cnt_default) { - for (size_t i = 0; i < arg_cnt; i++) - if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_DEFAULT) - return i; - assert_notreached("should have returned from inside for() loop"); - } - - struct _cr_select_waiters waiters = { - .cnt = arg_cnt, - .args = arg_vec, - .nodes = alloca(sizeof(struct _cr_chan_waiter) * arg_cnt), - }; - for (size_t i = 0; i < arg_cnt; i++) { - waiters.nodes[i] = (struct _cr_chan_waiter){ - .cid = cr_getcid(), - .val_ptr = arg_vec[i].val_ptr, - .dequeue = _cr_select_dequeue, - .dequeue_arg1 = &waiters, - .dequeue_arg2 = i, - }; - _cr_ipc_dll_push_to_rear(&arg_vec[i].ch->waiters, &waiters.nodes[i]); - } - cr_pause_and_yield(); - return waiters.cnt; -} +void _cr_select_dequeue(void *_waiters, size_t idx); +size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]); /* cr_select_l(arg1, arg2, arg3, ...) ******************************************/ diff --git a/libcr_ipc/select.c b/libcr_ipc/select.c new file mode 100644 index 0000000..4bd9067 --- /dev/null +++ b/libcr_ipc/select.c @@ -0,0 +1,102 @@ +/* libcr_ipc/select.c - Select between channels + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr_ipc/select.h> + +static inline enum _cr_select_class _cr_select_getclass(struct cr_select_arg arg) { + switch (arg.op) { + case _CR_SELECT_OP_RECV: + if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_SENDER) + return _CR_SELECT_CLASS_NONBLOCK; + else + return _CR_SELECT_CLASS_BLOCKING; + case _CR_SELECT_OP_SEND: + if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_RECVER) + return _CR_SELECT_CLASS_NONBLOCK; + else + return _CR_SELECT_CLASS_BLOCKING; + case _CR_SELECT_OP_DEFAULT: + return _CR_SELECT_CLASS_DEFAULT; + default: + assert_notreached("invalid arg.op"); + } +} + +void _cr_select_dequeue(void *_waiters, size_t idx) { + struct _cr_select_waiters *waiters = _waiters; + for (size_t i = 0; i < waiters->cnt; i++) + _cr_ipc_dll_remove(&(waiters->args[i].ch->waiters), + &(waiters->nodes[i])); + waiters->cnt = idx; +} + +size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) { + size_t cnt_blocking = 0; + size_t cnt_nonblock = 0; + size_t cnt_default = 0; + + assert(arg_cnt); + assert(arg_vec); + cr_assert_in_coroutine(); + + for (size_t i = 0; i < arg_cnt; i++) { + switch (_cr_select_getclass(arg_vec[i])) { + case _CR_SELECT_CLASS_BLOCKING: + cnt_blocking++; + break; + case _CR_SELECT_CLASS_NONBLOCK: + cnt_nonblock++; + break; + case _CR_SELECT_CLASS_DEFAULT: + cnt_default++; + break; + } + } + + if (cnt_nonblock) { + size_t choice = rand_uint63n(cnt_nonblock); + for (size_t i = 0, seen = 0; i < arg_cnt; i++) { + if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_NONBLOCK) { + if (seen == choice) { + _cr_chan_xfer(arg_vec[i].op == _CR_SELECT_OP_RECV + ? _CR_CHAN_RECVER + : _CR_CHAN_SENDER, + arg_vec[i].ch, + arg_vec[i].val_ptr, + arg_vec[i].val_siz); + return i; + } + seen++; + } + } + assert_notreached("should have returned from inside for() loop"); + } + + if (cnt_default) { + for (size_t i = 0; i < arg_cnt; i++) + if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_DEFAULT) + return i; + assert_notreached("should have returned from inside for() loop"); + } + + struct _cr_select_waiters waiters = { + .cnt = arg_cnt, + .args = arg_vec, + .nodes = alloca(sizeof(struct _cr_chan_waiter) * arg_cnt), + }; + for (size_t i = 0; i < arg_cnt; i++) { + waiters.nodes[i] = (struct _cr_chan_waiter){ + .cid = cr_getcid(), + .val_ptr = arg_vec[i].val_ptr, + .dequeue = _cr_select_dequeue, + .dequeue_arg1 = &waiters, + .dequeue_arg2 = i, + }; + _cr_ipc_dll_push_to_rear(&arg_vec[i].ch->waiters, &waiters.nodes[i]); + } + cr_pause_and_yield(); + return waiters.cnt; +} |