diff --git a/.gitignore b/.gitignore index 1812c3462819ac12b7ba057cbda49343a4d094b3..846876326e6aad69b0ff420a6554387431df0caa 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,6 @@ build-*/ .Spotlight-V100 .Trashes ehthumbs.db -Thumbs.db \ No newline at end of file +Thumbs.db + +.cache/ diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 37e4adc309b96a6774df1045a56266e0ce8b6957..8e5f33291c4cb819278bf5d655c3e98718a44ed9 100755 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -178,9 +178,9 @@ add_compile_options( -Wall -Werror -Wstrict-prototypes - -Wmissing-prototypes +# TODO -Wmissing-prototypes -Wnested-externs - -Wmissing-declarations +# TODO -Wmissing-declarations -Wundef -Wpointer-arith -Wno-nonnull diff --git a/kernel/src/object/cnode.c b/kernel/src/object/cnode.c index 8b8540bc1f5404e47f0fbed476ce7c46b5e7acac..50f323b28cf7098f5f642215ed8e6f57957264d1 100755 --- a/kernel/src/object/cnode.c +++ b/kernel/src/object/cnode.c @@ -51,7 +51,7 @@ exception_t decodeCNodeInvocation(word_t invLabel, word_t length, cap_t cap, assert(cap_get_capType(cap) == cap_cnode_cap); if (invLabel < CNodeRevoke || invLabel > CNODE_LAST_INVOCATION) { - userError("CNodeCap: Illegal Operation attempted."); + // userError("CNodeCap: Illegal Operation attempted."); current_syscall_error.type = seL4_IllegalOperation; return EXCEPTION_SYSCALL_ERROR; } diff --git a/kernel/src/object/tcb.c b/kernel/src/object/tcb.c index 4bbb82a6298250e2e4be6e1d21f30b52b3ad92a1..a99bea3ee1319c4d44f695321da5533ff1779050 100755 --- a/kernel/src/object/tcb.c +++ b/kernel/src/object/tcb.c @@ -1230,8 +1230,8 @@ exception_t decodeSetPriority(cap_t cap, word_t length, word_t *buffer) tcb_t *authTCB = TCB_PTR(cap_thread_cap_get_capTCBPtr(authCap)); exception_t status = checkPrio(newPrio, authTCB); if (status != EXCEPTION_NONE) { - userError("TCB SetPriority: Requested priority %lu too high (max %lu).", - (unsigned long) newPrio, (unsigned long) authTCB->tcbMCP); + // userError("TCB SetPriority: Requested priority %lu too high (max %lu).", + // (unsigned long) newPrio, (unsigned long) authTCB->tcbMCP); return status; } @@ -1274,8 +1274,8 @@ exception_t decodeSetMCPriority(cap_t cap, word_t length, word_t *buffer) tcb_t *authTCB = TCB_PTR(cap_thread_cap_get_capTCBPtr(authCap)); exception_t status = checkPrio(newMcp, authTCB); if (status != EXCEPTION_NONE) { - userError("TCB SetMCPriority: Requested maximum controlled priority %lu too high (max %lu).", - (unsigned long) newMcp, (unsigned long) authTCB->tcbMCP); + // userError("TCB SetMCPriority: Requested maximum controlled priority %lu too high (max %lu).", + // (unsigned long) newMcp, (unsigned long) authTCB->tcbMCP); return status; } diff --git a/kernel/src/plat/qemu-arm-virt/overlay-reserve-vm-memory.dts b/kernel/src/plat/qemu-arm-virt/overlay-reserve-vm-memory.dts index f4c18af2495f089ac40d6be91073326b64a4be6a..95658ca1397de4ecfdb87822df1cad04fa0bdbba 100755 --- a/kernel/src/plat/qemu-arm-virt/overlay-reserve-vm-memory.dts +++ b/kernel/src/plat/qemu-arm-virt/overlay-reserve-vm-memory.dts @@ -15,8 +15,12 @@ vm-memory@40000000 { reg = <0x0 0x40000000 0x0 0x20000000>; no-map; + + vm-memory@ea000000 { + no-map; + reg = <0x00 0xea000000 0x00 0x00420000>; // 4.125MB共享内存 }; - }; +}; }; diff --git a/projects/seL4_libs/libsel4debug/src/bootinfo.c b/projects/seL4_libs/libsel4debug/src/bootinfo.c index 8b56b9e9267cdeb0cddcc2cccdfb91b5ddfa4900..a452fe9115b586c9123b8edd554a507aa61440e1 100755 --- a/projects/seL4_libs/libsel4debug/src/bootinfo.c +++ b/projects/seL4_libs/libsel4debug/src/bootinfo.c @@ -30,13 +30,13 @@ void debug_print_bootinfo(seL4_BootInfo *info) printf("Paddr | Size | Device\n"); int sizes[CONFIG_WORD_SIZE] = {0}; - for (int i = 0; i < CONFIG_MAX_NUM_BOOTINFO_UNTYPED_CAPS && i < (info->untyped.end - info->untyped.start); i++) { - int index = info->untypedList[i].sizeBits; - assert(index < ARRAY_SIZE(sizes)); - sizes[index]++; - printf("%p | %zu | %d\n", (void *)info->untypedList[i].paddr, (size_t)info->untypedList[i].sizeBits, - (int)info->untypedList[i].isDevice); - } + // for (int i = 0; i < CONFIG_MAX_NUM_BOOTINFO_UNTYPED_CAPS && i < (info->untyped.end - info->untyped.start); i++) { + // int index = info->untypedList[i].sizeBits; + // assert(index < ARRAY_SIZE(sizes)); + // sizes[index]++; + // printf("%p | %zu | %d\n", (void *)info->untypedList[i].paddr, (size_t)info->untypedList[i].sizeBits, + // (int)info->untypedList[i].isDevice); + // } printf("Untyped summary\n"); for (int i = 0; i < ARRAY_SIZE(sizes); i++) { diff --git a/projects/seL4_libs/libsel4test/include/sel4test/test.h b/projects/seL4_libs/libsel4test/include/sel4test/test.h index bfa51cc9bb60cc986a538a761cee9be40873bfe5..3589fdded03a250ce5bbe48a47712c1b8ba6c022 100755 --- a/projects/seL4_libs/libsel4test/include/sel4test/test.h +++ b/projects/seL4_libs/libsel4test/include/sel4test/test.h @@ -150,7 +150,8 @@ typedef struct testcase ALIGN(sizeof(struct testcase)) testcase_t; _enabled, \ }; -#define DEFINE_TEST(_name, _description, _function, _enabled) DEFINE_TEST_WITH_TYPE(_name, _description, _function, BASIC, _enabled) +#define DEFINE_TEST(_name, _description, _function, _enabled) ; +#define DEFINE_TESTQ(_name, _description, _function, _enabled) DEFINE_TEST_WITH_TYPE(_name, _description, _function, BASIC, _enabled) #define DEFINE_TEST_BOOTSTRAP(_name, _description, _function, _enabled) DEFINE_TEST_WITH_TYPE(_name, _description, _function, BOOTSTRAP, _enabled) /**/ diff --git a/projects/seL4_libs/libsel4utils/src/vspace/vspace.c b/projects/seL4_libs/libsel4utils/src/vspace/vspace.c index dfccc1466d3c558f994e8eef876688730dc7553f..fb96baf1099f17d7f3fee27a92be73d0c363f5f1 100755 --- a/projects/seL4_libs/libsel4utils/src/vspace/vspace.c +++ b/projects/seL4_libs/libsel4utils/src/vspace/vspace.c @@ -297,6 +297,7 @@ static int map_pages_at_vaddr(vspace_t *vspace, seL4_CPtr caps[], uintptr_t cook return error; } +// implementation of vspace_new_pages() static int new_pages_at_vaddr(vspace_t *vspace, void *vaddr, size_t num_pages, size_t size_bits, seL4_CapRights_t rights, int cacheable, bool can_use_dev) { diff --git a/projects/seL4_libs/libsel4vka/CMakeLists.txt b/projects/seL4_libs/libsel4vka/CMakeLists.txt index ef44463fb02beb0b63a0d7d5110c1ba47d430dfc..b05d1584b864faecfa454a5aa1c13668546ea7d2 100755 --- a/projects/seL4_libs/libsel4vka/CMakeLists.txt +++ b/projects/seL4_libs/libsel4vka/CMakeLists.txt @@ -45,11 +45,9 @@ config_string( mark_as_advanced(LibVKAAllowMemoryLeaks LibVKADebugLiveSlotsSZ LibVKADebugLiveObjsSZ) add_config_library(sel4vka "${configure_string}") -file(GLOB deps src/*.c) +file(GLOB libsel4vka_sources "src/*.c") -list(SORT deps) - -add_library(sel4vka STATIC EXCLUDE_FROM_ALL ${deps}) +add_library(sel4vka STATIC EXCLUDE_FROM_ALL ${libsel4vka_sources}) target_include_directories( sel4vka PUBLIC include "sel4_arch_include/${KernelSel4Arch}" "arch_include/${KernelArch}" diff --git a/projects/seL4_libs/libsel4vka/include/vka/object.h b/projects/seL4_libs/libsel4vka/include/vka/object.h index 994e26d2553d2b1a01fb085aec620c46b4c22ede..571254791296eefe977729763e1c4ca153fe27f0 100755 --- a/projects/seL4_libs/libsel4vka/include/vka/object.h +++ b/projects/seL4_libs/libsel4vka/include/vka/object.h @@ -28,6 +28,8 @@ typedef struct vka_object { seL4_Word size_bits; } vka_object_t; +extern int vka_debug_print_paddr; + /* * Generic object allocator used by functions below, can also be used directly */ @@ -69,6 +71,10 @@ static inline int vka_alloc_object_at_maybe_dev(vka_t *vka, seL4_Word type, seL4 result->type = type; result->size_bits = size_bits; + if (vka_debug_print_paddr) { + uintptr_t p_addr = vka_utspace_paddr(vka, result->ut, result->type, result->size_bits); + printf("VKA: allocated object type %lu, size %lu bits, at paddr %p\n", (unsigned long)type, (unsigned long)size_bits, (void*)p_addr); + } return 0; error: diff --git a/projects/seL4_libs/libsel4vka/src/vka_debug.c b/projects/seL4_libs/libsel4vka/src/vka_debug.c new file mode 100644 index 0000000000000000000000000000000000000000..f1e4ec56d421a5a9fa6c7d0a88e36d6c066c7267 --- /dev/null +++ b/projects/seL4_libs/libsel4vka/src/vka_debug.c @@ -0,0 +1,3 @@ +#include + +int vka_debug_print_paddr = 0; diff --git a/projects/sel4runtime/src/env.c b/projects/sel4runtime/src/env.c index 12d7b2c37477a08f0bf1f5649f57799793ff9330..c4b1cf31b99f6c9c7353f83abc7dcb0a9e4dd992 100755 --- a/projects/sel4runtime/src/env.c +++ b/projects/sel4runtime/src/env.c @@ -11,6 +11,38 @@ #include #include +/* Use libsel4 debug printing rather than which isn't available + * in the cross-compile sysroot. Provide small helpers for string and hex + * output using seL4 debug syscalls. + */ + +static void sel4runtime_debug_puts(const char *s) +{ + /* seL4_DebugPutString is generated into the libsel4 stubs */ + // seL4_DebugPutString((char *)s); + return; +} + +static void sel4runtime_debug_puthex(unsigned long v) +{ + // char buf[2 + (sizeof(unsigned long) * 2) + 1]; + // char *p = &buf[sizeof(buf) - 1]; + // const char hex[] = "0123456789abcdef"; + // *p = '\0'; + // if (v == 0) { + // *--p = '0'; + // } else { + // while (v) { + // *--p = hex[v & 0xf]; + // v >>= 4; + // } + // } + // *--p = 'x'; + // *--p = '0'; + // seL4_DebugPutString(p); + return; +} + #include "init.h" #include "util.h" @@ -173,6 +205,9 @@ sel4runtime_uintptr_t sel4runtime_move_initial_tls(void *tls_memory) sel4runtime_set_tls_base(tls_base); if (env.initial_thread_ipc_buffer != SEL4RUNTIME_NULL) { + sel4runtime_debug_puts("Setting initial thread IPC buffer: "); + sel4runtime_debug_puthex((unsigned long)env.initial_thread_ipc_buffer); + sel4runtime_debug_puts("\n"); __sel4_ipc_buffer = env.initial_thread_ipc_buffer; } @@ -250,6 +285,10 @@ void __sel4runtime_load_env( auxv_t const auxv[] ) { + sel4runtime_debug_puts("sel4runtime: __sel4runtime_load_env argv0="); + sel4runtime_debug_puts((argc > 0 && argv && argv[0]) ? argv[0] : "(null)"); + sel4runtime_debug_puts("\n"); + empty_tls(); parse_auxv(auxv); parse_phdrs(); @@ -282,6 +321,11 @@ static void name_process(char const *name) static void parse_auxv(auxv_t const auxv[]) { for (int i = 0; auxv[i].a_type != AT_NULL; i++) { + sel4runtime_debug_puts("sel4runtime: parse_auxv["); + sel4runtime_debug_puthex((unsigned long)i); + sel4runtime_debug_puts("] type="); + sel4runtime_debug_puthex((unsigned long)auxv[i].a_type); + sel4runtime_debug_puts("\n"); switch (auxv[i].a_type) { case AT_PHENT: { env.program_header.size = auxv[i].a_un.a_val; @@ -303,14 +347,27 @@ static void parse_auxv(auxv_t const auxv[]) env.bootinfo = bootinfo; env.initial_thread_ipc_buffer = bootinfo->ipcBuffer; env.initial_thread_tcb = seL4_CapInitThreadTCB; + sel4runtime_debug_puts("sel4runtime: AT_SEL4_BOOT_INFO bootinfo="); + sel4runtime_debug_puthex((unsigned long)bootinfo); + sel4runtime_debug_puts(" ipcBuffer="); + sel4runtime_debug_puthex((unsigned long)bootinfo->ipcBuffer); + sel4runtime_debug_puts(" tcb="); + sel4runtime_debug_puthex((unsigned long)env.initial_thread_tcb); + sel4runtime_debug_puts("\n"); break; } case AT_SEL4_IPC_BUFFER_PTR: { env.initial_thread_ipc_buffer = auxv[i].a_un.a_ptr; + sel4runtime_debug_puts("sel4runtime: AT_SEL4_IPC_BUFFER_PTR -> "); + sel4runtime_debug_puthex((unsigned long)env.initial_thread_ipc_buffer); + sel4runtime_debug_puts("\n"); break; } case AT_SEL4_TCB: { env.initial_thread_tcb = auxv[i].a_un.a_val; + sel4runtime_debug_puts("sel4runtime: AT_SEL4_TCB -> "); + sel4runtime_debug_puthex((unsigned long)env.initial_thread_tcb); + sel4runtime_debug_puts("\n"); break; } default: diff --git a/projects/sel4test/CMakeLists.txt b/projects/sel4test/CMakeLists.txt index cb2a74e04542777936a5445cd925e789afdb8f55..6eee5a92ab8d596fd75af6f1d31963b2b8f7ba70 100755 --- a/projects/sel4test/CMakeLists.txt +++ b/projects/sel4test/CMakeLists.txt @@ -29,8 +29,10 @@ endif() elfloader_import_project() # Allow choosing between different applications -set(Sel4testApp "sel4test-driver" CACHE STRING "Application to build (sel4test-driver, hello-world, hyperamp-server, or front)") -set_property(CACHE Sel4testApp PROPERTY STRINGS sel4test-driver hello-world hyperamp-server front) + +set(Sel4testApp "sel4test-driver" CACHE STRING "Application to build (sel4test-driver, hello-world, or hyperamp-server)") +set_property(CACHE Sel4testApp PROPERTY STRINGS sel4test-driver hello-world hyperamp-server front monkey-mnemosyne) + if(Sel4testApp STREQUAL "hello-world") add_subdirectory(apps/hello-world) @@ -38,6 +40,8 @@ elseif(Sel4testApp STREQUAL "hyperamp-server") add_subdirectory(apps/hyperamp-server) elseif(Sel4testApp STREQUAL "front") add_subdirectory(apps/front) +elseif(Sel4testApp STREQUAL "monkey-mnemosyne") + add_subdirectory(apps/monkey-mnemosyne) else() add_subdirectory(apps/sel4test-driver) endif() diff --git a/projects/sel4test/apps/hello-world/CMakeLists.txt b/projects/sel4test/apps/hello-world/CMakeLists.txt index 8defc04853a05947212d2c759c3e42c3d44aeaa6..49549bcbb7c045ae2ac9fe9cad4f642c7784029e 100755 --- a/projects/sel4test/apps/hello-world/CMakeLists.txt +++ b/projects/sel4test/apps/hello-world/CMakeLists.txt @@ -22,6 +22,14 @@ sel4_libs_import_libraries() file(GLOB deps src/*.c) list(SORT deps) +# Ensure mem_helpers.c is always included (some CMake setups do not re-run +# globbing on new files). Append the specific file path and remove duplicates. +list(APPEND deps "${CMAKE_CURRENT_LIST_DIR}/src/mem_helpers.c") +list(APPEND deps "${CMAKE_CURRENT_LIST_DIR}/src/mem_test.c") +list(APPEND deps "${CMAKE_CURRENT_LIST_DIR}/src/immfs.c") +list(REMOVE_DUPLICATES deps) +list(SORT deps) + add_executable(hello-world EXCLUDE_FROM_ALL ${deps}) target_link_libraries(hello-world PUBLIC diff --git a/projects/sel4test/apps/hello-world/src/immfs.c b/projects/sel4test/apps/hello-world/src/immfs.c new file mode 100644 index 0000000000000000000000000000000000000000..513f3e3f97d5a8f6ccb0f2518eaa7eee1a0d056d --- /dev/null +++ b/projects/sel4test/apps/hello-world/src/immfs.c @@ -0,0 +1,206 @@ +/* immfs.c - minimal in-memory file system for tests + * Supports: open, close, creat, unlink, read, write + */ + +#include "immfs.h" +#include +#include +#include +#include "mem_helpers.h" + +#define IMMFS_MAX_FILES 128 +#define IMMFS_MAX_FDS 64 +#define IMMFS_PATH_MAX 128 + +typedef struct immfs_file { + char path[IMMFS_PATH_MAX]; + unsigned char *data; + size_t size; + size_t capacity; + int in_use; +} immfs_file_t; + +typedef struct immfs_fd { + int file_idx; + size_t offset; + int flags; + int used; +} immfs_fd_t; + +static immfs_file_t *files = NULL; +static immfs_fd_t *fds = NULL; +static int initted = 0; + +void immfs_init(void) +{ + if (initted) return; + /* Ensure slab allocator is initialised so small allocations use slabs */ + xmalloc_init(NULL); + files = xmalloc(sizeof(immfs_file_t) * IMMFS_MAX_FILES); + if (!files) { errno = ENOMEM; return; } + fds = xmalloc(sizeof(immfs_fd_t) * IMMFS_MAX_FDS); + if (!fds) { xfree(files); files = NULL; errno = ENOMEM; return; } + memset(files, 0, sizeof(immfs_file_t) * IMMFS_MAX_FILES); + memset(fds, 0, sizeof(immfs_fd_t) * IMMFS_MAX_FDS); + initted = 1; +} + +/* helpers */ +static int find_file_index(const char *path) +{ + for (int i = 0; i < IMMFS_MAX_FILES; i++) { + if (files[i].in_use && strcmp(files[i].path, path) == 0) return i; + } + return -1; +} + +static int alloc_file_slot(const char *path) +{ + for (int i = 0; i < IMMFS_MAX_FILES; i++) { + if (!files[i].in_use) { + files[i].in_use = 1; + strncpy(files[i].path, path, IMMFS_PATH_MAX - 1); + files[i].path[IMMFS_PATH_MAX - 1] = '\0'; + files[i].data = NULL; + files[i].size = 0; + files[i].capacity = 0; + return i; + } + } + return -1; +} + +static int alloc_fd_slot(void) +{ + for (int i = 0; i < IMMFS_MAX_FDS; i++) { + if (!fds[i].used) { + fds[i].used = 1; + fds[i].file_idx = -1; + fds[i].offset = 0; + fds[i].flags = 0; + return i; + } + } + return -1; +} + +static void free_file_slot(int idx) +{ + if (files[idx].data) xfree(files[idx].data); + files[idx].data = NULL; + files[idx].size = 0; + files[idx].capacity = 0; + files[idx].in_use = 0; + files[idx].path[0] = '\0'; +} + +int immfs_creat(const char *path, int mode) +{ + (void)mode; + immfs_init(); + if (find_file_index(path) != -1) { + errno = EEXIST; + return -1; + } + int idx = alloc_file_slot(path); + if (idx < 0) { errno = ENOSPC; return -1; } + /* return a descriptor like POSIX creat: open for write */ + int fd = alloc_fd_slot(); + if (fd < 0) { free_file_slot(idx); errno = EMFILE; return -1; } + fds[fd].file_idx = idx; + fds[fd].offset = 0; + fds[fd].flags = O_WRONLY | O_CREAT | O_TRUNC; + return fd; +} + +int immfs_open(const char *path, int flags, int mode) +{ + (void)mode; + immfs_init(); + int idx = find_file_index(path); + if (idx == -1) { + if (flags & O_CREAT) { + idx = alloc_file_slot(path); + if (idx < 0) { errno = ENOSPC; return -1; } + } else { + errno = ENOENT; return -1; + } + } + /* truncation */ + if ((flags & O_TRUNC) && (flags & O_WRONLY || flags & O_RDWR)) { + if (files[idx].data) { xfree(files[idx].data); files[idx].data = NULL; } + files[idx].size = 0; files[idx].capacity = 0; + } + int fd = alloc_fd_slot(); + if (fd < 0) { errno = EMFILE; return -1; } + fds[fd].file_idx = idx; + fds[fd].offset = 0; + fds[fd].flags = flags; + return fd; +} + +int immfs_close(int fd) +{ + immfs_init(); + if (fd < 0 || fd >= IMMFS_MAX_FDS || !fds[fd].used) { errno = EBADF; return -1; } + fds[fd].used = 0; + fds[fd].file_idx = -1; + fds[fd].offset = 0; + fds[fd].flags = 0; + return 0; +} + +int immfs_unlink(const char *path) +{ + immfs_init(); + int idx = find_file_index(path); + if (idx == -1) { errno = ENOENT; return -1; } + /* ensure no FD refers to this file */ + for (int i = 0; i < IMMFS_MAX_FDS; i++) { + if (fds[i].used && fds[i].file_idx == idx) { + errno = EBUSY; return -1; + } + } + free_file_slot(idx); + return 0; +} + +ssize_t immfs_read(int fd, void *buf, size_t count) +{ + immfs_init(); + if (fd < 0 || fd >= IMMFS_MAX_FDS || !fds[fd].used) { errno = EBADF; return -1; } + int idx = fds[fd].file_idx; + if (idx < 0 || !files[idx].in_use) { errno = EBADF; return -1; } + if ((fds[fd].flags & O_WRONLY) && !(fds[fd].flags & O_RDWR)) { errno = EBADF; return -1; } + size_t avail = files[idx].size > fds[fd].offset ? files[idx].size - fds[fd].offset : 0; + size_t toread = count < avail ? count : avail; + if (toread > 0) memcpy(buf, files[idx].data + fds[fd].offset, toread); + fds[fd].offset += toread; + return (ssize_t)toread; +} + +ssize_t immfs_write(int fd, const void *buf, size_t count) +{ + immfs_init(); + if (fd < 0 || fd >= IMMFS_MAX_FDS || !fds[fd].used) { errno = EBADF; return -1; } + int idx = fds[fd].file_idx; + if (idx < 0 || !files[idx].in_use) { errno = EBADF; return -1; } + if ((fds[fd].flags & O_RDONLY) && !(fds[fd].flags & O_RDWR)) { errno = EBADF; return -1; } + size_t need = fds[fd].offset + count; + if (need > files[idx].capacity) { + size_t newcap = files[idx].capacity ? files[idx].capacity : 1024; + while (newcap < need) newcap *= 2; + unsigned char *newdata = xmalloc(newcap); + if (!newdata) { errno = ENOMEM; return -1; } + if (files[idx].data) { + memcpy(newdata, files[idx].data, files[idx].size); + xfree(files[idx].data); + } + files[idx].data = newdata; + files[idx].capacity = newcap; + } + memcpy(files[idx].data + fds[fd].offset, buf, count); + fds[fd].offset += count; + if (fds[fd].offset > files[idx].size) files[idx].size = fds[fd].offset; + return (ssize_t)count; +} diff --git a/projects/sel4test/apps/hello-world/src/immfs.h b/projects/sel4test/apps/hello-world/src/immfs.h new file mode 100644 index 0000000000000000000000000000000000000000..dad2a1d308d1726075dff782ac390eebdda51cf1 --- /dev/null +++ b/projects/sel4test/apps/hello-world/src/immfs.h @@ -0,0 +1,21 @@ +/* Simple in-memory file system (immfs) + * Provides minimal POSIX-like APIs: open, close, creat, unlink, read, write. + * This is intended for tests and demos only. + */ +#pragma once + +#include +#include + +/* open flags (subset) */ +#include + +int immfs_open(const char *path, int flags, int mode); +int immfs_close(int fd); +int immfs_creat(const char *path, int mode); +int immfs_unlink(const char *path); +ssize_t immfs_read(int fd, void *buf, size_t count); +ssize_t immfs_write(int fd, const void *buf, size_t count); + +/* Optional: initialize immfs (called automatically on first use) */ +void immfs_init(void); diff --git a/projects/sel4test/apps/hello-world/src/main.c b/projects/sel4test/apps/hello-world/src/main.c index baef607582c3cfeb8150447dcb9ef67794b933b4..661725c7c29fbada757b368af66ef1ec508be511 100755 --- a/projects/sel4test/apps/hello-world/src/main.c +++ b/projects/sel4test/apps/hello-world/src/main.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include @@ -1075,6 +1075,7 @@ int main(void) { if (!g_root_q_vaddr || !g_sel4_q_vaddr || !g_data_vaddr) { printf("[ERROR] Invalid shared memory addresses!\n"); return -1; + } printf("[userspace] Shared memory communication ready\n"); diff --git a/projects/sel4test/apps/hello-world/src/mem_helpers.c b/projects/sel4test/apps/hello-world/src/mem_helpers.c new file mode 100644 index 0000000000000000000000000000000000000000..37c3dafda131f044979e85229a7df7978a435c00 --- /dev/null +++ b/projects/sel4test/apps/hello-world/src/mem_helpers.c @@ -0,0 +1,240 @@ +/* Implementation of memory helpers used by hello-world. This is intentionally + * minimal and independent of sel4test-tests so the hello-world binary can + * link the helpers directly. */ + +#include "mem_helpers.h" +#include +#include + +/* Simple slab-backed allocator for small allocations and malloc for large ones */ +#include + +#define MAX_SMALL_ALLOC 256 +#define SLOTS_PER_CLASS 1024 + +/* slab classes (must be sorted ascending) */ +static const size_t slab_sizes[] = {16, 32, 64, 128, 256}; +#define NSLAB (sizeof(slab_sizes) / sizeof(slab_sizes[0])) + +/* per-class slab storage: allocate exactly the slot size for each class to avoid waste. + * Storage and bookkeeping are allocated at runtime so the pool can be resized. + */ +static uint8_t *slab_base_ptrs[NSLAB] = { NULL }; +static size_t slab_slots[NSLAB] = { 0 }; +/* per-class free bitmaps */ +static uint32_t *slab_bitmap_ptr[NSLAB] = { NULL }; +static size_t slab_bitmap_words[NSLAB] = { 0 }; +static size_t slab_free_count[NSLAB] = { 0 }; +static int slab_initted = 0; + +/* default slots per class; can be grown at runtime */ +#define DEFAULT_SLOTS_PER_CLASS 1024 + +static void slab_init(void) +{ + if (slab_initted) return; + for (int c = 0; c < (int)NSLAB; c++) { + size_t item_size = slab_sizes[c]; + size_t slots = DEFAULT_SLOTS_PER_CLASS; + /* allocate storage using malloc (can't use xmalloc here) */ + uint8_t *base = malloc(item_size * slots); + if (!base) { + slab_base_ptrs[c] = NULL; + slab_slots[c] = 0; + slab_bitmap_ptr[c] = NULL; + slab_bitmap_words[c] = 0; + slab_free_count[c] = 0; + continue; + } + slab_base_ptrs[c] = base; + slab_slots[c] = slots; + /* bitmap: one bit per slot */ + size_t words = (slots + 31) / 32; + uint32_t *bitmap = calloc(words, sizeof(uint32_t)); + if (!bitmap) { + free(base); + slab_base_ptrs[c] = NULL; + slab_slots[c] = 0; + slab_bitmap_ptr[c] = NULL; + slab_bitmap_words[c] = 0; + slab_free_count[c] = 0; + continue; + } + /* set all valid bits to 1 (free) */ + for (size_t w = 0; w < words; w++) bitmap[w] = 0xFFFFFFFFu; + /* clear bits beyond slots */ + if (slots % 32) { + uint32_t last_mask = (1u << (slots % 32)) - 1u; + bitmap[words - 1] = last_mask; + } + slab_bitmap_ptr[c] = bitmap; + slab_bitmap_words[c] = words; + slab_free_count[c] = slots; + } + slab_initted = 1; +} + +static int slab_class_for_size(size_t size) +{ + for (int i = 0; i < (int)NSLAB; i++) { + if (size <= slab_sizes[i]) return i; + } + return -1; +} + +void xmalloc_init(void *env) +{ + (void)env; + slab_init(); +} + +void *xmalloc(size_t size) +{ + if (size == 0) return NULL; + slab_init(); + + if (size <= MAX_SMALL_ALLOC) { + int cls = slab_class_for_size(size); + if (cls >= 0 && slab_bitmap_ptr[cls] && slab_free_count[cls] > 0) { + /* find first set bit in bitmap */ + uint32_t *bitmap = slab_bitmap_ptr[cls]; + size_t words = slab_bitmap_words[cls]; + for (size_t w = 0; w < words; w++) { + uint32_t word = bitmap[w]; + if (word == 0) continue; + /* find least-significant set bit */ + unsigned bit = (unsigned)__builtin_ctz(word); + size_t idx = w * 32 + bit; + if (idx >= slab_slots[cls]) continue; + /* clear bit */ + bitmap[w] = word & ~(1u << bit); + slab_free_count[cls]--; + size_t item_size = slab_sizes[cls]; + return (void *)(slab_base_ptrs[cls] + idx * item_size); + } + } + /* no free slot in this class; fall through to malloc */ + } + + /* large allocation -> heap */ + return malloc(size); +} + +void xfree(void *ptr) +{ + if (!ptr) return; + slab_init(); + + /* check slab pools */ + for (int c = 0; c < (int)NSLAB; c++) { + uint8_t *base = slab_base_ptrs[c]; + size_t item_size = slab_sizes[c]; + size_t class_block = (size_t)slab_slots[c] * item_size; + if (base && (uint8_t *)ptr >= base && (uint8_t *)ptr < base + class_block) { + size_t offset = (uint8_t *)ptr - base; + size_t idx = offset / item_size; + /* set bit if it was not already free */ + if (slab_bitmap_ptr[c]) { + size_t w = idx / 32; + unsigned b = (unsigned)(idx % 32); + uint32_t mask = 1u << b; + if ((slab_bitmap_ptr[c][w] & mask) == 0) { + /* currently allocated -> mark free */ + slab_bitmap_ptr[c][w] |= mask; + slab_free_count[c]++; + } else { + /* double free or already free: ignore */ + } + } + return; + } + } + + /* otherwise was allocated via malloc */ + free(ptr); +} + +/* tiny minikv implementation */ +#define HW_MINIKV_BUCKETS 64 +typedef struct hw_kv_node { + char *key; + void *value; + struct hw_kv_node *next; +} hw_kv_node_t; +static hw_kv_node_t *hw_buckets[HW_MINIKV_BUCKETS]; +static int hw_minikv_initted = 0; + +static unsigned long hw_hash_str(const char *str) +{ + unsigned long hash = 5381; + int c; + while ((c = (unsigned char)*str++)) + hash = ((hash << 5) + hash) + c; + return hash; +} + +int minikv_init(void *env) +{ + (void)env; + if (hw_minikv_initted) return 0; + for (int i = 0; i < HW_MINIKV_BUCKETS; i++) hw_buckets[i] = NULL; + hw_minikv_initted = 1; + return 0; +} + +int minikv_set(const char *key, void *value) +{ + if (!hw_minikv_initted || !key) return -1; + unsigned long h = hw_hash_str(key) % HW_MINIKV_BUCKETS; + hw_kv_node_t *n = hw_buckets[h]; + while (n) { + if (strcmp(n->key, key) == 0) { + n->value = value; + return 0; + } + n = n->next; + } + hw_kv_node_t *node = xmalloc(sizeof(*node)); + if (!node) return -1; + size_t klen = strlen(key) + 1; + node->key = xmalloc(klen); + if (!node->key) { xfree(node); return -1; } + memcpy(node->key, key, klen); + node->value = value; + node->next = hw_buckets[h]; + hw_buckets[h] = node; + return 0; +} + +void *minikv_get(const char *key) +{ + if (!hw_minikv_initted || !key) return NULL; + unsigned long h = hw_hash_str(key) % HW_MINIKV_BUCKETS; + hw_kv_node_t *n = hw_buckets[h]; + while (n) { + if (strcmp(n->key, key) == 0) return n->value; + n = n->next; + } + return NULL; +} + +int minikv_delete(const char *key) +{ + if (!hw_minikv_initted || !key) return -1; + unsigned long h = hw_hash_str(key) % HW_MINIKV_BUCKETS; + hw_kv_node_t *n = hw_buckets[h]; + hw_kv_node_t *prev = NULL; + while (n) { + if (strcmp(n->key, key) == 0) { + if (prev) prev->next = n->next; else hw_buckets[h] = n->next; + xfree(n->key); + xfree(n); + return 0; + } + prev = n; + n = n->next; + } + return -1; + + } + diff --git a/projects/sel4test/apps/hello-world/src/mem_helpers.h b/projects/sel4test/apps/hello-world/src/mem_helpers.h new file mode 100644 index 0000000000000000000000000000000000000000..36d3bf2892b0be0d1368987b786f38c24c02801a --- /dev/null +++ b/projects/sel4test/apps/hello-world/src/mem_helpers.h @@ -0,0 +1,15 @@ +/* Simple memory helpers for hello-world: xmalloc/xfree and a tiny minikv + * Placed in a separate file so the implementations don't live in main.c. + */ +#pragma once + +#include + +void xmalloc_init(void *env); +void *xmalloc(size_t size); +void xfree(void *ptr); + +int minikv_init(void *env); +int minikv_set(const char *key, void *value); +void *minikv_get(const char *key); +int minikv_delete(const char *key); diff --git a/projects/sel4test/apps/hello-world/src/mem_test.c b/projects/sel4test/apps/hello-world/src/mem_test.c new file mode 100644 index 0000000000000000000000000000000000000000..af8960d25e94f12407cbf2783ddd64ab7c48ba04 --- /dev/null +++ b/projects/sel4test/apps/hello-world/src/mem_test.c @@ -0,0 +1,391 @@ +/* mem_test.c - exercises xmalloc/xfree and minikv from mem_helpers.c + * Keeps test logic out of main.c to minimize changes to main. */ + +#include +#include "mem_helpers.h" +#include "mem_test.h" +#include "immfs.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* forward declarations for test sections */ +static void basic_mem_kv_test(void); +static void stress_test(void); +static void fs_test(void); +static void pthread_test(void); +static void sel4_native_test(void); + +/* sel4 native threaded worker */ +#if defined(CONFIG_KERNEL_MCS) || 1 +/* small defaults used for bootstrap pool */ +#define SEL4_TEST_ALLOC_POOL_PAGES 40 +static char sel4_alloc_pool[SEL4_TEST_ALLOC_POOL_PAGES * PAGE_SIZE_4K]; + +static volatile int sel4_thread_done[8] = {0}; + +static void sel4_worker(void *arg0, void *arg1, void *ipc) { + intptr_t id = (intptr_t)arg0; + /* avoid any I/O from worker; just mark done */ + sel4_thread_done[id] = 1; + /* keep thread alive to avoid return-related faults */ + for (;;) { + seL4_Yield(); + } +} + +static void sel4_native_test(void) +{ + // const char *env = getenv("ENABLE_SEL4_THREAD_TEST"); + // if (!env) { + // printf("\n=== sel4 native thread test skipped (set ENABLE_SEL4_THREAD_TEST=1 to enable) ===\n"); + // return; + // } + printf("\n=== sel4 native thread test (enabled) ===\n"); + + /* init simple from bootinfo */ + simple_t simple; + printf("[mem_test] before platsupport_get_bootinfo()\n"); fflush(stdout); + seL4_BootInfo *bi = platsupport_get_bootinfo(); + printf("[mem_test] after platsupport_get_bootinfo(): %p\n", (void*)bi); fflush(stdout); + if (!bi) { printf("no bootinfo, skipping sel4 native test\n"); fflush(stdout); return; } + else { printf("get bootinfo success.\n"); fflush(stdout); } + simple_default_init_bootinfo(&simple, bi); + + /* bootstrap allocman and vka */ + printf("[mem_test] before bootstrap_use_current_simple()\n"); fflush(stdout); + allocman_t *allocman = bootstrap_use_current_simple(&simple, sizeof(sel4_alloc_pool), sel4_alloc_pool); + printf("[mem_test] after bootstrap_use_current_simple(): %p\n", (void*)allocman); fflush(stdout); + if (!allocman) { printf("allocman bootstrap failed\n"); fflush(stdout); return; } + vka_t vka; + allocman_make_vka(&vka, allocman); + printf("[mem_test] after allocman_make_vka()\n"); fflush(stdout); + + /* bootstrap vspace */ + sel4utils_alloc_data_t alloc_data; + vspace_t vspace; + seL4_CPtr pd = simple_get_pd(&simple); + printf("[mem_test] before second platsupport_get_bootinfo()\n"); fflush(stdout); + seL4_BootInfo *bootinfo = platsupport_get_bootinfo(); + printf("[mem_test] after second platsupport_get_bootinfo(): %p\n", (void*)bootinfo); fflush(stdout); + printf("[mem_test] before sel4utils_bootstrap_vspace_with_bootinfo_leaky()\n"); fflush(stdout); + int vres = sel4utils_bootstrap_vspace_with_bootinfo_leaky(&vspace, &alloc_data, pd, &vka, bootinfo); + printf("[mem_test] after sel4utils_bootstrap_vspace_with_bootinfo_leaky(): %d\n", vres); fflush(stdout); + if (vres) { + printf("bootstrap vspace failed\n"); fflush(stdout); + return; + } + + /* configure and start threads */ + const int nthreads = 4; + sel4utils_thread_t threads[nthreads]; + /* create a fault endpoint to catch thread faults */ + vka_object_t fault_ep; + if (vka_alloc_endpoint(&vka, &fault_ep) != 0) { + printf("[mem_test] failed to allocate fault endpoint\n"); fflush(stdout); + return; + } + for (int i = 0; i < nthreads; i++) { + /* Use sel4utils default config which sets reply cap, IPC buffer, priority, and fault EP */ + seL4_Word guard_data = api_make_guard_skip_word(seL4_WordBits - simple_get_cnode_size_bits(&simple)); + sel4utils_thread_config_t config = thread_config_default(&simple, + simple_get_cnode(&simple), + guard_data, + fault_ep.cptr, + 100); + if (config_set(CONFIG_KERNEL_MCS)) { + config.sched_params = sched_params_round_robin(config.sched_params, &simple, 0, 1000); + } + if (sel4utils_configure_thread_config(&vka, &vspace, &vspace, config, &threads[i]) != 0) { + printf("configure thread %d failed\n", i); + return; + } + printf("[mem_test] configured thread %d\n", i); fflush(stdout); + sel4_thread_done[i] = 0; + if (sel4utils_start_thread(&threads[i], sel4_worker, (void*)(intptr_t)i, NULL, 0) != 0) { + printf("start thread %d failed\n", i); + return; + } + int r = seL4_TCB_Resume(threads[i].tcb.cptr); + printf("[mem_test] started thread %d, Resume rc=%d\n", i, r); fflush(stdout); + } + + /* wait for completion */ + int all = 0; + printf("[mem_test] waiting for threads to finish...\n"); fflush(stdout); + while (!all) { + all = 1; + for (int i = 0; i < nthreads; i++) if (!sel4_thread_done[i]) { all = 0; break; } + if (!all) { + /* poll for any faults from worker threads */ + seL4_Word badge = 0; + seL4_MessageInfo_t tag = seL4_Poll(fault_ep.cptr, &badge); + if (seL4_MessageInfo_get_length(tag) != 0) { + /* A fault occurred; print some info */ + sel4utils_print_fault_message(tag, "mem_test worker"); + /* break to avoid tight loop */ + } + seL4_Yield(); + } + } + printf("[mem_test] threads finished\n"); fflush(stdout); + printf("sel4 native threads finished\n"); + + /* cleanup threads */ + for (int i = 0; i < nthreads; i++) sel4utils_clean_up_thread(&vka, &vspace, &threads[i]); + +} +#else +static void sel4_native_test(void) { printf("sel4 native test not supported in this build\n"); } +#endif + +void run_mem_tests(void) +{ + basic_mem_kv_test(); + stress_test(); + // sel4_native_test(); + // pthread_test(); + fs_test(); +} + +static void basic_mem_kv_test(void) +{ + /* ----- Basic xmalloc/xfree and minikv smoke tests ----- */ + printf("\n=== Basic mem/kv tests ===\n"); + xmalloc_init(NULL); + + void *s1 = xmalloc(32); + if (s1) { + printf("xmalloc small (32) OK: %p\n", s1); + xfree(s1); + printf("xfree small OK\n"); + } else { + printf("xmalloc small failed\n"); + } + + void *s2 = xmalloc(4096); /* larger than slab classes -> heap */ + if (s2) { + printf("xmalloc large (4096) returned %p\n", s2); + xfree(s2); + } else { + printf("xmalloc large returned NULL (expected if no heap)\n"); + } + + if (minikv_init(NULL) == 0) { + const char *k = "hello"; + void *val = (void *)0x1234; + if (minikv_set(k, val) == 0) { + printf("minikv set OK\n"); + void *g = minikv_get(k); + printf("minikv get: %p\n", g); + if (minikv_delete(k) == 0) printf("minikv delete OK\n"); else printf("minikv delete failed\n"); + } else { + printf("minikv set failed\n"); + } + } else { + printf("minikv_init failed\n"); + } +} + +static void stress_test(void) +{ + /* ----- Stress tests for slab allocator and minikv ----- */ + printf("\n=== Stress tests: xmalloc and minikv stress ===\n"); + xmalloc_init(NULL); + + const size_t ALLOC_STRESS = 6000; /* try to allocate more than default slab pool */ + void **ptrs = malloc(sizeof(void*) * ALLOC_STRESS); + if (!ptrs) { + printf("alloc stress: cannot malloc ptr array\n"); + } else { + size_t success = 0; + for (size_t i = 0; i < ALLOC_STRESS; i++) { + /* alternate sizes: exact class sizes and off-by-one */ + size_t sz; + switch (i % 5) { + case 0: sz = 16; break; + case 1: sz = 17; break; + case 2: sz = 64; break; + case 3: sz = 65; break; + default: sz = 200; break; + } + ptrs[i] = xmalloc(sz); + if (ptrs[i]) success++; + } + printf("alloc stress: requested=%zu succeeded=%zu\n", ALLOC_STRESS, success); + + /* free half of them, then allocate again to test reuse */ + for (size_t i = 0; i < ALLOC_STRESS; i += 2) { + xfree(ptrs[i]); + ptrs[i] = NULL; + } + size_t success2 = 0; + for (size_t i = 0; i < ALLOC_STRESS; i++) { + if (!ptrs[i]) { + ptrs[i] = xmalloc(32); + if (ptrs[i]) success2++; + } + } + printf("alloc reuse: reallocated=%zu\n", success2); + + /* double-free some pointers to ensure allocator tolerates it */ + if (ALLOC_STRESS >= 4) { + xfree(ptrs[1]); + xfree(ptrs[1]); /* double free should be tolerated (ignored) */ + printf("double-free test executed (no crash expected)\n"); + } + + for (size_t i = 0; i < ALLOC_STRESS; i++) if (ptrs[i]) xfree(ptrs[i]); + free(ptrs); + } + + /* minikv stress: many inserts, updates, deletes */ + if (minikv_init(NULL) == 0) { + const int N = 2000; + char key[48]; + int errors = 0; + for (int i = 0; i < N; i++) { + snprintf(key, sizeof(key), "key-%04d", i); + if (minikv_set(key, (void*)(intptr_t)i) != 0) { errors++; if (i < 10) printf("minikv_set failed for %s\n", key); } + } + printf("minikv stress: inserted=%d errors=%d\n", N, errors); + /* verify some random entries and update them */ + int mismatches = 0; + for (int i = 0; i < N; i += 37) { + snprintf(key, sizeof(key), "key-%04d", i); + void *v = minikv_get(key); + if ((intptr_t)v != i) { mismatches++; if (mismatches < 5) printf("minikv mismatch for %s got=%p expected=%d\n", key, v, i); } + /* update */ + if (minikv_set(key, (void*)(intptr_t)(i + 1)) != 0) printf("minikv update failed for %s\n", key); + void *v2 = minikv_get(key); + if ((intptr_t)v2 != i + 1) { printf("minikv update mismatch for %s got=%p expected=%d\n", key, v2, i+1); } + } + printf("minikv verify: mismatches=%d\n", mismatches); + + /* delete half */ + int dels = 0; + for (int i = 0; i < N; i += 2) { + snprintf(key, sizeof(key), "key-%04d", i); + if (minikv_delete(key) == 0) dels++; else if (i < 10) printf("minikv_delete failed for %s\n", key); + } + printf("minikv deleted=%d\n", dels); + + /* check non-existent */ + if (minikv_get("no-such-key") == NULL) printf("minikv correctly returns NULL for missing key\n"); + if (minikv_set(NULL, (void*)1) != 0) printf("minikv rejects NULL key (as expected)\n"); + + } else { + printf("minikv_init failed for stress tests\n"); + } +} + +/* pthread test: optional, only when pthreads are available */ +#if defined(__has_include) +# if __has_include() +# include +# define HAVE_PTHREAD 1 +# endif +#endif + +#if defined(HAVE_PTHREAD) +static void *pthread_worker(void *arg) +{ + intptr_t id = (intptr_t)arg; + for (int i = 0; i < 1000; i++) { + void *p = xmalloc(32 + (i & 31)); + if (p) xfree(p); + if ((i & 127) == 0) { + /* do small kv operations locally to avoid global races */ + char k[32]; + snprintf(k, sizeof(k), "th%02ld-%04d", (long)id, i); + minikv_set(k, (void*)id); + minikv_get(k); + minikv_delete(k); + } + } + return NULL; +} + +static void pthread_test(void) +{ + /* Only run pthread test when explicitly enabled via env var. */ + const char *env = getenv("ENABLE_PTHREAD_TEST"); + if (!env) { + printf("\n=== pthread test skipped (set ENABLE_PTHREAD_TEST=1 to enable) ===\n"); + return; + } + printf("\n=== pthread concurrency test (enabled) ===\n"); + xmalloc_init(NULL); + const int nthreads = 4; + pthread_t th[nthreads]; + for (int i = 0; i < nthreads; i++) { + int rc = pthread_create(&th[i], NULL, pthread_worker, (void*)(intptr_t)i); + if (rc != 0) { + printf("pthread_create failed (rc=%d) -- skipping pthread test\n", rc); + /* join any threads that were created */ + for (int j = 0; j < i; j++) pthread_join(th[j], NULL); + return; + } + } + for (int i = 0; i < nthreads; i++) pthread_join(th[i], NULL); + printf("pthread test finished\n"); +} +#else +static void pthread_test(void) +{ + printf("\n=== pthread test skipped (no pthreads available) ===\n"); +} +#endif + +static void fs_test(void) +{ + /* ----- Test immfs (in-memory filesystem) ----- */ + printf("\n=== immfs tests ===\n"); + immfs_init(); + const char *path = "/tmp/hello"; + int fd = immfs_creat(path, 0644); + if (fd < 0) { + printf("immfs_creat failed: %d\n", errno); + } else { + const char *msg = "hello immfs"; + ssize_t w = immfs_write(fd, msg, strlen(msg)); + if (w != (ssize_t)strlen(msg)) { + printf("immfs_write failed wrote=%zd errno=%d\n", w, errno); + } else { + printf("immfs_write OK wrote=%zd\n", w); + } + immfs_close(fd); + + int rfd = immfs_open(path, O_RDONLY, 0); + if (rfd < 0) { + printf("immfs_open for read failed: %d\n", errno); + } else { + char buf[64]; + ssize_t r = immfs_read(rfd, buf, sizeof(buf)); + if (r > 0) { + buf[r] = '\0'; + printf("immfs_read OK read=%zd data=\"%s\"\n", r, buf); + } else { + printf("immfs_read failed r=%zd errno=%d\n", r, errno); + } + immfs_close(rfd); + } + + if (immfs_unlink(path) == 0) { + printf("immfs_unlink OK\n"); + } else { + printf("immfs_unlink failed errno=%d\n", errno); + } + } +} diff --git a/projects/sel4test/apps/hello-world/src/mem_test.h b/projects/sel4test/apps/hello-world/src/mem_test.h new file mode 100644 index 0000000000000000000000000000000000000000..2ee043f71b0191ae99943f112ed08f8004d3980b --- /dev/null +++ b/projects/sel4test/apps/hello-world/src/mem_test.h @@ -0,0 +1,4 @@ +/* mem_test.h - small harness to exercise mem_helpers in hello-world */ +#pragma once + +void run_mem_tests(void); diff --git a/projects/sel4test/apps/monkey-mnemosyne/CMakeLists.txt b/projects/sel4test/apps/monkey-mnemosyne/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..e3fcd34ca6b10e46e7b37c40036f864fbf957e78 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/CMakeLists.txt @@ -0,0 +1,103 @@ +cmake_minimum_required(VERSION 3.16.0) + +project(monkey-mnemosyne) + +find_package(musllibc REQUIRED) +find_package(util_libs REQUIRED) +find_package(seL4_libs REQUIRED) + +# Setup build environment +musllibc_setup_build_environment_with_sel4runtime() +sel4_import_libsel4() +util_libs_import_libraries() +sel4_libs_import_libraries() + +# ---------------------------------------------------------------------------- +# Sources +# +# All C / C++ sources under src/ form the reusable static library +# `monkey-mnemosyne-lib`. The sole exception is `lab-main.cc`, which +# carries `int main()` and the lab-specific demo logic; it stays out of +# the library and is linked only into the standalone test executable. +# ---------------------------------------------------------------------------- +file(GLOB_RECURSE lib_srcs src/*.c src/*.cc) +list(REMOVE_ITEM lib_srcs ${CMAKE_CURRENT_SOURCE_DIR}/src/lab-main.cc) +list(SORT lib_srcs) +list(REMOVE_DUPLICATES lib_srcs) + +# ---------------------------------------------------------------------------- +# C++ flags shared by the library and the standalone executable. +# Hoisted out of the original target_compile_options so that both targets +# stay binary-compatible (same RTTI/EH/ABI flags). +# ---------------------------------------------------------------------------- +set(MNEMOSYNE_CXX_FLAGS + -Wall + -std=c++20 + -g + -nostartfiles + -fno-pic -fno-pie -no-pie + -fno-rtti + -fno-stack-protector + -fno-threadsafe-statics + -fno-exceptions + -fno-use-cxa-atexit + -fno-sized-deallocation + -Wno-error=unused-function + -fdiagnostics-color=always +) + +# ---------------------------------------------------------------------------- +# Reusable static library: monkey-mnemosyne-lib +# ---------------------------------------------------------------------------- +add_library(monkey-mnemosyne-lib STATIC EXCLUDE_FROM_ALL ${lib_srcs}) + +target_include_directories(monkey-mnemosyne-lib + PUBLIC + # Pure-C facade header for downstream apps. + ${CMAKE_CURRENT_SOURCE_DIR}/include + PRIVATE + # Internal C++ sources. + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/src +) + +target_link_libraries(monkey-mnemosyne-lib + PUBLIC + sel4_autoconf + muslc + sel4 + sel4runtime + sel4utils + sel4platsupport + sel4muslcsys + sel4allocman + PRIVATE + utils +) + +# Apply the C++ flags to library sources only (they are .cc). +target_compile_options(monkey-mnemosyne-lib PRIVATE + $<$:${MNEMOSYNE_CXX_FLAGS}> +) +target_compile_options(monkey-mnemosyne-lib PRIVATE -g) +set_property(TARGET monkey-mnemosyne-lib PROPERTY C_STANDARD 99) + +# ---------------------------------------------------------------------------- +# Standalone executable: kept for our own testing (lab-main.cc demo). +# This is what `Sel4testApp=monkey-mnemosyne` builds; integration builds +# that link the library into another rootserver simply do not declare +# this target as the rootserver. +# ---------------------------------------------------------------------------- +add_executable(monkey-mnemosyne EXCLUDE_FROM_ALL src/lab-main.cc) + +target_link_libraries(monkey-mnemosyne PRIVATE monkey-mnemosyne-lib) + +target_compile_options(monkey-mnemosyne PRIVATE + $<$:${MNEMOSYNE_CXX_FLAGS}> +) +target_compile_options(monkey-mnemosyne PRIVATE -Werror -g) +set_property(TARGET monkey-mnemosyne PROPERTY C_STANDARD 99) + +# Set this image as the rootserver +include(rootserver) +DeclareRootserver(monkey-mnemosyne) diff --git a/projects/sel4test/apps/monkey-mnemosyne/README.md b/projects/sel4test/apps/monkey-mnemosyne/README.md new file mode 100644 index 0000000000000000000000000000000000000000..94d81979a674c03831d13e55f08355aa18bca1c5 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/README.md @@ -0,0 +1 @@ +Although this folder and app is called monkey-mnemosyne, it actually acts as a special version of monkey lab. diff --git a/projects/sel4test/apps/monkey-mnemosyne/include/mnemosyne_api.h b/projects/sel4test/apps/monkey-mnemosyne/include/mnemosyne_api.h new file mode 100644 index 0000000000000000000000000000000000000000..78813957efd7ed7ee04caa1e01b6ae439fc3e2d6 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/include/mnemosyne_api.h @@ -0,0 +1,261 @@ +/** + * @file mnemosyne_api.h + * @brief Pure-C facade for monkey-mnemosyne, mirroring the apps/front + * (network proxy) API surface for unified upper-layer integration. + * + * Design intent + * ------------- + * The integration spec asks monkey-mnemosyne to expose a small set of + * functions whose *shape* matches the proxy frontend's + * frontend_engine_init / frontend_sess_new / + * frontend_sess_connect_by_addrstr_devid / frontend_sess_send / + * frontend_sess_recv / frontend_engine_run_hyperamp_once / + * frontend_sess_close. The semantics underneath remain the existing + * monkey-mnemosyne client (Protocol2Connection over HyperAMP), with a + * single shared 4 KiB block as the data carrier. + * + * Compared with apps/front the only signature differences are: + * - functions are prefixed `mnemosyne_` so they can coexist with + * `frontend_*` in the same translation unit; + * - there is no `engine` handle; the engine state is global because + * the underlying HyperAmpBridge is itself a singleton. + * + * Linkage + * ------- + * This header is C-clean (extern "C", no C++ types, opaque session + * handle). Upper-layer C code only needs: + * + * #include + * ... + link against monkey-mnemosyne-lib + * + * The implementation is C++ but the ABI exported is plain C. + * + * Concurrency + * ----------- + * The underlying HyperAmpBridge is a singleton; only ONE *connected* + * session may exist at a time. Calling mnemosyne_sess_new() twice + * without closing the first session is undefined behaviour. + * + * Roles (alloc vs ref) + * -------------------- + * monkey-mnemosyne shares a single 4 KiB page between two endpoints: + * - the "alloc" endpoint : calls tryAlloc to create the page; + * - the "ref" endpoint : calls refBlock to attach to it. + * The role is fixed at engine init time via `role` in + * mnemosyne_engine_init(). Block id and access key are currently + * predictable (see TODO in mnemosyne_api.cc). + * + * IPC-MR ordering — IMPORTANT + * --------------------------- + * mnemosyne_engine_init() reads the CH2 virtual addresses from the IPC + * message registers (seL4_GetMR(8/9/10), set up by the kernel boot + * loader). The IPC buffer is volatile across syscalls, so the call + * MUST happen BEFORE the host program issues any seL4 syscall. In + * practice that means it should be the very first thing your `main()` + * does, exactly as apps/hyperamp-server/src/main.c reads MR(2..7) at + * the top. + * + * gongty [at] tongji [dot] edu [dot] cn + */ + +#ifndef MNEMOSYNE_API_H +#define MNEMOSYNE_API_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ====================================================================== */ +/* Constants */ +/* ====================================================================== */ + +/** Role: this endpoint allocates the shared block (issues tryAlloc). */ +#define MNEMOSYNE_ROLE_ALLOC 1 + +/** Role: this endpoint references an already-allocated block. */ +#define MNEMOSYNE_ROLE_REF 2 + +/** + * HyperAMP channel id. CH2 is reserved for monkey-mnemosyne by + * HYPERAMP_MULTI_CHANNEL_DESIGN.md. Pass 0 to mnemosyne_engine_init() + * to mean "default" which resolves to CH2. + */ +#define MNEMOSYNE_CHANNEL_DEFAULT 2 + +/** + * Sentinel for "no specific NIC selection on backend". Same numeric + * value as DEV_ID_AUTO_HANDOVER used throughout apps/front. + */ +#define MNEMOSYNE_DEV_AUTO 0xFF + +/** The data carrier is a single 4 KiB page; send/recv length must fit. */ +#define MNEMOSYNE_BLOCK_SIZE 4096 + +/** + * Transport protocol values, kept binary-compatible with apps/front's + * SessTranProto enum (SESS_UDP_PROTO=0, SESS_TCP_PROTO=1). monkey- + * mnemosyne itself only uses TCP at the moment but the parameter is + * accepted for API symmetry with frontend_sess_connect_by_addrstr_devid. + */ +#define MNEMOSYNE_PROTO_UDP 0 +#define MNEMOSYNE_PROTO_TCP 1 + +/* ====================================================================== */ +/* Opaque types */ +/* ====================================================================== */ + +typedef struct mnemosyne_session_s mnemosyne_session_t; + +/* ====================================================================== */ +/* Engine */ +/* ====================================================================== */ + +/** + * Initialise the mnemosyne engine. Counterpart of frontend_engine_init(). + * + * - Initialises the ADL allocator (idempotent). + * - Reads the CH2 shared-memory virtual addresses from the IPC message + * registers (seL4_GetMR(8/9/10)). See "IPC-MR ordering" in the file + * header — this must happen before any other syscall. + * - Initialises the HyperAMP bridge on the requested channel. + * - Stores the server endpoint, auth key and role for use by sessions + * created via mnemosyne_sess_new(). + * + * Idempotent: subsequent calls return 0 without reinitialising. + * + * @param server_ip IPv4 dotted string (e.g. "10.0.0.5"). Copied + * internally; may be freed by the caller afterwards. + * @param server_port Server TCP port (host byte order). + * @param auth_key UUID-style string (e.g. lab-main App1Key). Copied + * internally. + * @param channel_id HyperAMP channel: 0 = default (= MNEMOSYNE_CHANNEL_ + * DEFAULT = CH2), 2 = CH2 explicitly, 1 = CH1 (debug + * / experimental only). + * @param role MNEMOSYNE_ROLE_ALLOC or MNEMOSYNE_ROLE_REF. + * + * @return 0 on success; <0 on error. + */ +int mnemosyne_engine_init(const char *server_ip, + uint16_t server_port, + const char *auth_key, + uint8_t channel_id, + uint8_t role); + +/** + * Single-shot scheduler hook, mirrors frontend_engine_run_hyperamp_once(). + * + * monkey-mnemosyne does not need an engine event loop: every send/recv + * call drives the HyperAMP queue itself. This function is provided for + * API symmetry only; it is intentionally a no-op so that integrators can + * write a unified main loop: + * + * while (running) { + * frontend_engine_run_hyperamp_once(); + * mnemosyne_engine_run_hyperamp_once(); + * ... pull / push session data ... + * } + */ +void mnemosyne_engine_run_hyperamp_once(void); + +/* ====================================================================== */ +/* Session */ +/* ====================================================================== */ + +/** + * Allocate a new session object. Counterpart of frontend_sess_new(). + * + * The returned handle is owned by the caller and must be released with + * mnemosyne_sess_close(). + * + * @return Session handle on success; NULL on allocation failure or if + * mnemosyne_engine_init() was not called yet. + */ +mnemosyne_session_t *mnemosyne_sess_new(void); + +/** + * Bind the session to a remote endpoint and complete the mnemosyne + * handshake (TCP connect → HelloMode::CLIENT, protocol 2 → auth). + * + * Counterpart of frontend_sess_connect_by_addrstr_devid(). Signature is + * deliberately a one-to-one mirror so that integrators can swap between + * the two with mechanical regularity: + * + * int frontend_sess_connect_by_addrstr_devid( + * struct FrontendSession *sess, int proto, + * const char *addr_str, uint16_t dev_id); + * + * int mnemosyne_sess_connect_by_addrstr_devid( + * mnemosyne_session_t *sess, int proto, + * const char *addr_str, uint16_t dev_id); + * + * If `role == MNEMOSYNE_ROLE_ALLOC` (set at engine init time) the + * session also issues a tryAlloc to create the shared 4 KiB block; + * if `role == MNEMOSYNE_ROLE_REF` the session issues refBlock against + * the predictable access key. + * + * @param sess Session created by mnemosyne_sess_new(). + * @param proto MNEMOSYNE_PROTO_TCP (1) or MNEMOSYNE_PROTO_UDP (0). + * monkey-mnemosyne only uses TCP at present; UDP is + * accepted at the API surface but rejected internally. + * @param addr_str "IP:PORT" string, e.g. "10.0.0.5:10100". Same format + * as front's API. May be NULL/empty to reuse the IP and + * port given to mnemosyne_engine_init(). + * @param dev_id Backend NIC selector. MNEMOSYNE_DEV_AUTO (0xFF) lets + * the backend pick; pass an explicit id for multi-NIC + * deployments. Stored in SessIPv4Params.device_selection + * and forwarded to the backend verbatim. + * + * @return 0 on success; <0 on protocol / network error. + */ +int mnemosyne_sess_connect_by_addrstr_devid(mnemosyne_session_t *sess, + int proto, + const char *addr_str, + uint16_t dev_id); + +/** + * Write up to MNEMOSYNE_BLOCK_SIZE bytes into the shared 4 KiB block. + * + * The buffer is copied into an internal staging page (zero-padded if + * `size < MNEMOSYNE_BLOCK_SIZE`) and then writeBlock is issued to the + * server. + * + * @return Number of bytes consumed from `data` (== `size` on success); + * <0 on error (e.g. size > MNEMOSYNE_BLOCK_SIZE, no session). + */ +int mnemosyne_sess_send(mnemosyne_session_t *sess, + const void *data, uint32_t size); + +/** + * Read the current contents of the shared 4 KiB block and copy up to + * `size` bytes into `buf`. + * + * This is a single readBlock (not a polling-until-changed loop); the + * upper-layer application is expected to call mnemosyne_sess_recv() + * repeatedly inside its own loop and detect updates itself, exactly as + * lab-main.cc::waitDataUpdate() does. + * + * @return Number of bytes copied into `buf` (== min(size, 4096) on + * success); <0 on error. + */ +int mnemosyne_sess_recv(mnemosyne_session_t *sess, + void *buf, uint32_t size); + +/** + * Close the session and release all resources. + * + * If this endpoint holds a reference to the shared block it is unref'd + * before the underlying TCP connection is torn down. After this call + * `sess` is invalid and must not be used. + * + * Safe to call with NULL. + */ +void mnemosyne_sess_close(mnemosyne_session_t *sess); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* MNEMOSYNE_API_H */ diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/Allocator.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/Allocator.h new file mode 100644 index 0000000000000000000000000000000000000000..39f96a3a963c94ce7a68d9677473e022aa38287f --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/Allocator.h @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + * 分配器。 + * + * 创建于 2023年7月2日 上海市嘉定区安亭镇 + */ + +// forked from yros stdlib. +// github.com/FlowerBlackG/YurongOS + +// modified for Amkos + +#pragma once + +#include "./sys/types.h" + +namespace adl { struct Allocator; }; + + +// DO NOT call this outside adl::Allocator! +inline void* operator new(adl::size_t, adl::Allocator* addr) { + return addr; +} + + +namespace adl { + + +struct Allocator { +public: + typedef void* (* AllocFunc) (size_t, void*); + typedef void (* FreeFunc) (void*, size_t, void*); + +protected: + void* data = nullptr; + AllocFunc _alloc = nullptr; + FreeFunc _free = nullptr; + + struct Header { + size_t size; + size_t count; + }; + + +public: + + template + inline T* allocNoConstruct(size_t count = 1, size_t objSize = sizeof(T)) { + if (count == 0) + return nullptr; + + uintptr_t addr = (uintptr_t) this->_alloc(objSize * count + sizeof(Header), data); + + if (!addr) + return nullptr; + + auto header = (Header*) addr; + header->size = objSize; + header->count = count; + + auto objs = (T*) (addr + sizeof(Header)); + + return objs; + } + + + template + inline T* alloc(size_t count = 1, bool construct = true, size_t objSize = sizeof(T)) { + + auto objs = allocNoConstruct(count, objSize); + if (!objs) + return nullptr; + + if (construct) { + for (size_t i = 0; i < count; i++) + // redirect to fake allocation that doesn't give any extra memory. + new ((Allocator*) (objs + i)) T; + } + + return objs; + } + + + inline void free(void* addr) { + auto header = (Header*) (uintptr_t(addr) - sizeof(Header)); + this->_free(header, header->count * header->size + sizeof(Header), data); + } + + + template + inline void free(T* addr, bool destruct = true) { + auto header = (Header*) (uintptr_t(addr) - sizeof(Header)); + + if (destruct) { + size_t i = header->count - 1; + do { + uintptr_t objAddr = uintptr_t(addr) + i * header->size; + ((T*) objAddr)->~T(); + } while (i--); + } + + this->_free(header, header->count * header->size + sizeof(Header), data); + } + + + inline bool ready() { return !!_alloc && !!_free; } + inline bool notReady() { return !ready(); } + + inline void bindAlloc(AllocFunc func) { this->_alloc = func; }; + inline void bindFree(FreeFunc func) { this->_free = func; }; + inline void bindData(void* data) { this->data = data; }; + + + void init(AllocFunc alloc, FreeFunc free, void* data = nullptr) { + bindAlloc(alloc); + bindFree(free); + bindData(data); + } + + struct InitParams { + AllocFunc alloc; + FreeFunc free; + void* data = nullptr; + }; + + Allocator() = default; + + Allocator(AllocFunc alloc, FreeFunc free, void* data = nullptr) { init(alloc, free, data); } + Allocator(const InitParams& x) { init(x.alloc, x.free, x.data); } + + inline void init(InitParams x) { init(x.alloc, x.free, x.data); } + + inline Allocator operator = (InitParams x) { init(x); return *this; } + +}; + + + + +} // namespace adl + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/AsciiChar.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/AsciiChar.h new file mode 100644 index 0000000000000000000000000000000000000000..3bf10581f6ceaa5cf58ab27df962d869447d8c49 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/AsciiChar.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + * ASCII 特殊字符定义。 + * 创建于 2022年7月12日。 + * + * 参考: + * https://www.asciitable.com/ + */ + + +// forked from yros stdlib. +// github.com/FlowerBlackG/YurongOS + +#pragma once +#include "./sys/types.h" + +namespace adl { + +class AsciiChar { +public: + static const uint8_t NUL = 0; + static const uint8_t SOH = 1; + static const uint8_t STX = 2; + static const uint8_t ETX = 3; + static const uint8_t EOT = 4; + static const uint8_t ENQ = 5; + static const uint8_t ACK = 6; + static const uint8_t BEL = 7; + static const uint8_t BS = 8; + static const uint8_t TAB = 9; + + /** NL line feed, new line (n). */ + static const uint8_t LF = 10; + static const uint8_t VT = 11; + static const uint8_t FF = 12; + + /** carriage return (r). */ + static const uint8_t CR = 13; + static const uint8_t SO = 14; + static const uint8_t SI = 15; + static const uint8_t DLE = 16; + static const uint8_t DC1 = 17; + static const uint8_t DC2 = 18; + static const uint8_t DC3 = 19; + static const uint8_t DC4 = 20; + static const uint8_t NAK = 21; + static const uint8_t SYN = 22; + static const uint8_t ETB = 23; + static const uint8_t CAN = 24; + static const uint8_t EM = 25; + static const uint8_t SUB = 26; + static const uint8_t ESC = 27; + static const uint8_t FS = 28; + static const uint8_t GS = 29; + static const uint8_t RS = 30; + static const uint8_t US = 31; + + /** 可见字符起始位置。含空格。 */ + static const uint8_t START_OF_VISIBLE_CHARS = 32; + static const uint8_t END_OF_VISIBLE_CHARS = 126; +}; + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/TString.cc b/projects/sel4test/apps/monkey-mnemosyne/src/adl/TString.cc new file mode 100644 index 0000000000000000000000000000000000000000..97a334b693c05e6b5116604fbe2bd8fe06e5b61c --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/TString.cc @@ -0,0 +1,826 @@ +/* 2051565 GTY Tongji CS */ +// Ported from TJ OOP HW. Modified for Amkos. + +#define _CRT_SECURE_NO_WARNINGS + + +#include + +#include +#include +#include + +#include + +using namespace std; + + +static void __die() { + printf("[FATAL] Panic. "); + printf(__FILE__); + printf("\n"); + while (1) + ; +} + + +namespace adl { + + +TString::TString(adl::Allocator* alloc) +{ + this->allocator = alloc; + content = nullptr; + len = 0; +} + +TString::TString(const char* str, adl::Allocator* alloc) +{ + this->allocator = alloc; + + if (str == nullptr || str[0] == '\0') { + content = nullptr; + len = 0; + } + else { + len = adl::strlen(str); + + content = alloc->alloc(len + 1, false); + + if (content == nullptr) { + __die(); + } + + + adl::strcpy(content, str); + } +} + +#if defined(__GNUC__) + +#else +TString::TString(const int x) +{ + if (x == 0) { // x == NULL + len = 0; + content = nullptr; + } + else { + len = 0; + content = nullptr; + } +} +#endif + +TString::TString(const TString& str, adl::Allocator* alloc) +{ + this->allocator = alloc; + + if (str.length() == 0) { + content = nullptr; + len = 0; + } + else { + len = str.length(); + content = alloc->alloc(len + 1, false); + if (content == nullptr) { + __die(); + } + + adl::strcpy(content, str.c_str()); + } +} + +TString::~TString() +{ + freeUp(); +} + +void TString::freeUp() +{ + if (content != nullptr) { + freeUpContent(); + len = 0; + } +} + + +void TString::clear() { + freeUp(); +} + + +void TString::freeUpContent() +{ + if (content != nullptr) { + allocator->free(content); + content = nullptr; + } +} + +size_t TString::length() const +{ + return len; +} + +const char* TString::c_str() const +{ + return content; +} + +TString& TString::operator=(const TString& str) +{ + if (&str == this) { + return *this; + } + + freeUp(); + + if (str.length() == 0) { + return *this; + } + else { + content = allocator->alloc(str.length() + 1, false); + if (content == nullptr) { + __die(); + } + + adl::strcpy(content, str.c_str()); + len = str.length(); + return *this; + } +} + +TString& TString::operator=(const char* str) +{ + if (str == this->content) { + return *this; + } + + freeUp(); + + if (str == nullptr || str[0] == '\0') { + return *this; + } + else { + len = strlen(str); + content = allocator->alloc(len + 1, false); + if (content == nullptr) { + __die(); + } + + strcpy(content, str); + return *this; + } +} + +const TString TString::operator+(const TString& str) const +{ + TString ret; + + ret.len = this->len + str.len; + + if (ret.len == 0) { + return ret; + } + + ret.content = allocator->alloc(ret.len + 1, false); + if (ret.content == nullptr) { + __die(); + } + + + if (this->content != nullptr) { + adl::memcpy(ret.content, this->content, this->len * sizeof(char)); + } + if (str.c_str() != nullptr) { + adl::memcpy(ret.content + this->len, str.c_str(), str.length() * sizeof(char)); + } + ret.content[ret.len] = '\0'; + + return ret; +} + +const TString TString::operator+(const char* str) const +{ + TString ret; + + auto lengthOfStr = (str == nullptr ? 0 : adl::strlen(str)); + + ret.len = this->len + lengthOfStr; + + if (ret.len == 0) { + return ret; + } + + ret.content = allocator->alloc(ret.len + 1, false); + if (ret.content == nullptr) { + __die(); + } + + if (this->content != nullptr) { + adl::memcpy(ret.content, this->content, this->len * sizeof(char)); + } + + adl::memcpy(ret.content + this->len, str, lengthOfStr * sizeof(char)); + ret.content[ret.len] = '\0'; + + return ret; +} + +const TString operator+(const char* strA, const TString& strB) +{ + TString ret {strB.allocator}; + + auto lengthOfStrA = (strA == nullptr ? 0 : adl::strlen(strA)); + ret.len = lengthOfStrA + strB.length(); + + if (ret.len == 0) { + return ret; + } + + ret.content = ret.allocator->alloc(ret.len + 1, false); + if (ret.content == nullptr) { + __die(); + } + + adl::memcpy(ret.content, strA, lengthOfStrA * sizeof(char)); + adl::memcpy(ret.content + lengthOfStrA, strB.c_str(), strB.length() * sizeof(char)); + ret.content[ret.len] = '\0'; + + return ret; +} + +const TString TString::operator+(const char c) const +{ + char tmp[2] = { c, '\0' }; + return *this + tmp; +} + +const TString TString::operator!() const +{ + TString ret = *this; + if (ret.length() >= 2) { + char c; + for (size_t i = 0; i < ret.length() / 2; i++) { + c = ret[i]; + ret[i] = ret[ret.length() - 1 - i]; + ret[ret.length() - 1 - i] = c; + } + } + + return ret; + +} + +const TString TString::operator-(const TString& str) const +{ + if (str.len == 0 || this->len == 0) { + return *this; + } + else { + char* occurenceLocation = adl::strstr(content, str.content); + if (occurenceLocation == nullptr) { + return *this; + } + else if (len == str.length()) { + return TString(this->allocator); + } + else { + char* p = allocator->alloc(len - str.length() + 1, false); + if (p == nullptr) { + __die(); + } + TString ret {this->allocator}; + ret.len = len - str.length(); + ret.content = p; + + adl::memcpy(p, content, adl::uint64_t(occurenceLocation - content) * sizeof(char)); + adl::memcpy( + p + (occurenceLocation - content), + occurenceLocation + str.length(), + (len - adl::uint64_t(occurenceLocation - content) - str.length()) * sizeof(char) + ); + p[ret.len] = '\0'; + + return ret; + } + } +} + +const TString TString::operator-(const char* str) const +{ + if (str == nullptr || str[0] == '\0' || this->len == 0) { + return *this; + } + else { + auto lengthOfStr = adl::strlen(str); + + char* occurenceLocation = adl::strstr(content, str); + if (occurenceLocation == nullptr) { + return *this; + } + else if (len == lengthOfStr) { + return TString(this->allocator); + } + else { + char* p = allocator->alloc(len - lengthOfStr + 1); + if (p == nullptr) { + __die(); + } + TString ret; + ret.len = len - lengthOfStr; + ret.content = p; + + adl::memcpy(p, content, adl::uint64_t(occurenceLocation - content) * sizeof(char)); + adl::memcpy( + p + (occurenceLocation - content), + occurenceLocation + lengthOfStr, + (len - adl::uint64_t(occurenceLocation - content) - lengthOfStr) * sizeof(char) + ); + p[ret.len] = '\0'; + + return ret; + } + } +} + +const TString TString::operator - (const char c) const +{ + if (c == '\0' || len == 0) { + return *this; + } + else { + for (size_t i = 0; i < len; i++) { + if (c == content[i]) { + TString ret{this->allocator}; + ret.len = len - 1; + ret.content = allocator->alloc(len, false); + if (ret.content == nullptr) { + __die(); + } + + adl::memcpy(ret.content, content, i * sizeof(char)); + adl::memcpy(ret.content + i, content + i + 1, (len - i - 1) * sizeof(char)); + ret[ret.len] = '\0'; + return ret; + } + } + + return *this; + } +} + +TString& TString::operator+=(const TString& str) +{ + if (str.len == 0) { + return *this; + } + else { + char* p; + auto resLen = len + str.len; + + p = allocator->alloc(resLen + 1, false); + if (p == nullptr) { + __die(); + } + if (content != nullptr) { + adl::memcpy(p, content, len * sizeof(char)); + + this->freeUpContent(); + } + + adl::memcpy(p + len, str.content, str.len * sizeof(char)); + p[resLen] = '\0'; + + this->len = resLen; + + this->content = p; + return *this; + } +} +TString& TString::operator+=(const char* str) +{ + if (str == nullptr || str[0] == '\0') { + return *this; + } + else { + auto lengthOfStr = adl::strlen(str); + auto resLen = len + lengthOfStr; + + char* p; + + p = allocator->alloc(resLen + 1, false); + if (p == nullptr) { + __die(); + } + if (content != nullptr) { + adl::memcpy(p, content, len * sizeof(char)); + this->freeUpContent(); + } + + adl::memcpy(p + len, str, lengthOfStr * sizeof(char)); + p[resLen] = '\0'; + + this->len = resLen; + + this->content = p; + return *this; + } +} +TString& TString::operator+=(const char c) +{ + if (c == '\0') { + return *this; + } + else { + auto resLen = len + 1; + char* p; + + p = allocator->alloc(resLen + 1, false); + if (p == nullptr) { + __die(); + } + + if (this->content != nullptr) { + adl::memcpy(p, content, len * sizeof(char)); + this->freeUpContent(); + } + + p[resLen - 1] = c; + p[resLen] = '\0'; + + this->len = resLen; + this->content = p; + return *this; + } +} + +TString& TString::operator-=(const TString& str) +{ + return *this = *this - str; +} +TString& TString::operator-=(const char* str) +{ + return *this = *this - str; +} +TString& TString::operator-=(const char c) +{ + return *this = *this - c; +} + +char& TString::operator[](size_t i) +{ + return content[i]; +} + +const char& TString::operator[](size_t i) const +{ + return content[i]; +} + + +const TString TString::operator*(int x) const +{ + TString ret; + + if (this->len == 0 || x <= 0) { + return ret; + } + else { + // x >= 1 + char* p = allocator->alloc(len * adl::size_t(x) + 1, false); + if (p == nullptr) { + __die(); + } + + for (adl::size_t i = 0; i < adl::size_t(x); i++) { + adl::memcpy(p + i * len, content, len * sizeof(char)); + } + + ret.content = p; + ret.len = this->len * adl::size_t(x); + ret.content[ret.len] = '\0'; + + return ret; + } +} + +const TString operator*(const int x, const TString& str) +{ + return str * x; +} + +TString& TString::operator*=(int x) +{ + return *this = *this * x; +} + +const TString operator+(const char c, const TString& str) +{ + char tmp[2] = { c, '\0' }; + return tmp + str; +} + +bool TString::operator==(const TString& str) const +{ + if (this->content == str.content) { + return true; + } + else if (this->len != str.len) { + return false; + } + else if (this->content == nullptr || str.content == nullptr) { + return false; + } + else { + for (size_t i = 0; i < len; i++) { + if (this->content[i] != str[i]) { + return false; + } + } + return true; + } +} + +bool TString::operator==(const char* str) const +{ + if (str == content) { + return true; + } + else if (str == nullptr || str[0] == '\0') { + return len == 0; + } + else if (len == 0) { + return false; + } + + auto lenOfStr = adl::strlen(str); + if (lenOfStr != len) { + return false; + } + for (size_t i = 0; i < lenOfStr; i++) { + if (str[i] != content[i]) { + return false; + } + } + return true; +} + +bool TString::operator!=(const char* str) const +{ + return !(*this == str); +} + +bool TString::operator!=(const TString& str) const +{ + return !(*this == str); +} + +bool TString::operator>(const TString& str) const +{ + if (this->len + str.len == 0 || this->len == 0) { + return false; + } + else if (str.len == 0) { + return true; + } + else { + auto minLen = len < str.len ? len : str.len; + for (size_t i = 0; i < minLen; i++) { + if (content[i] > str[i]) { + return true; + } + else if (content[i] < str[i]) { + return false; + } + } + + return len > str.len; + } +} + +bool TString::operator>(const char* str) const +{ + auto lengthOfStr = (str == nullptr ? 0 : adl::strlen(str)); + if (this->len + lengthOfStr == 0 || this->len == 0) { + return false; + } + else if (lengthOfStr == 0) { + return true; + } + else { + auto minLen = len < lengthOfStr ? len : lengthOfStr; + for (size_t i = 0; i < minLen; i++) { + if (content[i] > str[i]) { + return true; + } + else if (content[i] < str[i]) { + return false; + } + } + + return len > lengthOfStr; + } +} + + +bool TString::operator<(const TString& str) const +{ + if (this->len + str.len == 0 || str.len == 0) { + return false; + } + else if (len == 0) { + return true; + } + else { + auto minLen = len < str.len ? len : str.len; + for (size_t i = 0; i < minLen; i++) { + if (content[i] < str[i]) { + return true; + } + else if (content[i] > str[i]) { + return false; + } + } + + return len < str.len; + } +} + +bool TString::operator<(const char* str) const +{ + auto lengthOfStr = (str == nullptr ? 0 : adl::strlen(str)); + if (this->len + lengthOfStr == 0 || lengthOfStr == 0) { + return false; + } + else if (len == 0) { + return true; + } + else { + auto minLen = len < lengthOfStr ? len : lengthOfStr; + for (size_t i = 0; i < minLen; i++) { + if (content[i] < str[i]) { + return true; + } + else if (content[i] > str[i]) { + return false; + } + } + + return len < lengthOfStr; + } +} + +bool TString::operator>=(const TString& str) const +{ + return !(*this < str); +} +bool TString::operator>=(const char* str) const +{ + return !(*this < str); +} + +bool TString::operator<=(const TString& str) const +{ + return !(*this > str); +} +bool TString::operator<=(const char* str) const +{ + return !(*this > str); +} + + +TString TString::substr(const size_t pos, const size_t len) const { + TString ret; + + if (len == 0 || pos >= this->len) + return ret; + + size_t endIdx = pos + len; // exclusive + if (endIdx > this->len) { + endIdx = this->len; + } + + ret.len = endIdx - pos; + ret.content = allocator->alloc(ret.len + 1, false); + if (ret.content == nullptr) { + __die(); + } + + adl::memcpy(ret.content, this->content + pos, ret.len); + ret.content[ret.len] = '\0'; + + return ret; +} + + + +void TString::split(const TString& key, ArrayList& out) const { + out.clear(); + if (key.length() == 0) { + out.append(key); + return; + } + + adl::TString tmp{this->allocator}; + adl::size_t i = 0; + while (i < length()) { + if (adl::strncmp(key.c_str(), content + i, key.length()) == 0) { + if (tmp.length()) { + out.append(tmp); + tmp.clear(); + } + + i += key.length(); + continue; + } + + tmp += content[i++]; + } + + if (tmp.length()) { + out.append(tmp); + } +} + + +bool operator>(const char* strA, const TString& strB) +{ + return strB < strA; +} +bool operator<(const char* strA, const TString& strB) +{ + return strB > strA; +} +bool operator>=(const char* strA, const TString& strB) +{ + return strB <= strA; +} +bool operator<=(const char* strA, const TString& strB) +{ + return strB >= strA; +} + + +bool operator==(const char* strA, const TString& strB) +{ + return strB == strA; +} + +bool operator!=(const char* strA, const TString& strB) +{ + return strB != strA; +} + + +int64_t TString::toInt64() const { + if (len == 0) + return 0; + + bool negative = false; + int64_t res = 0; + size_t idx = 0; + if (content[0] == '-') { + negative = true; + idx++; + } + + while (idx < len) { + char ch = content[idx]; + + if (ch < '0' || ch > '9') + break; + + res *= 10; + res += ch - '0'; + + idx++; + } + + return negative ? (-res) : res; +} + + +#define TSTRING_IMPL_TO_STRING(Type) \ + TString TString::to_string(const Type x) { \ + TString res; \ + Type value = x; \ + while (value) { \ + res = char((value % 10) + '0') + res; \ + value /= 10; \ + } \ +\ + return res.length() ? res : "0"; \ + } + + +TSTRING_IMPL_TO_STRING(adl::uint32_t) +TSTRING_IMPL_TO_STRING(adl::uint64_t) +TSTRING_IMPL_TO_STRING(adl::size_t) + + +#undef TSTRING_IMPL_TO_STRING + + +} // namespace adl diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/TString.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/TString.h new file mode 100644 index 0000000000000000000000000000000000000000..6446c055fcfe4ff88a240c2c86b3cdda3c7abc6c --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/TString.h @@ -0,0 +1,116 @@ +/* 2051565 GTY Tongji CS */ +// Ported from TJ OOP HW. Modified for Amkos. + +#pragma once + +#include "./Allocator.h" +#include "./config.h" + +namespace adl { + + +template class ArrayList; + + +class TString { +protected: + char* content; + size_t len; + adl::Allocator* allocator = nullptr; + + inline void freeUpContent(); + + +public: + static const size_t npos = -1; + + TString(adl::Allocator* alloc = &defaultAllocator); + TString(const char* str, adl::Allocator* alloc = &defaultAllocator); + TString(const TString& str, adl::Allocator* alloc = &defaultAllocator); + +#if defined(__GNUC__) + +#else + TString(const int x); +#endif + + virtual ~TString(); + + + void freeUp(); + void clear(); + + size_t length() const; + const char* c_str() const; + inline char* data() { return this->content; } + inline const char* data() const { return this->content; } + + TString& operator = (const TString& str); + TString& operator = (const char* str); + + const TString operator + (const TString& str) const; + const TString operator + (const char* str) const; + const TString operator + (const char c) const; + + const TString operator - (const TString& str) const; + const TString operator - (const char* str) const; + const TString operator - (const char c) const; + + const TString operator * (const int x) const; + + const TString operator ! () const; + + TString& operator *= (int x); + TString& operator += (const TString& str); + TString& operator += (const char* str); + TString& operator += (const char c); + + TString& operator -= (const TString& str); + TString& operator -= (const char* str); + TString& operator -= (const char c); + + bool operator == (const TString& str) const; + bool operator == (const char* str) const; + bool operator != (const TString& str) const; + bool operator != (const char* str) const; + bool operator > (const TString& str) const; + bool operator > (const char* str) const; + bool operator < (const TString& str) const; + bool operator < (const char* str) const; + bool operator >= (const TString& str) const; + bool operator >= (const char* str) const; + bool operator <= (const TString& str) const; + bool operator <= (const char* str) const; + + TString substr(const size_t pos, const size_t len = npos) const; + char& at(const int n); + const char& at(const int n) const; + + void split(const adl::TString&, adl::ArrayList& out) const; + + char& operator[] (size_t i); + const char& operator[] (size_t i) const; + + friend const TString operator + (const char* strA, const TString& strB); + + friend const TString operator + (const char c, const TString& str); + friend const TString operator * (const int x, const TString& str); + + friend bool operator == (const char* strA, const TString& strB); + friend bool operator != (const char* strA, const TString& strB); + friend bool operator > (const char* strA, const TString& strB); + friend bool operator < (const char* strA, const TString& strB); + friend bool operator >= (const char* strA, const TString& strB); + friend bool operator <= (const char* strA, const TString& strB); + + int64_t toInt64() const; + + static TString to_string(const adl::uint32_t); + static TString to_string(const adl::uint64_t); + static TString to_string(const unsigned long); +}; + + + +} // namespace MtsysKv + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/arpa/inet.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/arpa/inet.h new file mode 100644 index 0000000000000000000000000000000000000000..c1fbe0dfe26944d820417f637fb271e6f78abefb --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/arpa/inet.h @@ -0,0 +1,61 @@ +// 124033910070 GTY +// gongty [at] tongji [dot] edu [dot] cn + +// created on 2024.11.25 +// at Jiangchuan, Minhang, Shanghai + +// implements: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/arpa_inet.h.html + +#pragma once + +#include "../endian.h" + +namespace adl { + + +inline int32_t htonl(int32_t x) { + return htobe32(x); +} + +inline uint32_t htonl(uint32_t x) { + return htobe32(x); +} + + +inline uint16_t htons(uint16_t x) { + return htobe16(x); +} + + +inline int64_t htonq(int64_t x) { + return htobe64(x); +} + + +inline uint64_t htonq(uint64_t x) { + return htobe64(x); +} + + +inline int32_t ntohl(int32_t x) { + return htonl(x); +} + +inline uint32_t ntohl(uint32_t x) { + return htonl(x); +} + +inline uint16_t ntohs(uint16_t x) { + return htons(x); +} + +inline int64_t ntohq(int64_t x) { + return htonq(x); +} + +inline uint64_t ntohq(uint64_t x) { + return htonq(x); +} + + +} // namespace adl diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/ArrayList.hpp b/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/ArrayList.hpp new file mode 100644 index 0000000000000000000000000000000000000000..50ca155f7cbc329294b5a7ecf1bbbbd9876a19ce --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/ArrayList.hpp @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + * Array List Structure + * by gongty [AT] tongji [DOT] edu [DOT] cn + * + * Warning!! Heavy Data Structure: + * This structure heavily depends on template, each usage could make your kernel larger. + * + * + * created on 2023.7.3 at Anting Town, Jiading, Shanghai + */ + +/* + * This data structure is forked from YurongOS, modified for Amkos. + * + * https://github.com/FlowerBlackG/YurongOS/blob/master/src/lib/collections/ArrayList.hpp + */ + +#pragma once + + +#include "../string.h" +#include "../config.h" +#include "../stdint.h" +#include + +#include + +namespace adl { + + +template +class ArrayListIterator { + typedef ArrayListIterator Self; + +protected: + size_t size; + size_t curr; + DataType* data; + +public: + ArrayListIterator(DataType* data, size_t size, size_t curr) { + this->size = size; + this->data = data; + this->curr = curr; + } + + DataType& operator * () const { return data[curr]; }; + Self& operator ++ () { + curr++; + return *this; + } + + Self operator ++ (int) { + auto tmp = *this; + curr++; + return tmp; + } + + friend bool operator == (const Self& a, const Self& b) { + return a.curr == b.curr + && a.size == b.size + && a.data == b.data; + } + + friend bool operator != (const Self& a, const Self& b) { + return a.curr != b.curr + || a.size != b.size + || a.data != b.data; + } + +}; + + +template +class ArrayList { + +protected: + size_t _size = 0; + size_t _capacity = 0; + DataType* _data = nullptr; + Allocator* allocator; + +public: + ArrayList() { + this->allocator = &defaultAllocator; + } + + ArrayList(Allocator& allocator) { + this->allocator = &allocator; + } + + + ArrayList(const ArrayList& other) { + this->allocator = other.allocator; + if (!this->resize(other.size())) { + printf("failed to copy arraylist!\n"); + return; + } + + adl::memcpy(this->_data, other._data, other._size * sizeof(DataType)); + } + + + const ArrayList& operator = (const ArrayList& other) { + if (this == &other) { + return *this; + } + + if (_capacity) + allocator->free(_data); + + this->allocator = other.allocator; + if (!this->resize(other.size())) { + printf("failed to copy arraylist!\n"); + return *this; + } + + adl::memcpy(this->_data, other._data, other._size * sizeof(DataType)); + return *this; + } + + + void clear() { + _size = 0; + } + + virtual ~ArrayList() { + if (_capacity) + allocator->free(_data); + } + + inline size_t size() const { + return this->_size; + } + + inline size_t capacity() const { + return this->_capacity; + } + + inline DataType* data() { + return this->_data; + } + + inline const DataType* data() const { + return this->_data; + } + + void reserve(size_t new_capacity) { + if (new_capacity <= _capacity) { + // Do nothing + return; + } + if (!this->allocator) { // Check null ptr + printf("[CRITICAL] Allocator is null!\n"); + return; + } + // Allocate new memory + auto newAddr = allocator->alloc(new_capacity); + if (!newAddr) { + printf("[CRITICAL] ArrayList reserve failed: insufficient memory.\n"); + return; + } + // Copy the original data to new memory + for (size_t i = 0; i < _size; i++) + newAddr[i] = _data[i]; + // Free old memory + if (_capacity) { + allocator->free(_data); + } + // Update ptr and capacity + _data = newAddr; + _capacity = new_capacity; + } + + + bool resize(size_t newSize) { + if (newSize > _size) { + reserve(newSize); + _size = _capacity; + return _size == newSize; + } + else { + _size = newSize; + return true; + } + } + + int append(const DataType& data) { + + if (_size == _capacity) { + // alloc more memory + size_t newCapacity = _capacity; + if (newCapacity == 0) { + newCapacity = 16; + } else { + newCapacity += newCapacity / 2; + } + + auto newAddr = allocator->alloc(newCapacity); + if (!newAddr) { + return 1; // error + } + + for (size_t i = 0; i < _size; i++) + newAddr[i] = _data[i]; + + if (_capacity) + allocator->free(_data); + + _data = newAddr; + _capacity = newCapacity; + } + + _data[_size++] = data; + + return 0; // inserted successfully + } + + inline int push(const DataType& data) { + return this->append(data); + } + + DataType pop() { + if (_size) { + return _data[--_size]; // todo: should call destructor + } + + return * ( DataType* ) 0; + } + + + adl::size_t count(const DataType& data) const { + adl::size_t res = 0; + for (const auto& it : *this) { + if (it == data) { + res++; + } + } + return res; + } + + + bool contains(const DataType& data) const { + return count(data); + } + + + DataType& operator [] (size_t idx) { + return _data[idx]; + } + + const DataType& operator [] (size_t idx) const { + return _data[idx]; + } + + + bool operator == (const ArrayList& other) const { + if (_size != other._size) + return false; + for (adl::size_t i = 0; i < _size; i++) { + if (_data[i] != other[i]) + return false; + } + return true; + } + + + DataType& back() { return _data[_size - 1]; } + DataType& front() { return _data[0]; } + + + bool isEmpty() const { return _size == 0; } + bool isNotEmpty() const { return _size > 0; } + + + // ------ iteration related ------ + + ArrayListIterator begin() const { + return ArrayListIterator(_data, _size, 0); + } + + ArrayListIterator end() const { + return ArrayListIterator(_data, _size, _size); + } + +}; + + +class ByteArray : public ArrayList { +protected: + int construct(const void* data, adl::size_t dataLen, adl::Allocator& alloc) { + this->allocator = &alloc; + if (!resize(dataLen)) { + // failed to reserve. + printf("Failed to initialize ByteArray! No memory.\n"); + return -1; + } + adl::memcpy(this->_data, data, dataLen); + return 0; + } + +public: + ByteArray() : adl::ArrayList() {} + + ByteArray(adl::Allocator& alloc) : adl::ArrayList(alloc) {} + + + ByteArray(const void* data, adl::size_t dataLen, adl::Allocator& alloc = defaultAllocator) + : adl::ArrayList(alloc) + { + construct(data, dataLen, alloc); + } + + + ByteArray(const char* str, adl::Allocator& alloc = defaultAllocator) + : adl::ArrayList(alloc) + { + construct(str, (str ? strlen(str) : 0), alloc); + } + + + ByteArray(const TString& str, adl::Allocator& alloc = defaultAllocator) + : adl::ArrayList(alloc) + { + construct(str.data(), str.length(), alloc); + } + + + TString toString() const { + TString str; + for (auto& ch : *this) { + str += char(ch); + } + + return str; + } + + + int append(const void* data, adl::size_t len) { + if (len == 0) + return 0; + + if (!resize(_size + len)) + return 1; // out of resource. + + adl::memcpy(_data + _size - len, data, len); + + return 0; + } + +}; + + + +} // namespace adl diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/HashMap.hpp b/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/HashMap.hpp new file mode 100644 index 0000000000000000000000000000000000000000..33c0dbf7caa0ddacc2e0d998cb17b9f9fc388255 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/HashMap.hpp @@ -0,0 +1,22 @@ +/* + HashMap + + Created on 2025.1.19 at Zhuhai, Guangdong + + gongty [at] tongji [dot] edu [dot] cn + +*/ + + +#pragma once + +#include "./RedBlackTree.hpp" + +namespace adl { + +template +using HashMap = RedBlackTree; + + +} + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/LinkedList.cpp b/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/LinkedList.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cf4215215e6bf389022d936df5dd330c8a65c915 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/LinkedList.cpp @@ -0,0 +1,315 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + + 链表 + + 创建于2023年2月5日 江西省上饶市玉山县 + +*/ + +// forked from yros stdlib. +// github.com/FlowerBlackG/YurongOS + +// modified for Amkos + +#include + +namespace adl { + +LinkedList& LinkedList::append(LinkedListNode* node) { + if (tail) { + + tail->next = node; + node->prev = tail; + node->next = nullptr; + + tail = node; + + } else { + + node->prev = node->next = nullptr; + head = tail = node; + + } + + length++; + + return *this; + +} + + +LinkedList& LinkedList::insert(LinkedListNode* node, int index) { + + if (index >= this->length) { + pushBack(node); + } else if (index <= 0) { + if (this->head) { + node->next = this->head->next; + this->head->next->prev = node; + } + + node->prev = nullptr; + this->head = node; + this->length++; + } else { + + auto prevNode = this->get(index); + node->prev = prevNode; + node->next = prevNode->next; + prevNode->next->prev = node; + prevNode->next = node; + this->length++; + + } + + + return *this; +} + +LinkedList& LinkedList::insertWhen( + LinkedListNode& node, + const int64_t cargo, + int (* judge) (const int64_t cargo, LinkedListNode* before, LinkedListNode* after) +) { + + + LinkedListNode* prev = nullptr; + LinkedListNode* next = this->head; + + do { + + int judgement = judge(cargo, prev, next); + if (judgement == 0) { // accept + + this->length++; + + if (prev && next) { + + // ... -> prev -> (new) -> next -> ... + + prev->next = &node; + next->prev = &node; + node.prev = prev; + node.next = next; + + } else if (prev) { + + // ... -> prev -> (new) -> tail + + this->tail = prev->next = &node; + node.next = nullptr; + node.prev = prev; + + } else if (next) { + + // head -> (new) -> next -> ... + + this->head = next->prev = &node; + node.next = next; + node.prev = nullptr; + + } else { + + // head -> (new) -> tail + this->head = this->tail = &node; + node.prev = node.next = nullptr; + + } + + break; + + } else if (judgement < 0) { // cancel + + break; + + } else { // continue + + prev = next; + if (next) { + next = next->next; + } + + } + + } while (prev || next); + + return *this; +} + +LinkedList& LinkedList::insertWhen( + LinkedListNode* node, + const int64_t cargo, + int (* judge) (const int64_t cargo, LinkedListNode* before, LinkedListNode* after) +) { + return this->insertWhen(*node, cargo, judge); +} + +LinkedList& LinkedList::pushBack(LinkedListNode* node) { + + return this->append(node); + +} + + +LinkedList& LinkedList::remove(LinkedListNode* node) { + + if (node == this->head && node == this->tail) { + this->head = this->tail = nullptr; + + } else if (node == this->head) { + this->head->next->prev = nullptr; + this->head = this->head->next; + + } else if (node == this->tail) { + this->tail->prev->next = nullptr; + this->tail = this->tail->prev; + + } else { + node->prev->next = node->next; + node->next->prev = node->prev; + } + + this->length --; + + return *this; +} + + +LinkedListNode* LinkedList::removeAt(int index) { + auto node = this->get(index); + + this->remove(node); + + return node; +} + +LinkedListNode* LinkedList::removeFirst() { + + LinkedListNode* ret = nullptr; + + if (head) { + + length--; + ret = head; + if (this->head == this->tail) { + this->head = this->tail = nullptr; + } else { + this->head->next->prev = nullptr; + this->head = this->head->next; + } + + } + + return ret; +} + +LinkedListNode* LinkedList::removeLast() { + + auto res = this->tail; + + if (res) { + + this->tail = res->prev; + res->prev = nullptr; + + if (this->tail) { + this->tail->next = nullptr; + } else { + this->head = nullptr; + } + + length--; + + } + + res->next = nullptr; + res->prev = nullptr; + + return res; +} + +LinkedListNode* LinkedList::popBack() { + return this->removeLast(); +} + + +bool LinkedList::contains(LinkedListNode* node) { + return this->indexOf(node) >= 0; +} + + +LinkedListNode* LinkedList::getFirst() { + return this->head; +} + +LinkedListNode* LinkedList::getLast() { + return this->tail; +} + +LinkedListNode* LinkedList::get(int index) { + + if (index < length) { + + LinkedListNode* res; + + if (index < length / 2) { + + int counter = index; + res = this->head; + while (counter--) { + res = res->next; + } + + } else { + + int counter = length - index - 1; + res = this->tail; + while (counter--) { + res = res->prev; + } + + } + + return res; + + } else { + return nullptr; + } +} + + +int LinkedList::indexOf(LinkedListNode* node) { + auto curr = this->head; + int counter = 0; + + while (curr && curr != node) { + counter++; + curr = curr->next; + } + + return curr ? counter : -1; +} + +bool LinkedList::isEmpty() { + return this->length == 0; +} + +bool LinkedList::isNotEmpty() { + return this->length != 0; +} + +void LinkedList::clear() { + this->head = this->tail = nullptr; +} + +void LinkedList::forEach( void (* callable) (LinkedListNode*) ) { + auto curr = this->head; + while (curr) { + callable(curr); + curr = curr->next; + } +} + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/LinkedList.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/LinkedList.h new file mode 100644 index 0000000000000000000000000000000000000000..753b9d0add4c2e62e11d62b67bf5384d3e23fc55 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/LinkedList.h @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + + 链表 + + 创建于2023年2月5日 江西省上饶市玉山县 + +*/ + +#pragma once + +#include "../sys/types.h" + +namespace adl { + +/** + * 链表节点。 + * 将该结构嵌入需要用链表连接的对象结构,通过偏移方式得到数据结构本体。 + */ +struct LinkedListNode { + LinkedListNode* prev; + LinkedListNode* next; +}; + + +class LinkedListIterator { + + typedef LinkedListIterator Self; + typedef LinkedListNode ElementType; + +public: + LinkedListIterator(ElementType* node) { this->node = node;} + + ElementType& operator * () const { return *node; }; + + Self& operator ++ () { + node = node->next; + return *this; + }; + + Self operator ++ (int) { + auto tmp = *this; + node = node->next; + return tmp; + } + + Self& operator -- () { + node = node->prev; + return *this; + } + + Self operator -- (int) { + auto tmp = *this; + node = node->prev; + return tmp; + } + + friend bool operator == (const Self& a, const Self& b) { + return a.node == b.node; + } + + friend bool operator != (const Self& a, const Self& b) { + return a.node != b.node; + } + + +protected: + ElementType* node; +}; + +struct LinkedList { + LinkedListNode* head = nullptr; + LinkedListNode* tail = nullptr; + + int length = 0; + + LinkedList& append(LinkedListNode* node); + LinkedList& insert(LinkedListNode* node, int index); + + /** + * 逐位置判断是否可插入。 + * + * @param judge 返回 0 表示可以插入。返回正数表示继续查找。返回负数表示取消操作。 + */ + LinkedList& insertWhen( + LinkedListNode& node, + const adl::int64_t cargo, + int (* judge) (const adl::int64_t cargo, LinkedListNode* before, LinkedListNode* after) + ); + + LinkedList& insertWhen( + LinkedListNode* node, + const adl::int64_t cargo, + int (* judge) (const adl::int64_t cargo, LinkedListNode* before, LinkedListNode* after) + ); + + LinkedList& pushBack(LinkedListNode* node); + + LinkedList& remove(LinkedListNode* node); + + LinkedListNode* removeAt(int index); + LinkedListNode* removeFirst(); + LinkedListNode* removeLast(); + LinkedListNode* popBack(); + + bool contains(LinkedListNode* node); + + LinkedListNode* getFirst(); + LinkedListNode* getLast(); + LinkedListNode* get(int index); + + int indexOf(LinkedListNode* node); + + bool isEmpty(); + bool isNotEmpty(); + + void clear(); + + void forEach( + void (* callable) ( + LinkedListNode* node + ) + ); + + // ------ 迭代相关 ------ + + inline LinkedListIterator begin() const { + return LinkedListIterator(this->head); + } + + inline LinkedListIterator end() const { + return LinkedListIterator(nullptr); + } +}; + + +} // namespace adl diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/RedBlackTree.hpp b/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/RedBlackTree.hpp new file mode 100644 index 0000000000000000000000000000000000000000..08650691ac5081db105ad432d2cfc900e7599bd2 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/collections/RedBlackTree.hpp @@ -0,0 +1,1334 @@ +/** + * Red Black Tree Hpp + * by GTY + * 2022.1 + * at Yushan County, Shangrao, Jiangxi + */ + +// https://blog.csdn.net/m0_62405272/article/details/122612246 +// https://blog.csdn.net/m0_62405272/article/details/122631653 + +// 2024.11.6: modified for Amkos + + +#include "../Allocator.h" +#include "../config.h" +#include "../sys/types.h" +#include + + +static void __die() { + printf("die RedBlackTree.hpp\n"); + while (1) + ; +} + + +namespace adl { + + +template +class RedBlackTreeIterator; + + +template +class RedBlackTree { + friend RedBlackTreeIterator; + +public: + /** Object's life-management methods */ + RedBlackTree(adl::Allocator* = &adl::defaultAllocator); + ~RedBlackTree(); + + /** + * Clear all elements in tree. + */ + void clear(); + +public: + /** Basic query methods */ + + /** + * Determine whether a key is in the tree. + * + * @param queryKey + */ + bool hasKey(const KeyType&); + + bool contains(const KeyType& key) { + return hasKey(key); + } + + /** + * Get data (ref) by key. + * + * @param key + */ + DataType& getData(const KeyType&); + + DataType& operator [] (const KeyType&); + + /** + * Get data (clone) by key. + */ + DataType copyData(const KeyType&, const DataType* fallback = nullptr); + + /** + * Set data. If data with same key already exists, it would be overwritten. + * + * @param key + * @param data + */ + RedBlackTree& setData(const KeyType&, const DataType&); + + /** + * Delete key. + * + * @param key + */ + RedBlackTree& removeKey(const KeyType&, bool noExcept = true); + + adl::size_t size(); + + /** + * Range scan. This method will search for keys inside [lhs, rhs], + * and calls collector with data for each node discovered. + * + * @param lhs inclusive + * @param rhs inclusive + * @param data to be passed to collector + * @param collector + * @return How many elements collected. + */ + adl::size_t rangeScan( + const KeyType& lhs, + const KeyType& rhs, + void* data, + bool (*collector) (void* data, const KeyType&, const DataType&) + ); + + + /* ------ iteration ------ */ + + RedBlackTreeIterator begin() const { + return RedBlackTreeIterator(this->root); + } + + + RedBlackTreeIterator end() const { + return RedBlackTreeIterator(nullptr); + } + + +public: + + + +protected: + enum class NodeColor { + RED, BLACK + }; + enum class ChildSide { + LEFT, RIGHT + }; + struct Node { + KeyType key; + DataType data; + NodeColor color = NodeColor::RED; + Node* parent = nullptr; + Node* leftChild = nullptr; + Node* rightChild = nullptr; + }; + +protected: + /** + * Release one node with all of its children, recursively. + * + * @param node first node to be released recursively + */ + void cleanup(Node* node); + + + Node* locateNode(const KeyType&); + + + /** + * Non-locked version of range scan. Designed to be called by public rangeScan method. + */ + adl::size_t doRangeScan( + const Node* node, + const KeyType& lhs, + const KeyType& rhs, + void* data, + bool (*collector) (void* data, const KeyType&, const DataType&) + ); + + + /** + * Rotate left. + * + * @param node + * @exception If `node` dosen't have right subtree, undefined behaviour occurs. + */ + void rotateLeft(Node* node); + + /** + * Rotate right + * + * @param node + * @exception If `node` dosen't have left subtree, undefined behaviour occurs + */ + void rotateRight(Node* node); + + /** + * + * + * @param node Child node of the two connected red nodes. + * @exception If node is nullptr, UB would occur. + */ + void rebalanceRedNode(Node* node); + + /** + * + * + * @param node Lighter node. + * @exception UB if node is not the lighter one. + */ + void rebalanceChildren(Node* node); + + + enum class LockType { + READ, WRITE + }; + + void lock(LockType); + void unlock(LockType); + + +protected: + /** + * Root node. + */ + Node* root = nullptr; + + adl::Allocator* allocator = nullptr; + +}; + + +/* ---------------- Iterator ---------------- */ + + +template +class RedBlackTreeIterator { + typedef RedBlackTreeIterator Self; + +protected: + RedBlackTree::Node* root; // Set to nullptr to disable this iterator. + RedBlackTree::Node* curr; + +protected: + void doOperatorPlusPlus() { + if (root == nullptr) + return; + + if (curr->rightChild) { + curr = curr->rightChild; + while (curr->leftChild) + curr = curr->leftChild; + return; + } + + + + while (true) { + + if (curr->parent == nullptr) { + root = nullptr; + return; + } + + if (curr->parent->rightChild == curr) { + curr = curr->parent; + continue; + } + + curr = curr->parent; + return; + + } + + + } // void doOperatorPlusPlus() + +public: + RedBlackTreeIterator(RedBlackTree::Node* root) { + this->root = this->curr = root; + + if (this->root == nullptr) + return; + + while (this->curr->leftChild) + this->curr = this->curr->leftChild; + } + + adl::ref_pair operator * () const { + return make_ref_pair(curr->key, curr->data); + } + + Self& operator ++ () { + doOperatorPlusPlus(); + return *this; + } + + Self operator ++ (int) { + auto tmp = *this; + doOperatorPlusPlus(); + return tmp; + } + + friend bool operator == (const Self& a, const Self& b) { + return (!a.root && !b.root) || (a.root == b.root && a.curr == b.curr); + } + + friend bool operator != (const Self& a, const Self& b) { + return !(a == b); + } + + +}; + + + +/* ---------------- Impl ---------------- */ + +template +RedBlackTree::RedBlackTree(adl::Allocator* allocator) +{ + this->allocator = allocator; +} + + +template +RedBlackTree::~RedBlackTree() +{ + this->clear(); +} + + +template +void RedBlackTree::clear() +{ + + if (this->root != nullptr) { + this->cleanup(this->root); + this->root = nullptr; + } +} + + +template +bool RedBlackTree::hasKey(const KeyType& queryKey) +{ + + return !!locateNode(queryKey); +} + + +template +DataType& RedBlackTree::getData(const KeyType& key) +{ + + auto node = locateNode(key); + + if (node) { + return node->data; // Warning: Race condition here. + } + + +#if 0 + throw std::runtime_error("could not find your key in the object."); // key not found. +#else + __die(); +#endif + +} + + + +template +DataType& RedBlackTree::operator [] (const KeyType& key) +{ + + { + auto node = locateNode(key); + + if (node) { + return node->data; // Warning: Race condition here. + } + } + + + this->setData(key, DataType {}); + + return locateNode(key)->data; + +} + + + + + +template +DataType RedBlackTree::copyData(const KeyType& key, const DataType* fallback) { + + auto node = locateNode(key); + + if (node) { + return node->data; + } else if (fallback) { + return *fallback; + } + +#if 0 + throw std::runtime_error("could not find your key in the object."); // key not found. +#else + __die(); +#endif +} + + +template +RedBlackTree& RedBlackTree::setData( + const KeyType& key, + const DataType& data +) +{ + + + Node* currentNode = root; + Node* currentParent = nullptr; + + while (currentNode != nullptr) { + if (key == currentNode->key) { // key found + currentNode->data = data; + return *this; // data updated. exit. + } + else { + currentParent = currentNode; + currentNode = (currentNode->key > key ? currentNode->leftChild : currentNode->rightChild); + } + } + + + // now, we should create new node for data. + + /* + now, currentNode points to nullptr, currentParent points to the last visited node, probably nullptr. + new node is red, inserted to the end. + */ + + // create node + currentNode = allocator->alloc(); + currentNode->parent = currentParent; + currentNode->leftChild = nullptr; + currentNode->rightChild = nullptr; + currentNode->key = key; + currentNode->data = data; + + // If tree is empty, just set new node as root. + if (currentParent == nullptr) { + currentNode->color = NodeColor::BLACK; + this->root = currentNode; + return *this; + } + + + // now, we should handle when tree is not empty. + + currentNode->color = NodeColor::RED; + // bind new node to parent. + if (currentParent->key > key) { + currentParent->leftChild = currentNode; + } + else { + currentParent->rightChild = currentNode; + } + + this->rebalanceRedNode(currentNode); + return *this; +} + + +template +RedBlackTree& RedBlackTree::removeKey( + const KeyType& key, + bool noExcept +) +{ + + + Node* currentNode = root; + + while (currentNode != nullptr) { + if (key == currentNode->key) { + break; // key found. matched. + } + else { + currentNode = (currentNode->key > key ? currentNode->leftChild : currentNode->rightChild); + } + } + + if (currentNode == nullptr) { + + if (noExcept) + return *this; + else { +#if 0 + throw std::runtime_error("could not find your key in the object."); // key not found. +#else + __die(); +#endif + } + } + + + // Otherwise, find an alternative node. + // As long as there is at least one child, continue to search for an alternative node. + while (currentNode->leftChild != nullptr || currentNode->rightChild != nullptr) { + if (currentNode->rightChild != nullptr) { + // current node has right child. + // replace the original deleted node with its successor. + // note that successor must has no left child. + Node* replacementNode = currentNode->rightChild; + while (replacementNode->leftChild != nullptr) { + replacementNode = replacementNode->leftChild; + } + + struct { + Node* leftChild; + Node* rightChild; + NodeColor color; + Node* parent; + } currentNodeInfo = { + currentNode->leftChild, currentNode->rightChild, + currentNode->color, currentNode->parent + }, replacementNodeInfo = { + replacementNode->leftChild, replacementNode->rightChild, + replacementNode->color, replacementNode->parent + }; + + // exchange color. + currentNode->color = replacementNodeInfo.color; + replacementNode->color = currentNodeInfo.color; + + // edit parent node. + if (currentNodeInfo.parent != nullptr) { + if (currentNodeInfo.parent->leftChild == currentNode) { + currentNodeInfo.parent->leftChild = replacementNode; + } + else { + currentNodeInfo.parent->rightChild = replacementNode; + } + } + else { + this->root = replacementNode; + } + + // edit currentNode's left child's data. + if (currentNodeInfo.leftChild != nullptr) { + currentNodeInfo.leftChild->parent = replacementNode; + } + + // edit replacementNode's right child's data (if has) + if (replacementNodeInfo.rightChild != nullptr) { + replacementNodeInfo.rightChild->parent = currentNode; + } + + currentNode->leftChild = nullptr; + currentNode->rightChild = replacementNodeInfo.rightChild; + if (replacementNodeInfo.parent == currentNode) { + currentNode->parent = replacementNode; + } + else { + currentNode->parent = replacementNodeInfo.parent; + } + + replacementNode->leftChild = currentNodeInfo.leftChild; + replacementNode->parent = currentNodeInfo.parent; + if (currentNodeInfo.rightChild == replacementNode) { + replacementNode->rightChild = currentNode; + } + else { + replacementNode->rightChild = currentNodeInfo.rightChild; + } + + if (currentNodeInfo.rightChild != replacementNode) { + currentNodeInfo.rightChild->parent = replacementNode; + replacementNodeInfo.parent->leftChild = currentNode; + } + } // if (currnode has right child) + else { // curr child only have left child + + // note that prev node's right child must be nullptr. + Node* replacementNode = currentNode->leftChild; + while (replacementNode->rightChild != nullptr) { + replacementNode = replacementNode->rightChild; + } + + struct { + Node* leftChild; + Node* rightChild; + NodeColor color; + Node* parent; + } currentNodeInfo = { + currentNode->leftChild, currentNode->rightChild, + currentNode->color, currentNode->parent + }, replacementNodeInfo = { + replacementNode->leftChild, replacementNode->rightChild, + replacementNode->color, replacementNode->parent + }; + + // exchange color + currentNode->color = replacementNodeInfo.color; + replacementNode->color = currentNodeInfo.color; + + // edit parent node + if (currentNodeInfo.parent != nullptr) { + if (currentNodeInfo.parent->leftChild == currentNode) { + currentNodeInfo.parent->leftChild = replacementNode; + } + else { + currentNodeInfo.parent->rightChild = replacementNode; + } + } + else { + this->root = replacementNode; + } + + + if (currentNodeInfo.rightChild != nullptr) { + currentNodeInfo.rightChild->parent = replacementNode; + } + + if (replacementNodeInfo.leftChild != nullptr) { + replacementNodeInfo.leftChild->parent = currentNode; + } + + currentNode->rightChild = nullptr; + currentNode->leftChild = replacementNodeInfo.leftChild; + if (replacementNodeInfo.parent == currentNode) { + currentNode->parent = replacementNode; + } + else { + currentNode->parent = replacementNodeInfo.parent; + } + + replacementNode->rightChild = currentNodeInfo.rightChild; + replacementNode->parent = currentNodeInfo.parent; + if (currentNodeInfo.leftChild == replacementNode) { + replacementNode->leftChild = currentNode; + } + else { + replacementNode->leftChild = currentNodeInfo.leftChild; + } + + if (currentNodeInfo.leftChild != replacementNode) { + currentNodeInfo.leftChild->parent = replacementNode; + replacementNodeInfo.parent->rightChild = currentNode; + } + } + } // while (currentNode->leftChild != nullptr || currentNode->rightChild != nullptr) + + + // now, node to be deleted has no child. + + if (currentNode == this->root) { // if deleting root + this->root = nullptr; + allocator->free(currentNode); + } + else if (currentNode->color == NodeColor::RED) { + if (currentNode == currentNode->parent->leftChild) { + currentNode->parent->leftChild = nullptr; + } + else { + currentNode->parent->rightChild = nullptr; + } + allocator->free(currentNode); + } + else { + // target node's parent must exists. + // because target is black, it must have sibling. + Node* sibling = (currentNode->parent->leftChild != currentNode ? + currentNode->parent->leftChild : currentNode->parent->rightChild); + + Node* currentparentParent = currentNode->parent; + + ChildSide siblingSideToparentParent = + (sibling == currentparentParent->leftChild ? ChildSide::LEFT : ChildSide::RIGHT); + + // now we can delete the node. + if (currentparentParent->leftChild == currentNode) { + currentparentParent->leftChild = nullptr; + } + else { + currentparentParent->rightChild = nullptr; + } + + allocator->free(currentNode); + + + // Next, let's consider different scenarios. + + if (currentparentParent->color == NodeColor::RED) { + // when parent is red, sibling must be red. + + if (sibling->leftChild != nullptr && sibling->rightChild != nullptr) + { + /* + X: Remove; R: Red; B: Black + R + / \ + X B + / \ + R R Or her symmetrical counterpart + */ + // sibling's children are red + // note: If her black uncle has child, his child must be red + // operation: rotate sibling, and rotate parent. set parent to black, sibling to red. + sibling->color = NodeColor::RED; + currentparentParent->color = NodeColor::BLACK; + + if (siblingSideToparentParent == ChildSide::RIGHT) { + sibling->rightChild->color = NodeColor::BLACK; + this->rotateLeft(currentparentParent); + } + else { + sibling->leftChild->color = NodeColor::BLACK; + this->rotateRight(currentparentParent); + } + } + else if (siblingSideToparentParent == ChildSide::RIGHT && sibling->leftChild != nullptr) + { + /* + X: Remove; R: Red; B: Black + R + / \ + X B + / + R + */ + + currentparentParent->color = NodeColor::BLACK; + this->rotateRight(sibling); + this->rotateLeft(currentparentParent); + } + else if (siblingSideToparentParent == ChildSide::LEFT && sibling->rightChild != nullptr) + { + /* + X: Remove; R: Red; B: Black + R + / \ + B X + \ + R + */ + + currentparentParent->color = NodeColor::BLACK; + this->rotateLeft(sibling); + this->rotateRight(currentparentParent); + } + else if (siblingSideToparentParent == ChildSide::RIGHT && sibling->rightChild != nullptr) { + /* + X: Remove; R: Red; B: Black + R + / \ + X B + \ + R + */ + currentparentParent->color = NodeColor::BLACK; + sibling->color = NodeColor::RED; + sibling->rightChild->color = NodeColor::BLACK; + this->rotateLeft(currentparentParent); + } + else if (siblingSideToparentParent == ChildSide::LEFT && sibling->leftChild != nullptr) + { + /* + X: Remove; R: Red; B: Black + R + / \ + B X + / + R + */ + currentparentParent->color = NodeColor::BLACK; + sibling->color = NodeColor::RED; + sibling->leftChild->color = NodeColor::BLACK; + this->rotateRight(currentparentParent); + } + else { // sibling has no child + sibling->color = NodeColor::RED; + currentparentParent->color = NodeColor::BLACK; + } + + } // currentparentParent->color == NodeColor::RED + else { // currentparentParent->color == NodeColor::BLACK + if (sibling->color == NodeColor::BLACK + && sibling->leftChild != nullptr && sibling->rightChild != nullptr) + { + // sibling is black, and it has two children. then these children must be red. + if (siblingSideToparentParent == ChildSide::RIGHT) { + /* + X: Remove; R: Red; B: Black + B + / \ + X B + / \ + R R + */ + sibling->leftChild->color = NodeColor::BLACK; + this->rotateRight(sibling); + this->rotateLeft(currentparentParent); + } + else { + /* + X: Remove; R: Red; B: Black + B + / \ + B X + / \ + R R + */ + sibling->rightChild->color = NodeColor::BLACK; + this->rotateLeft(sibling); + this->rotateRight(currentparentParent); + } + } + else if (sibling->color == NodeColor::BLACK && + (sibling->leftChild != nullptr || sibling->rightChild != nullptr)) + { + // sibling is black, and has one red child. + if (siblingSideToparentParent == ChildSide::RIGHT && sibling->rightChild != nullptr) { + /* + X: Remove; R: Red; B: Black + B + / \ + X B + \ + R + */ + sibling->rightChild->color = NodeColor::BLACK; + this->rotateLeft(currentparentParent); + } + else if (siblingSideToparentParent == ChildSide::RIGHT && sibling->leftChild != nullptr) + { + /* + X: Remove; R: Red; B: Black + B + / \ + X B + / + R + */ + sibling->leftChild->color = NodeColor::BLACK; + this->rotateRight(sibling); + this->rotateLeft(currentparentParent); + } + else if (siblingSideToparentParent == ChildSide::LEFT && sibling->leftChild != nullptr) + { + /* + X: Remove; R: Red; B: Black + B + / \ + B X + / + R + */ + sibling->leftChild->color = NodeColor::BLACK; + this->rotateRight(currentparentParent); + } + else { + /* + X: Remove; R: Red; B: Black + B + / \ + B X + \ + R + */ + sibling->rightChild->color = NodeColor::BLACK; + this->rotateLeft(sibling); + this->rotateRight(currentparentParent); + } + } + else if (sibling->color == NodeColor::BLACK) { + // black sibling without child + /* + B B + / \ -> / \ + X B X R + */ + if (currentparentParent->leftChild == nullptr) { + currentparentParent->rightChild->color = NodeColor::RED; + this->rebalanceChildren(currentparentParent); + } + else { + currentparentParent->leftChild->color = NodeColor::RED; + this->rebalanceChildren(currentparentParent); + } + } + else { + // sibling is red. then it must has two black children. + sibling->color = NodeColor::BLACK; + currentparentParent->color = NodeColor::RED; + if (siblingSideToparentParent == ChildSide::RIGHT) { + /* + B + / \ + X R + / \ + B B + */ + this->rotateLeft(currentparentParent); + this->rotateLeft(currentparentParent); + if (currentparentParent->rightChild != nullptr) { + this->rebalanceRedNode(currentparentParent->rightChild); + } + } + else { + /* + B + / \ + R X + / \ + B B + */ + this->rotateRight(currentparentParent); + this->rotateRight(currentparentParent); + if (currentparentParent->leftChild != nullptr) { + this->rebalanceRedNode(currentparentParent->leftChild); + } + } + } + } + } + + return *this; +} + + + +template +adl::size_t RedBlackTree::size() { + + adl::size_t count = 0; + for (const auto& _ : *this) { + count++; + } + + return count; +} + + +template +adl::size_t RedBlackTree::rangeScan( + const KeyType& lhs, + const KeyType& rhs, + void* data, + bool (*collector) (void* data, const KeyType&, const DataType&) +) { + + return this->root ? doRangeScan(this->root, lhs, rhs, data, collector) : 0; +} + + + +template +void RedBlackTree::cleanup(Node* node) +{ + if (node->leftChild != nullptr) { + cleanup(node->leftChild); + } + if (node->rightChild != nullptr) { + cleanup(node->rightChild); + } + allocator->free(node); +} + + +template +RedBlackTree::Node* RedBlackTree::locateNode(const KeyType& key) { + Node* currentNode = root; + while (currentNode != nullptr) { + if (key == currentNode->key) { + return currentNode; // key found + } + else if (currentNode->key > key) { + currentNode = currentNode->leftChild; // target is less than curr. search from left. + } + else { // key > currentNode->key + currentNode = currentNode->rightChild; // target is more than curr. search from right. + } + } + + return nullptr; // key not found. +} + + +template +adl::size_t RedBlackTree::doRangeScan( + const Node* node, + const KeyType& lhs, + const KeyType& rhs, + void* data, + bool (*collector) (void* data, const KeyType&, const DataType&) +) { + adl::size_t count = 0; + + auto left = node->leftChild; + auto right = node->rightChild; + + if (left && node->key >= lhs) + count += doRangeScan(left, lhs, rhs, data, collector); + + if (node->key >= lhs && rhs >= node->key) { + count++; + if (collector) { + collector(data, node->key, node->data); + } + } + + if (right && rhs >= node->key) + count += doRangeScan(right, lhs, rhs, data, collector); + + return count; +} + + +template +void RedBlackTree::rotateLeft(Node* node) +{ + Node* parent = node->parent; + Node* targetRoot = node->rightChild; + + // rebind root. + if (parent == nullptr) { + this->root = targetRoot; + } + else { + if (node == parent->leftChild) { + parent->leftChild = targetRoot; + } + else { + parent->rightChild = targetRoot; + } + } + targetRoot->parent = parent; + + node->rightChild = targetRoot->leftChild; // child might be nullptr + if (node->rightChild != nullptr) { // only when child is not null then we can bind parent node. + node->rightChild->parent = node; + } + targetRoot->leftChild = node; + node->parent = targetRoot; +} + + +template +void RedBlackTree::rotateRight(Node* node) +{ + Node* parent = node->parent; + Node* targetRoot = node->leftChild; + + if (parent == nullptr) { + this->root = targetRoot; + } + else { + if (node == parent->leftChild) { + parent->leftChild = targetRoot; + } + else { + parent->rightChild = targetRoot; + } + } + targetRoot->parent = parent; + + node->leftChild = targetRoot->rightChild; // child might be nullptr + if (node->leftChild != nullptr) { + node->leftChild->parent = node; + } + targetRoot->rightChild = node; + node->parent = targetRoot; +} + + +template +void RedBlackTree::rebalanceRedNode(Node* node) +{ + Node* currentNode = node; + + // Only when the current node is red is it possible to violate the red-black tree rule + // when the parent node is also red, thus requiring a repair operation. + while (currentNode->color == NodeColor::RED) { + Node* currentparentParent = currentNode->parent; + if (currentparentParent == nullptr) { + // currentNode is root + currentNode->color = NodeColor::BLACK; + break; + } + else if (currentparentParent->color == NodeColor::BLACK) { + // parent is black. no problem! + break; + } + + + // Arriving here indicates that both the parent and target nodes are colored red. + + // If a parent node is red, then it cannot be the root, so a grandparent must exist. + + Node* currentGrandpa = currentparentParent->parent; + + // find uncle (might be null) + Node* uncle = + (currentGrandpa->leftChild != currentparentParent ? + currentGrandpa->leftChild : currentGrandpa->rightChild); + + if (uncle != nullptr && uncle->color == NodeColor::RED) { + + uncle->color = NodeColor::BLACK; + currentparentParent->color = NodeColor::BLACK; + currentGrandpa->color = NodeColor::RED; + + currentNode = currentGrandpa; + } + else { + + if (currentparentParent == currentGrandpa->leftChild) { + if (currentNode == currentparentParent->leftChild) { + this->rotateRight(currentGrandpa); + // recolor + currentparentParent->color = NodeColor::BLACK; + currentGrandpa->color = NodeColor::RED; + } + else { + this->rotateLeft(currentparentParent); + this->rotateRight(currentGrandpa); + // recolor + currentGrandpa->color = NodeColor::RED; + currentNode->color = NodeColor::BLACK; + } + } // if (currentparentParent == currentGrandpa->leftChild) + else { + + if (currentNode == currentparentParent->rightChild) { + this->rotateLeft(currentGrandpa); + // recolor + currentparentParent->color = NodeColor::BLACK; + currentGrandpa->color = NodeColor::RED; + } + else { + this->rotateRight(currentparentParent); + this->rotateLeft(currentGrandpa); + // recolor + currentGrandpa->color = NodeColor::RED; + currentNode->color = NodeColor::BLACK; + } + } // if (currentparentParent != currentGrandpa->leftChild) + + return; + } + } +} + + +template +void RedBlackTree::rebalanceChildren(Node* node) +{ + Node* currentNode = node; + + while (currentNode != this->root) { + Node* currentparentParent = currentNode->parent; + Node* sibling = (currentparentParent->leftChild != currentNode ? + currentparentParent->leftChild : currentparentParent->rightChild); + ChildSide siblingSideToparentParent = + (sibling == currentparentParent->leftChild ? ChildSide::LEFT : ChildSide::RIGHT); + + if (currentparentParent->color == NodeColor::RED) { + + + if (siblingSideToparentParent == ChildSide::RIGHT + && sibling->leftChild->color == NodeColor::BLACK) + { + /* + R + / \ + X B + / \ + B ? + */ + this->rotateLeft(currentparentParent); + break; + } + else if (siblingSideToparentParent == ChildSide::LEFT + && sibling->rightChild->color == NodeColor::BLACK) + { + /* + R + / \ + B X + / \ + ? B + */ + this->rotateRight(currentparentParent); + break; + } + else if (siblingSideToparentParent == ChildSide::RIGHT + && sibling->rightChild->color == NodeColor::BLACK) + { + /* + R + / \ + X B + / \ + R B + */ + currentparentParent->color = NodeColor::BLACK; + sibling->color = NodeColor::RED; + this->rebalanceRedNode(sibling->leftChild); + break; + } + else if (siblingSideToparentParent == ChildSide::LEFT + && sibling->leftChild->color == NodeColor::BLACK) + { + /* + R + / \ + B X + / \ + B R + */ + currentparentParent->color = NodeColor::BLACK; + sibling->color = NodeColor::RED; + this->rebalanceRedNode(sibling->rightChild); + break; + } + else { // sibling has two red child + currentparentParent->color = NodeColor::BLACK; + sibling->color = NodeColor::RED; + if (siblingSideToparentParent == ChildSide::RIGHT) { + sibling->rightChild->color = NodeColor::BLACK; + this->rotateLeft(currentparentParent); + } + else { + sibling->leftChild->color = NodeColor::BLACK; + this->rotateRight(currentparentParent); + } + break; + } + } + else { // parent is black + if (sibling->color == NodeColor::RED) { // sibling is red + /* + B + / \ + X R + / \ + B B + + or her symmatrical counterpart + */ + currentparentParent->color = NodeColor::RED; + sibling->color = NodeColor::BLACK; + if (siblingSideToparentParent == ChildSide::RIGHT) { + + this->rotateLeft(currentparentParent); + } + else { + this->rotateRight(currentparentParent); + } + } + else { // sibling is black + if (sibling->leftChild->color == NodeColor::BLACK + && sibling->rightChild->color == NodeColor::BLACK) + { + /* + B + / \ + X B + / \ + B B + + or her symmatrical counterpart + */ + sibling->color = NodeColor::RED; + currentNode = currentparentParent; + } + else if (siblingSideToparentParent == ChildSide::RIGHT + && sibling->rightChild->color == NodeColor::RED) + { + /* + B + / \ + X B + / \ + ? R + + */ + sibling->rightChild->color = NodeColor::BLACK; + this->rotateLeft(currentparentParent); + break; + } + else if (siblingSideToparentParent == ChildSide::LEFT + && sibling->leftChild->color == NodeColor::RED) + { + /* + B + / \ + B X + / \ + R ? + + */ + sibling->leftChild->color = NodeColor::BLACK; + this->rotateRight(currentparentParent); + break; + } + else if (siblingSideToparentParent == ChildSide::RIGHT) + { + /* + B + / \ + X B + / \ + R B + + */ + sibling->leftChild->color = NodeColor::BLACK; + this->rotateRight(sibling); + this->rotateLeft(currentparentParent); + break; + } + else + { + /* + B + / \ + B X + / \ + B R + + */ + sibling->rightChild->color = NodeColor::BLACK; + this->rotateLeft(sibling); + this->rotateRight(currentparentParent); + break; + } + } + } + } +} + + +} // namespace adl + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/config.cpp b/projects/sel4test/apps/monkey-mnemosyne/src/adl/config.cpp new file mode 100644 index 0000000000000000000000000000000000000000..90c09a568d75a01ccc496d879a6c77cd4ec8a84a --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/config.cpp @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + * 设置。 + * + * 创建于 2023年7月2日 上海市嘉定区安亭镇 + */ + +// forked from yros stdlib. +// github.com/FlowerBlackG/YurongOS + +// modified for Amkos + +#include "./config.h" +#include "./sys/types.h" + +namespace adl { + +Allocator defaultAllocator; + +} + + + + +// by Gemini 3.0 Pro + +// ------------------------------------------------------------- +// 全局 operator delete 重载 +// ------------------------------------------------------------- +// 因为编译选项使用了 -nostdlib,C++ 编译器生成的隐式析构调用 +// 找不到标准库的 delete,必须手动链接到 libc 的 free。 + +extern "C" void free(void* ptr); // 声明 C 库的 free 函数 + +void operator delete(void* ptr) noexcept { + if (ptr) { + free(ptr); + } +} + +// 既然你在写 C++,为了保险起见,建议把 operator new 也加上, +// 否则万一哪里隐式用了 new 也会报错。 +extern "C" void* malloc(unsigned long size); + +void* operator new(unsigned long size) { + return malloc(size); +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/config.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/config.h new file mode 100644 index 0000000000000000000000000000000000000000..624834fbb2faae388188b4a97a51af614b6c4a30 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/config.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + * 设置。 + * + * 创建于 2023年7月2日 上海市嘉定区安亭镇 + */ + +// forked from yros stdlib. +// github.com/FlowerBlackG/YurongOS + +// modified for Amkos + +#pragma once + +#include "./Allocator.h" + + +namespace adl { + +extern Allocator defaultAllocator; + +} + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/endian.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/endian.h new file mode 100644 index 0000000000000000000000000000000000000000..a4764cea9e411c33d74fefe9c86159254feaa61e --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/endian.h @@ -0,0 +1,144 @@ +// 124033910070 GTY +// gongty [at] tongji [dot] edu [dot] cn + +// created on 2024.11.25 +// at Jiangchuan, Minhang, Shanghai + +// implements: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/endian.h.html + + +#pragma once + +#include "./stdint.h" + + +#ifndef __BYTE_ORDER__ + #error "__BYTE_ORDER__ undefined. Compiler you are using is not supported by us." +#endif + +#ifndef BYTE_ORDER + #define BYTE_ORDER __BYTE_ORDER__ +#endif + +#ifndef BIG_ENDIAN + #define BIG_ENDIAN __ORDER_BIG_ENDIAN__ +#endif + +#ifndef LITTLE_ENDIAN + #define LITTLE_ENDIAN __ORDER_LITTLE_ENDIAN__ +#endif + +#if ((BYTE_ORDER != BIG_ENDIAN) && (BYTE_ORDER != LITTLE_ENDIAN)) + #error "Strange system. Our system won't work on your device." +#endif + + +#if defined(_BYTEORDER_FUNC_DEFINED) +#error "Byteorder functions defined elsewhere. " \ + "You should include this header before any other." +#endif + +#define _BYTEORDER_FUNC_DEFINED + + +namespace adl { + + +inline uint16_t be16toh(uint16_t x) { +#if BYTE_ORDER == BIG_ENDIAN + return x; +#else // BYTE_ORDER == LITTLE_ENDIAN + return __builtin_bswap16(x); +#endif +} + + +inline uint32_t be32toh(uint32_t x) { +#if BYTE_ORDER == BIG_ENDIAN + return x; +#else // BYTE_ORDER == LITTLE_ENDIAN + return __builtin_bswap32(x); +#endif + +} +inline int32_t be32toh(int32_t x) { + return (int32_t) be32toh((uint32_t) x); +} + + +inline uint64_t be64toh(uint64_t x) { +#if BYTE_ORDER == BIG_ENDIAN + return x; +#else // BYTE_ORDER == LITTLE_ENDIAN + return __builtin_bswap64(x); +#endif + +} + + +inline int64_t be64toh(int64_t x) { + return (int64_t) be64toh((uint64_t) x); +} + + + +inline uint16_t htobe16(uint16_t x) { + return be16toh(x); +} +inline int32_t htobe32(int32_t x) { + return be32toh(x); +} +inline uint32_t htobe32(uint32_t x) { + return be32toh(x); +} +inline int64_t htobe64(int64_t x) { + return be64toh(x); +} +inline uint64_t htobe64(uint64_t x) { + return be64toh(x); +} + + + +inline uint16_t htole16(uint16_t x) { +#if BYTE_ORDER == BIG_ENDIAN + return __builtin_bswap16(x); +#else // BYTE_ORDER == LITTLE_ENDIAN + return x; +#endif + +} + + +inline uint32_t htole32(uint32_t x) { +#if BYTE_ORDER == BIG_ENDIAN + return __builtin_bswap32(x); +#else // BYTE_ORDER == LITTLE_ENDIAN + return x; +#endif +} + + +inline uint64_t htole64(uint64_t x) { +#if BYTE_ORDER == BIG_ENDIAN + return __builtin_bswap64(x); +#else // BYTE_ORDER == LITTLE_ENDIAN + return x; +#endif +} + + +inline uint16_t le16toh(uint16_t x) { + return htole16(x); +} + +inline uint32_t le32toh(uint32_t x) { + return htole32(x); +} + +inline uint64_t le64toh(uint64_t x) { + return htole64(x); +} + + +} // namespace adl diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/recursive_mutex b/projects/sel4test/apps/monkey-mnemosyne/src/adl/recursive_mutex new file mode 100644 index 0000000000000000000000000000000000000000..657752f37a389564e8870a577053bbd07715732f --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/recursive_mutex @@ -0,0 +1,84 @@ +/* + recursive lock + + Created on 2025.6.22 at Minhang + By gongty +*/ + +#pragma once + +#include +#include +#include +#include + +namespace adl { + + +class recursive_mutex { +public: + adl::int64_t count = 0; + Genode::Mutex countMutex; + Genode::Thread* owner; // only trusted when count is not 0. + + Genode::Semaphore waitSemaphore {1}; // used to wait for lock. + +public: + recursive_mutex() { + + } + + recursive_mutex(const recursive_mutex&) = delete; + recursive_mutex& operator=(const recursive_mutex&) = delete; + + + void lock() { + while (!try_lock()) + waitSemaphore.down(); + }; + + + bool try_lock() { + Genode::Mutex::Guard _g {countMutex}; + if (count > 0 && Genode::Thread::myself() == owner) { + count++; + return true; + } + else if (count == 0) { + owner = Genode::Thread::myself(); + count++; + return true; + } + else { + return false; + } + }; + + + void unlock() { + Genode::Mutex::Guard _g {countMutex}; + if (count > 0) { + count--; + if (count == 0) { + waitSemaphore.up(); + } + } + }; + + + class guard { + protected: + recursive_mutex& _mutex; + public: + explicit guard(recursive_mutex& mutex) : _mutex(mutex) { + _mutex.lock(); + }; + + ~guard() { + _mutex.unlock(); + }; + }; + +}; + +}; diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/stdarg.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/stdarg.h new file mode 100644 index 0000000000000000000000000000000000000000..e9fcb719dc62b29ef81f20667723c213779c6223 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/stdarg.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + * 可变参数。 + * 创建于 2022年7月13日。 + * + * 参考: + * https://pubs.opengroup.org/onlinepubs/9699919799/ + * https://cplusplus.com/reference/cstdarg/ + */ + +#pragma once + +#include // for NULL. + + +// 参考 linux5.19 include/linux/stdarg.h +// 原来 gcc 编译期已经实现了这个啊... 害得我思考半天怎么写... + +typedef __builtin_va_list va_list; + +#define va_start(ap, argN) __builtin_va_start(ap, argN) + +#define va_copy(dest, src) __builtin_va_copy(dest, src) + +#define va_arg(ap, type) __builtin_va_arg(ap, type) + +#define va_end(ap) __builtin_va_end(ap) diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/stdbool.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/stdbool.h new file mode 100644 index 0000000000000000000000000000000000000000..5ec6207dcc8640940fe5b3bf111f867bb55f6c47 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/stdbool.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + * 为 C 语言提供 bool 数据类型支持。 + */ + +// forked from yros stdlib. +// github.com/FlowerBlackG/YurongOS + +#pragma once + +#ifndef __cplusplus + #ifndef __bool_true_false_are_defined + #define bool _Bool + #define true 1 + #define false 0 + #define __bool_true_false_are_defined 1 + #endif +#endif diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/stddef.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/stddef.h new file mode 100644 index 0000000000000000000000000000000000000000..8fdc202437f99834304f0b9b311a2f811941967e --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/stddef.h @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + + stddef header + + 创建于 2023年2月12日 江西省上饶市玉山县 + +*/ + +// forked from yros stdlib. +// github.com/FlowerBlackG/YurongOS + +#pragma once + +#define offsetof(type, member) ((unsigned long) &((type*)0)->member) + +namespace adl { + +using nullptr_t = decltype(nullptr); + +} // namespace adl diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/stdint.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/stdint.h new file mode 100644 index 0000000000000000000000000000000000000000..c8a0659cb96c3f89cd6507c113041111839b85fb --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/stdint.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + * 整数类型头文件。 + * 创建于 2022年7月2日。 + */ + +#pragma once + +// forked from yros stdlib. +// github.com/FlowerBlackG/YurongOS + +// modified for Amkos + +namespace adl { + +typedef char int8_t; +typedef short int16_t; +typedef int int32_t; +typedef long long int64_t; +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long uint64_t; + +// 拥有最大宽度的整数类型。 +typedef int64_t intmax_t; +typedef uint64_t uintmax_t; + +#ifndef intptr_t +typedef int64_t intptr_t; +#endif + +#ifndef uintptr_t +typedef uint64_t uintptr_t; +#endif + +} + + +// https://github.com/openbsd/src/blob/master/sys/sys/stdint.h + +/* + * 7.18.2 Limits of specified-width integer types. + * + * The following object-like macros specify the minimum and maximum limits + * of integer types corresponding to the typedef names defined above. + */ + +/* 7.18.2.1 Limits of exact-width integer types */ +#define INT8_MIN (-0x7f - 1) +#define INT16_MIN (-0x7fff - 1) +#define INT32_MIN (-0x7fffffff - 1) +#define INT64_MIN (-0x7fffffffffffffffLL - 1) + +#define INT8_MAX 0x7f +#define INT16_MAX 0x7fff +#define INT32_MAX 0x7fffffff +#define INT64_MAX 0x7fffffffffffffffLL + +#define UINT8_MAX 0xff +#define UINT16_MAX 0xffff +#define UINT32_MAX 0xffffffffU +#define UINT64_MAX 0xffffffffffffffffULL + +/* 7.18.2.4 Limits of integer types capable of holding object pointers */ +#ifdef __LP64__ +#define INTPTR_MIN (-0x7fffffffffffffffL - 1) +#define INTPTR_MAX 0x7fffffffffffffffL +#define UINTPTR_MAX 0xffffffffffffffffUL +#else +#define INTPTR_MIN (-0x7fffffffL - 1) +#define INTPTR_MAX 0x7fffffffL +#define UINTPTR_MAX 0xffffffffUL +#endif + +/* 7.18.2.5 Limits of greatest-width integer types */ +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/string.cpp b/projects/sel4test/apps/monkey-mnemosyne/src/adl/string.cpp new file mode 100644 index 0000000000000000000000000000000000000000..305e652872059c33c29445485b03314857c9b536 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/string.cpp @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + * 字符串处理函数。 + * 创建于 2022年7月4日。 + */ + +// forked from yros stdlib. +// github.com/FlowerBlackG/YurongOS + +// modified for Amkos + +#include + +namespace adl { + +char* strcpy(char* dest, const char* src) { + char* pDest = dest; + const char* pSrc = src; + while ((*pDest++ = *pSrc++) != '\0') + ; + + return dest; +} + +char* strncpy(char* dest, const char* src, size_t count) { + char* pDest = dest; + const char* pSrc = src; + + while (pDest < ((char*) dest) + count) { + *pDest = *pSrc; + if (*pDest == '\0') { + break; + } else { + ++pDest; + ++pSrc; + } + } + + *pDest = '\0'; + + return dest; +} + +char* strcat(char* dest, const char* src) { + strcpy(dest + strlen(dest), src); + return dest; +} + +char* strncat(char* dest, const char* src, size_t count) { + strncpy(dest + strlen(dest), src, count); + return dest; +} + +size_t strlen(const char* str) { + // 参考 glibc 的实现方式。 + // 解释见:https://blog.csdn.net/m0_62405272/article/details/125600719 + + /* 先将指针与字节对齐。 + 将指针转换成 long 这种操作在有些平台会报错。注意,不等号的优先级高于与运算。 */ + const char* charPtr = str; + while (((long) charPtr & (sizeof(long) - 1)) != 0) { + if (*charPtr == '\0') { + return charPtr - str; + } else { + ++charPtr; + } + } + + const unsigned long* longPtr = (const unsigned long*) charPtr; + + unsigned long highMagic = 0x80808080L; + unsigned long lowMagic = 0x01010101L; + if (sizeof(long) > 4) { + highMagic = ((highMagic << 16) << 16) | highMagic; + lowMagic = ((lowMagic << 16) << 16) | lowMagic; + } + // 暂不考虑 long 的宽度超过 8 字节的情况。 + + while (true) { + const unsigned long& longWords = *longPtr++; + if (((longWords - lowMagic) & ~longWords & highMagic) != 0) { + auto prevLongPtr = longPtr - 1; + const char* p = (const char*) prevLongPtr; + while (p < ((const char*) prevLongPtr) + sizeof(long)) { + if (*p == '\0') { + return p - str; + } else { + ++p; + } + } + } + } +} + +int strcmp(const char* lhs, const char* rhs) { + const char* pLhs = lhs; + const char* pRhs = rhs; + + while (*pLhs == *pRhs) { + if (*pLhs == '\0') { + return 0; + } else { + ++pLhs; + ++pRhs; + } + } + + return *(const unsigned char*) pLhs - *(const unsigned char*) pRhs; +} + +int strncmp(const char* lhs, const char* rhs, size_t count) { + const char* pLhs = lhs; + const char* pRhs = rhs; + + while (pLhs < lhs + count) { + if (*pLhs != *pRhs) { + return *(const unsigned char*) pLhs - *(const unsigned char*) pRhs; + } + else if (*pLhs == '\0') { + return 0; + } + else { + ++pLhs; + ++pRhs; + } + } + + return 0; +} + +char* strchr(const char* str, int ch) { + char b = (char) ch; + const char* p = str; + while (*p != '\0') { + if (b == *p) { + return (char*) p; + } else { + ++p; + } + } + + return NULL; +} + +char* strrchr(const char* str, int ch) { + char b = (char) ch; + const char* p = str + strlen(str) - 1; + + while (str <= p) { + if (b == *p) { + return (char*) p; + } else { + --p; + } + } + + return NULL; +} + +size_t strspn(const char* dest, const char* src) { + bool tar[256]; // 暂不考虑对 C 语言的兼容问题。 + memset(tar, 0, sizeof(tar)); + + const unsigned char* p = (const unsigned char*) src; + while (*p != '\0') { + tar[*p++] = true; + } + + p = (const unsigned char*) dest; + while (tar[*p++]) + ; + + return p - (const unsigned char*) dest - 1; +} + +size_t strcspn(const char* dest, const char* src) { + bool tar[256]; // 暂不考虑对 C 语言的兼容问题。 + memset(tar, 0xff, sizeof(tar)); + + const unsigned char* p = (const unsigned char*) src; + while (*p != '\0') { + tar[*p++] = false; + } + + p = (const unsigned char*) dest; + while (tar[*p++]) + ; + + return p - (const unsigned char*) dest - 1; +} + +char* strpbrk(const char* dest, const char* breakset) { + bool tar[256]; + memset(tar, 0, sizeof(tar)); +; + const unsigned char* p = (const unsigned char*) breakset; + while (*p != '\0') { + tar[*p++] = true; + } + + p = (const unsigned char*) dest; + while (*p != '\0') { + if (tar[*p]) { + return (char*) p; + } else { + ++p; + } + } + + return NULL; +} + +char* strstr(const char* str, const char* substr) { + size_t lenOfSubstr = strlen(substr); + size_t lenOfStr = strlen(str); + const char* pStr = str; + while (lenOfSubstr + (pStr - str) <= lenOfStr) { + if (strncmp(pStr, substr, lenOfSubstr) == 0) { + return (char*) pStr; + } else { + ++pStr; + } + } + + return NULL; + + // 注意:现在这种算法效率很低。可以考虑研究更好的算法。 +} + +char* strtok(char* str, const char* delim) { + bool tar[256]; + memset(tar, 0, sizeof(tar)); + + const unsigned char* p = (const unsigned char*) delim; + while (*p != 0) { + tar[*p++] = true; + } + + static unsigned char* searchPtr = (unsigned char*) str; + if (str != NULL) { + searchPtr = (unsigned char*) str; + } + + char* pToken = NULL; + while (*searchPtr != '\0') { + if (p[*searchPtr]) { + if (pToken != NULL) { + *searchPtr = '\0'; + ++searchPtr; + return pToken; + } + } else if (pToken == NULL) { + pToken = (char*) str; + } + + ++searchPtr; + } + + return NULL; +} + +void* memchr(const void* ptr, int ch, size_t count) { + auto* cPtr = (const unsigned char*) ptr; + auto b = (unsigned char) ch; + while (cPtr < ((const unsigned char*) ptr) + count) { + if (b == *cPtr) { + return (void*) cPtr; + } else { + ++cPtr; + } + } + + return nullptr; +} + +int memcmp(const void* lhs, const void* rhs, size_t count) { + auto pLhs = (const unsigned char*) lhs; + auto pRhs = (const unsigned char*) rhs; + while (pRhs < ((const unsigned char*) rhs) + count) { + if (*pLhs != *pRhs) { + return *pLhs - *pRhs; + } else { + ++pLhs; + ++pRhs; + } + } + + return 0; +} + +void* memset(void* dest, int ch, size_t count) { + char b = (char) ch; + char* p = (char*) dest; + while (p < ((char*) dest) + count) { + *p++ = b; + } + + return dest; +} + +/** + * 快速内存拷贝。调用时,假设传入的内存可以互相对齐,否则性能会降低。 + * + * 传统内存拷贝时,一次只能拷贝1个字节,速度较慢。 + * 本方法在观察到源地址和目的地址低 n 位相同时, + * 每次拷贝一个 CPU 字长。其中,n 为机器字长除以字节长度 8。 + * + * 例如,当 src 为 0x345A, dest 为 0x987A 时, + * src 和 dest 的低 4 位相同。当机器字长为 4 位时,可以先使用单字节拷贝方法, + * 将 src 和 dest 与内存对齐。将 src 和 dest 分别对齐到 0b..000, + * 即可借助机器字长的优势,一次性拷贝更多字符。 + * + * 覆盖问题:memcpy 本身不允许拷贝区间互相覆盖。因此,本方法不保证此方面安全性。 + * 短拷贝:当 src 和 dest 的距离小于一个寄存器的长度时,根本无法互相对齐。因此,不存在此问题。 + * + * @param dest 拷贝目标地址。需要与 src 距离对齐,不要求与内存单元对齐。 + * @param src 拷贝源地址。 + * @param count 拷贝字符总数。 + * @return dest + */ +static inline void* memcpy_aligned(void* dest, const void* src, size_t count) { + size_t remain = count; + char* pDest = (char*) dest; + char* pSrc = (char*) src; + while (pSrc < ((char*) src) + count && (uintptr_t(pSrc) & (sizeof(long) - 1))) { + *pDest++ = *pSrc++; + remain--; + } + + while (remain >= sizeof(long)) { + *(long*) pDest = *(long*) pSrc; + pSrc += sizeof(long); + pDest += sizeof(long); + remain -= sizeof(long); + } + + while (remain) { + *pDest++ = *pSrc++; + remain--; + } + + return dest; + +} + + +void* memcpy(void* dest, const void* src, size_t count) { + + // 如果 src 和 dest 可以互相对齐,则使用快速拷贝。 + if (!((uintptr_t(dest) ^ uintptr_t(src)) & (sizeof(long) - 1))) { + return memcpy_aligned(dest, src, count); + } + + // 单字节拷贝。 + char* pDest = (char*) dest; + char* pSrc = (char*) src; + while (pSrc < ((char*) src) + count) { + *pDest++ = *pSrc++; + } + + return dest; +} + +} + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/string.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/string.h new file mode 100644 index 0000000000000000000000000000000000000000..84fd93cd5dc5ab159c66dd88aa862612ea7681d4 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/string.h @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + * 字符串处理函数头文件。 + * 创建于 2022年7月4日。 + * + * 参考: + * cppreference.com. Null-terminated byte strings + * https://en.cppreference.com/w/c/string/byte + */ + +#pragma once + +#include "./sys/types.h" + + +// forked from yros stdlib. +// github.com/FlowerBlackG/YurongOS + +// modified for Amkos + +namespace adl { + +char* strcpy(char* dest, const char* src); +char* strncpy(char* dest, const char* src, size_t count); +char* strcat(char* dest, const char* src); +char* strncat(char* dest, const char* src, size_t count); +size_t strlen(const char* str); +int strcmp(const char* lhs, const char* rhs); +int strncmp(const char* lhs, const char* rhs, size_t count); +char* strchr(const char* str, int ch); +char* strrchr(const char* str, int ch); +size_t strspn(const char* dest, const char* src); +size_t strcspn(const char* dest, const char* src); +char* strpbrk(const char* dest, const char* breakset); +char* strstr(const char* str, const char* substr); +char* strtok(char* str, const char* delim); + +void* memchr(const void* ptr, int ch, size_t count); +int memcmp(const void* lhs, const void* rhs, size_t count); +void* memset(void* dest, int ch, size_t count); +void* memcpy(void* dest, const void* src, size_t count); + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/sys/types.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/sys/types.h new file mode 100644 index 0000000000000000000000000000000000000000..0e0f9d73b4d92df9d358b7a130d7bb3ad3a7c88c --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/sys/types.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: MulanPSL-2.0 + +/* + * 数据类型定义。 + * 创建于 2022年7月2日。 + */ + +// forked from yros stdlib. +// github.com/FlowerBlackG/YurongOS + +// modified for Amkos + +#pragma once + +#include "../stdint.h" + +/** 文件结尾。 */ +#ifndef EOF + #define EOF -1 +#endif + +/** 空指针。 */ +#ifndef NULL + #define NULL 0 +#endif + +#ifdef ADL_DEFINE_GCC_SHORT_MACROS +/** 结构体紧凑。 */ +#ifndef __packed + #define __packed __attribute__((packed)) +#endif + +#ifndef __no_return + #define __no_return __attribute__((__noreturn__)) +#endif + +/** 令函数不保存 rsp 和 rbp。 */ +#ifndef __omit_frame_pointer + #define __omit_frame_pointer __attribute__((optimize("omit-frame-pointer"))) +#endif + +/** 强制内联。 */ +#ifndef __force_inline + #define __force_inline __attribute__((always_inline)) inline +#endif + +/** 内联汇编。 */ +#ifndef __asm + #define __asm __asm__ __volatile__ +#endif + +/** 指定代码存放的区域。 */ +#ifndef __section + #define __section(name) __attribute__((section(name))) +#endif +#endif +namespace adl { + +typedef unsigned long size_t; + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/type_traits b/projects/sel4test/apps/monkey-mnemosyne/src/adl/type_traits new file mode 100644 index 0000000000000000000000000000000000000000..1296c0ff578e78b5553e39eddd8d7e1285766c95 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/type_traits @@ -0,0 +1,27 @@ +// 124033910070 GTY +// gongty [at] tongji [dot] edu [dot] cn + +// created on 2024.11.25 +// at Jiangchuan, Minhang, Shanghai + +// implements: https://cplusplus.com/reference/type_traits + + +#pragma once + +namespace adl { + + +struct true_type { + static constexpr bool value = true; + constexpr operator bool() const noexcept { return value; } +}; + +struct false_type { + static constexpr bool value = false; + constexpr operator bool() const noexcept { return value; } +}; + + +} // namespace adl + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/utility b/projects/sel4test/apps/monkey-mnemosyne/src/adl/utility new file mode 100644 index 0000000000000000000000000000000000000000..18304999fb48ce957bf0b854cb77ed1e8e60519c --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/utility @@ -0,0 +1,43 @@ +// 124033910070 GTY +// gongty [at] tongji [dot] edu [dot] cn + +// created on 2024.11.25 +// at Jiangchuan, Minhang, Shanghai + +// reference: std: + +#pragma once + +namespace adl { + + +template +struct pair { + U first; + V second; + pair(const U& first = U(), const V& second = V()) : first(first), second(second) {} +}; + + +template +inline pair make_pair(const U& first, const V& second) { + return pair {first, second}; +} + + +template +struct ref_pair { + U& first; + V& second; + ref_pair(U& first, V& second) : first(first), second(second) {} +}; + + +template +inline ref_pair make_ref_pair(U& first, V& second) { + return ref_pair {first, second}; +} + + + +} // namespace adl diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/wl_list.cpp b/projects/sel4test/apps/monkey-mnemosyne/src/adl/wl_list.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0ff68670b52f159fb3c47f88039ecdc98d66eea7 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/wl_list.cpp @@ -0,0 +1,97 @@ +/* + * Copyright © 2008-2011 Kristian Høgsberg + * Copyright © 2011 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +/* + Modified from Wayland + https://gitlab.freedesktop.org/wayland/wayland + + For Amkos ADL use. + +*/ + + +#include + + +void +wl_list_init(struct wl_list *list) +{ + list->prev = list; + list->next = list; +} + +void +wl_list_insert(struct wl_list *list, struct wl_list *elm) +{ + elm->prev = list; + elm->next = list->next; + list->next = elm; + elm->next->prev = elm; +} + +void +wl_list_remove(struct wl_list *elm) +{ + elm->prev->next = elm->next; + elm->next->prev = elm->prev; + elm->next = nullptr; + elm->prev = nullptr; +} + +int +wl_list_length(const struct wl_list *list) +{ + struct wl_list *e; + int count; + + count = 0; + e = list->next; + while (e != list) { + e = e->next; + count++; + } + + return count; +} + +int +wl_list_empty(const struct wl_list *list) +{ + return list->next == list; +} + +void +wl_list_insert_list(struct wl_list *list, struct wl_list *other) +{ + if (wl_list_empty(other)) + return; + + other->next->prev = list; + other->prev->next = list->next; + list->next->prev = other->prev; + list->next = other->next; +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/adl/wl_list.h b/projects/sel4test/apps/monkey-mnemosyne/src/adl/wl_list.h new file mode 100644 index 0000000000000000000000000000000000000000..8e5b8a70773085360fbe70ca68bfda5ea74c6c26 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/adl/wl_list.h @@ -0,0 +1,324 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +/* + Modified from Wayland + https://gitlab.freedesktop.org/wayland/wayland + + For Amkos ADL use. + +*/ + +#pragma once + +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L +#define WL_TYPEOF(expr) typeof(expr) +#else +#define WL_TYPEOF(expr) __typeof__(expr) +#endif + + +/** \class wl_list + * + * \brief Doubly-linked list + * + * On its own, an instance of `struct wl_list` represents the sentinel head of + * a doubly-linked list, and must be initialized using wl_list_init(). + * When empty, the list head's `next` and `prev` members point to the list head + * itself, otherwise `next` references the first element in the list, and `prev` + * refers to the last element in the list. + * + * Use the `struct wl_list` type to represent both the list head and the links + * between elements within the list. Use wl_list_empty() to determine if the + * list is empty in O(1). + * + * All elements in the list must be of the same type. The element type must have + * a `struct wl_list` member, often named `link` by convention. Prior to + * insertion, there is no need to initialize an element's `link` - invoking + * wl_list_init() on an individual list element's `struct wl_list` member is + * unnecessary if the very next operation is wl_list_insert(). However, a + * common idiom is to initialize an element's `link` prior to removal - ensure + * safety by invoking wl_list_init() before wl_list_remove(). + * + * Consider a list reference `struct wl_list foo_list`, an element type as + * `struct element`, and an element's link member as `struct wl_list link`. + * + * The following code initializes a list and adds three elements to it. + * + * \code + * struct wl_list foo_list; + * + * struct element { + * int foo; + * struct wl_list link; + * }; + * struct element e1, e2, e3; + * + * wl_list_init(&foo_list); + * wl_list_insert(&foo_list, &e1.link); // e1 is the first element + * wl_list_insert(&foo_list, &e2.link); // e2 is now the first element + * wl_list_insert(&e2.link, &e3.link); // insert e3 after e2 + * \endcode + * + * The list now looks like [e2, e3, e1]. + * + * The `wl_list` API provides some iterator macros. For example, to iterate + * a list in ascending order: + * + * \code + * struct element *e; + * wl_list_for_each(e, foo_list, link) { + * do_something_with_element(e); + * } + * \endcode + * + * See the documentation of each iterator for details. + * \sa http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/include/linux/list.h + */ +struct wl_list { + /** Previous list element */ + struct wl_list *prev; + /** Next list element */ + struct wl_list *next; +}; + + + + +/** + * Initializes the list. + * + * \param list List to initialize + * + * \memberof wl_list + */ +void +wl_list_init(struct wl_list *list); + +/** + * Inserts an element into the list, after the element represented by \p list. + * When \p list is a reference to the list itself (the head), set the containing + * struct of \p elm as the first element in the list. + * + * \note If \p elm is already part of a list, inserting it again will lead to + * list corruption. + * + * \param list List element after which the new element is inserted + * \param elm Link of the containing struct to insert into the list + * + * \memberof wl_list + */ +void +wl_list_insert(struct wl_list *list, struct wl_list *elm); + +/** + * Removes an element from the list. + * + * \note This operation leaves \p elm in an invalid state. + * + * \param elm Link of the containing struct to remove from the list + * + * \memberof wl_list + */ +void +wl_list_remove(struct wl_list *elm); + +/** + * Determines the length of the list. + * + * \note This is an O(n) operation. + * + * \param list List whose length is to be determined + * + * \return Number of elements in the list + * + * \memberof wl_list + */ +int +wl_list_length(const struct wl_list *list); + +/** + * Determines if the list is empty. + * + * \param list List whose emptiness is to be determined + * + * \return 1 if empty, or 0 if not empty + * + * \memberof wl_list + */ +int +wl_list_empty(const struct wl_list *list); + +/** + * Inserts all of the elements of one list into another, after the element + * represented by \p list. + * + * \note This leaves \p other in an invalid state. + * + * \param list List element after which the other list elements will be inserted + * \param other List of elements to insert + * + * \memberof wl_list + */ +void +wl_list_insert_list(struct wl_list *list, struct wl_list *other); + +/** + * Retrieves a pointer to a containing struct, given a member name. + * + * This macro allows "conversion" from a pointer to a member to its containing + * struct. This is useful if you have a contained item like a wl_list, + * wl_listener, or wl_signal, provided via a callback or other means, and would + * like to retrieve the struct that contains it. + * + * To demonstrate, the following example retrieves a pointer to + * `example_container` given only its `destroy_listener` member: + * + * \code + * struct example_container { + * struct wl_listener destroy_listener; + * // other members... + * }; + * + * void example_container_destroy(struct wl_listener *listener, void *data) + * { + * struct example_container *ctr; + * + * ctr = wl_container_of(listener, ctr, destroy_listener); + * // destroy ctr... + * } + * \endcode + * + * \note `sample` need not be a valid pointer. A null or uninitialised pointer + * is sufficient. + * + * \param ptr Valid pointer to the contained member + * \param sample Pointer to a struct whose type contains \p ptr + * \param member Named location of \p ptr within the \p sample type + * + * \return The container for the specified pointer + */ +#define wl_container_of(ptr, sample, member) \ + (WL_TYPEOF(sample))((char *)(ptr) - \ + offsetof(WL_TYPEOF(*sample), member)) + +/** + * Iterates over a list. + * + * This macro expresses a for-each iterator for wl_list. Given a list and + * wl_list link member name (often named `link` by convention), this macro + * assigns each element in the list to \p pos, which can then be referenced in + * a trailing code block. For example, given a wl_list of `struct message` + * elements: + * + * \code + * struct message { + * char *contents; + * wl_list link; + * }; + * + * struct wl_list *message_list; + * // Assume message_list now "contains" many messages + * + * struct message *m; + * wl_list_for_each(m, message_list, link) { + * do_something_with_message(m); + * } + * \endcode + * + * \param pos Cursor that each list element will be assigned to + * \param head Head of the list to iterate over + * \param member Name of the link member within the element struct + * + * \relates wl_list + */ +#define wl_list_for_each(pos, head, member) \ + for (pos = wl_container_of((head)->next, pos, member); \ + &pos->member != (head); \ + pos = wl_container_of(pos->member.next, pos, member)) + +/** + * Iterates over a list, safe against removal of the list element. + * + * \note Only removal of the current element, \p pos, is safe. Removing + * any other element during traversal may lead to a loop malfunction. + * + * \sa wl_list_for_each() + * + * \param pos Cursor that each list element will be assigned to + * \param tmp Temporary pointer of the same type as \p pos + * \param head Head of the list to iterate over + * \param member Name of the link member within the element struct + * + * \relates wl_list + */ +#define wl_list_for_each_safe(pos, tmp, head, member) \ + for (pos = wl_container_of((head)->next, pos, member), \ + tmp = wl_container_of((pos)->member.next, tmp, member); \ + &pos->member != (head); \ + pos = tmp, \ + tmp = wl_container_of(pos->member.next, tmp, member)) + +/** + * Iterates backwards over a list. + * + * \sa wl_list_for_each() + * + * \param pos Cursor that each list element will be assigned to + * \param head Head of the list to iterate over + * \param member Name of the link member within the element struct + * + * \relates wl_list + */ +#define wl_list_for_each_reverse(pos, head, member) \ + for (pos = wl_container_of((head)->prev, pos, member); \ + &pos->member != (head); \ + pos = wl_container_of(pos->member.prev, pos, member)) + +/** + * Iterates backwards over a list, safe against removal of the list element. + * + * \note Only removal of the current element, \p pos, is safe. Removing + * any other element during traversal may lead to a loop malfunction. + * + * \sa wl_list_for_each() + * + * \param pos Cursor that each list element will be assigned to + * \param tmp Temporary pointer of the same type as \p pos + * \param head Head of the list to iterate over + * \param member Name of the link member within the element struct + * + * \relates wl_list + */ +#define wl_list_for_each_reverse_safe(pos, tmp, head, member) \ + for (pos = wl_container_of((head)->prev, pos, member), \ + tmp = wl_container_of((pos)->member.prev, tmp, member); \ + &pos->member != (head); \ + pos = tmp, \ + tmp = wl_container_of(pos->member.prev, tmp, member)) + + + \ No newline at end of file diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/base/log.h b/projects/sel4test/apps/monkey-mnemosyne/src/base/log.h new file mode 100644 index 0000000000000000000000000000000000000000..a9f6334d7c5460506f46e717ace7875c44a82e31 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/base/log.h @@ -0,0 +1,60 @@ +// Mock Genode::log +// by Gemini 3.0 Pro + +#pragma once + +#include + +namespace Genode { + + // --- 1. Helper overloads --- + + // Signed integers + inline void print_arg(int v) { printf("%d", v); } + inline void print_arg(long v) { printf("%ld", v); } + inline void print_arg(long long v) { printf("%lld", v); } + + // Unsigned integers (Fixes "ambiguous overload" errors) + inline void print_arg(unsigned int v) { printf("%u", v); } + inline void print_arg(unsigned long v) { printf("%lu", v); } + inline void print_arg(unsigned long long v) { printf("%llu", v); } + + // Pointers and Strings + inline void print_arg(const char* v) { printf("%s", v); } + inline void print_arg(void* v) { printf("%p", v); } + + // --- 2. Base cases (Fixes "no matching function" errors) --- + + inline void log() { + printf("\n"); + } + + inline void error() { + printf("\n"); + } + + inline void warning() { + printf("\n"); + } + + // --- 3. Recursive variadic templates --- + + template + inline void log(T value, Args... args) { + print_arg(value); + log(args...); // Recurse + } + + template + inline void error(T value, Args... args) { + print_arg(value); + error(args...); // Recurse + } + + template + inline void warning(T value, Args... args) { + print_arg(value); + warning(args...); // Recurse + } + +} \ No newline at end of file diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/config.h b/projects/sel4test/apps/monkey-mnemosyne/src/config.h new file mode 100644 index 0000000000000000000000000000000000000000..a4192e705d91f32c294180ab26cb8781ffba649f --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/config.h @@ -0,0 +1,28 @@ +/* + * Monkey Lab for JK. + * + * Created on 2025.2.14 at Yushan, Shangrao + * + * + * gongty [at] tongji [dot] edu [dot] cn + * + */ + + +#pragma once + +#include + +enum class LabApp { + App1, + App2 +}; + +struct { + const char* mnemosyneIp = ""; + adl::uint16_t mnemosynePort = 10100; + + const char* app1Key = "f578bd06-6f8e-42b3-8be9-860c7c645549"; + const char* app2Key = "8370c1fe-d422-42d9-a261-05aed72313c3"; + LabApp labApp = LabApp::App1; +} static const labConfig; diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/lab-main.cc b/projects/sel4test/apps/monkey-mnemosyne/src/lab-main.cc new file mode 100644 index 0000000000000000000000000000000000000000..4aabc68494e9374785d2e8d4a5d51c645acfb503 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/lab-main.cc @@ -0,0 +1,227 @@ +// ddst.sjtu.edu.cn + +#include + +#include + +#include + +#include +#include +#include + +#include + +#include "config.h" + +using namespace monkey; + +using HelloMode = net::ProtocolConnection::HelloMode; + + +static adl::int64_t getPageWriteKey(const adl::int64_t keyBase) { + return 0x8000000000000000LL + keyBase; +} + +static adl::int64_t getPageReadKey(const adl::int64_t keyBase) { + return keyBase; +} + +static adl::int64_t nextPageAccessKey() { + static adl::int64_t nextKey = 10000001; + return nextKey++; +} + + +static void initAdlAlloc() { + + static struct { + + } adlAllocData; + + + adl::defaultAllocator.init({ + .alloc = [] (adl::size_t size, void* data) { + return malloc(size); + }, + + .free = [] (void* addr, adl::size_t size, void* data) { + free(addr); + }, + + .data = &adlAllocData + }); +} + + +static void init() { + initAdlAlloc(); +} + + +#define MK_PROTO_DO_RETURN_ON_ERR(call, prompt) do { \ + monkey::Status status = call; \ + if (status != monkey::Status::SUCCESS) { \ + Genode::error(prompt); \ + return status; \ + } \ +} while (0) + + +static Status createMnemosyneSession(net::Protocol2Connection& client) { + MK_PROTO_DO_RETURN_ON_ERR(client.connect(), "Failed to connect with server."); + MK_PROTO_DO_RETURN_ON_ERR(client.hello(2, HelloMode::CLIENT), "Failed to say hello to server."); + + const char* appKey = (labConfig.labApp == LabApp::App1) ? labConfig.app1Key : labConfig.app2Key; + + MK_PROTO_DO_RETURN_ON_ERR(client.auth(appKey), "Failed to auth with server."); + + return monkey::Status::SUCCESS; +} + + +struct MonkeySharedPage { + adl::uint64_t magics[4]; + + adl::int64_t strVer; + + char str[1024]; + + adl::int64_t zero0; + + adl::int8_t paddings[3024]; + +} __attribute__((__packed__)); + +static const adl::uint64_t monkeyPageMagics[4] = { + 0x544b65123da24f5aLL, 0x8ec8154e934c885dLL, + 0xdc8b8114553c472cLL, 0xa463d81acd9e4e22LL +}; + + +static char localPage[4096] __attribute__((__aligned__(4096))); + +static adl::int64_t currentStrVer = 1; + +static_assert(sizeof(MonkeySharedPage) == 4096, "MonkeySharedPage must be 4KB."); + + +static Status waitDataUpdate(net::Protocol2Connection& client, const adl::int64_t blockId) { + while (true) { + usleep(1000 * 1000); + MK_PROTO_DO_RETURN_ON_ERR(client.readBlock(blockId, localPage), "Failed to read page from server."); + + if (currentStrVer != ((MonkeySharedPage*)localPage)->strVer) { + currentStrVer = ((MonkeySharedPage*)localPage)->strVer; + break; + } + } + + return monkey::Status::SUCCESS; +} + + +static Status doLabApp1(monkey::net::Protocol2Connection& client) { + MonkeySharedPage& sharedPage = *(MonkeySharedPage*)localPage; + + // alloc a page. + + adl::int64_t blockId; + MK_PROTO_DO_RETURN_ON_ERR(client.tryAlloc(&blockId), "Failed to alloc page from server."); + + adl::memcpy(sharedPage.magics, monkeyPageMagics, sizeof(sharedPage.magics)); + adl::strcpy(sharedPage.str, "Hello from DDST, Shanghai Jiao Tong University! This is Lab App 1 speaking."); + sharedPage.strVer = currentStrVer; + sharedPage.zero0 = 0; + + // upload page to mnemosyne. + MK_PROTO_DO_RETURN_ON_ERR(client.writeBlock(blockId, localPage), "Failed to write page to server."); + + // wait for data change. + MK_PROTO_DO_RETURN_ON_ERR(waitDataUpdate(client, blockId), "Failed to wait for data update."); + Genode::log("Data updated, new strVer: ", currentStrVer); + Genode::log("New str: ", ((MonkeySharedPage*)localPage)->str); + + // write response. + + adl::strcpy(sharedPage.str, "Got your message, Lab App 1 here. Nice to meet you!"); + sharedPage.strVer = ++currentStrVer; + MK_PROTO_DO_RETURN_ON_ERR(client.writeBlock(blockId, localPage), "Failed to write page to server."); + + // unref the page. + MK_PROTO_DO_RETURN_ON_ERR(client.unrefBlock(blockId), "Failed to unref block."); + + + return monkey::Status::SUCCESS; +} + + +static Status doLabApp2(monkey::net::Protocol2Connection& client) { + MonkeySharedPage& sharedPage = *(MonkeySharedPage*)localPage; + + auto keyBase = nextPageAccessKey(); + auto writeKey = getPageWriteKey(keyBase); + + // ref the page. + adl::int64_t blockId; + while (client.refBlock(writeKey, &blockId) == Status::PROTOCOL_ERROR) { + usleep(1907 * 1000); + } + + // wait for magic match. + while (true) { + usleep(1000 * 1000); + MK_PROTO_DO_RETURN_ON_ERR(client.readBlock(blockId, localPage), "Failed to read page from server."); + if (adl::memcmp(sharedPage.magics, monkeyPageMagics, sizeof(sharedPage.magics)) == 0) { + break; + } + } + + // read the string. + currentStrVer = sharedPage.strVer; + Genode::log("Got strVer ", currentStrVer, " and str: ", sharedPage.str); + + // write response. + adl::strcpy(sharedPage.str, "Got your message, Lab App 2 here. Nice to meet you!"); + sharedPage.strVer = ++currentStrVer; + MK_PROTO_DO_RETURN_ON_ERR(client.writeBlock(blockId, localPage), "Failed to write page to server."); + + // read response from server. + MK_PROTO_DO_RETURN_ON_ERR(waitDataUpdate(client, blockId), "Failed to wait for data update."); + Genode::log("Data updated, new strVer: ", currentStrVer); + Genode::log("New str: ", ((MonkeySharedPage*)localPage)->str); + + // unref the page. + MK_PROTO_DO_RETURN_ON_ERR(client.unrefBlock(blockId), "Failed to unref block."); + + + return monkey::Status::SUCCESS; +} + + +int main() { + init(); + + monkey::net::Protocol2Connection client; + client.ip.set(labConfig.mnemosyneIp); + client.port = labConfig.mnemosynePort; + + if (createMnemosyneSession(client) != monkey::Status::SUCCESS) { + Genode::error("Failed to create session with server."); + client.close(); + return -1; + } + + if (labConfig.labApp == LabApp::App1) { + doLabApp1(client); + } + else if (labConfig.labApp == LabApp::App2) { + doLabApp2(client); + } + else { + Genode::error("Unknown lab app."); + } + + client.close(); + return 0; +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/mnemosyne_api.cc b/projects/sel4test/apps/monkey-mnemosyne/src/mnemosyne_api.cc new file mode 100644 index 0000000000000000000000000000000000000000..80fd2adb289598babeb158007dd609c897ad65dc --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/mnemosyne_api.cc @@ -0,0 +1,520 @@ +/** + * @file mnemosyne_api.cc + * @brief Pure-C facade for monkey-mnemosyne — implementation. + * + * Bridges plain-C callers (e.g. apps/front-style validation apps) into + * the existing C++ Protocol2Connection client. See + * include/mnemosyne_api.h for the contract. + * + * Internally this is a thin shim: + * + * mnemosyne_engine_init() + * -> read CH2 vaddrs from IPC MR(8/9/10) (FIRST!) + * -> initAdlAllocOnce() + * -> HyperAmpBridge::instance().init(2, tx, rx, dt) + * -> remember server ip / port / auth_key / role + * + * mnemosyne_sess_new() + * -> new Protocol2Connection + * + * mnemosyne_sess_connect_by_addrstr_devid(sess, proto, "ip:port", dev_id) + * -> parse "ip:port" + * -> Socket4::connect() (routed via HyperAmpBridge) + * -> Protocol1Connection::hello(2, CLIENT) + * -> Protocol1Connection::auth(auth_key) + * -> role == ALLOC : tryAlloc() -> remember blockId + * -> role == REF : refBlock(key) -> remember blockId + * + * mnemosyne_sess_send() / recv() + * -> single 4 KiB writeBlock() / readBlock() + * + * mnemosyne_sess_close() + * -> unrefBlock() (best-effort) + Protocol2Connection::close() + delete + * + * gongty [at] tongji [dot] edu [dot] cn + */ + +#include "../include/mnemosyne_api.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include + +extern "C" { +#include +} + + +/* ====================================================================== */ +/* Internal state */ +/* ====================================================================== */ + +namespace { + +/* + * Global engine state. monkey-mnemosyne does not have a per-engine + * object; HyperAmpBridge is already a singleton, and we only need a + * handful of scalars to remember what mnemosyne_engine_init() was called + * with. Keeping them file-static avoids exposing any C++ types through + * the C ABI. + */ +struct EngineState { + bool inited = false; + char ip[64] = {0}; + adl::uint16_t port = 0; + char authKey[128] = {0}; + adl::uint8_t channelId = MNEMOSYNE_CHANNEL_DEFAULT; + adl::uint8_t role = MNEMOSYNE_ROLE_ALLOC; +}; + +EngineState g_engine; + +/* + * Predictable access key shared between the alloc-side and the ref-side. + * + * TODO(gty 2026): this assumes (a) exactly one alloc-side and one + * ref-side per deployment and (b) the alloc-side runs first. If + * multiple sessions ever coexist or the order is not guaranteed this + * scheme breaks. Lift the access key out into mnemosyne_engine_init() + * as a proper parameter once the integrator confirms the deployment + * topology. + */ +constexpr adl::int64_t kPredictableKeyBase = 10000001; +constexpr adl::int64_t kPageWriteKeyBit = 0x8000000000000000LL; + +inline adl::int64_t predictableWriteKey() { + return kPageWriteKeyBit + kPredictableKeyBase; +} + +bool g_adlAllocInited = false; + +void initAdlAllocOnce() { + if (g_adlAllocInited) return; + + static struct { + } adlAllocData; + + adl::defaultAllocator.init({ + .alloc = [] (adl::size_t size, void* /*data*/) -> void* { + return malloc(size); + }, + .free = [] (void* addr, adl::size_t /*size*/, void* /*data*/) { + free(addr); + }, + .data = &adlAllocData + }); + + g_adlAllocInited = true; +} + +/* + * Parse "ip:port" into separate ip string + numeric port. + * + * Mirrors what front does in IPV4_PORT_STR_TO_TUPLE: split on ':', + * take everything before as IP, everything after (decimal) as port. + * + * On success returns 0 and writes the IP into ipOut[0..ipOutMax-1] + * (NUL-terminated) and the port into *portOut. Returns -1 on any + * parse error. + */ +int parseIpPort(const char* s, char* ipOut, size_t ipOutMax, + adl::uint16_t* portOut) +{ + if (!s || !ipOut || !portOut || ipOutMax < 2) return -1; + + const char* colon = strchr(s, ':'); + if (!colon || colon == s) return -1; + + size_t ipLen = static_cast(colon - s); + if (ipLen >= ipOutMax) return -1; + + memcpy(ipOut, s, ipLen); + ipOut[ipLen] = '\0'; + + /* Decimal port. */ + long p = 0; + for (const char* q = colon + 1; *q; ++q) { + if (*q < '0' || *q > '9') return -1; + p = p * 10 + (*q - '0'); + if (p > 0xFFFF) return -1; + } + if (p == 0) return -1; + + *portOut = static_cast(p); + return 0; +} + +} // namespace + + +/* + * Concrete session struct. Hidden behind an opaque typedef in the C + * header so the layout never leaks. + */ +struct mnemosyne_session_s { + monkey::net::Protocol2Connection* client = nullptr; + + adl::int64_t blockId = -1; + bool hasBlock = false; + bool connected = false; + + /* + * 4 KiB staging page for send/recv. Aligned to 4 KiB to match what + * Protocol2Connection::readBlock / writeBlock expect (they assume + * the caller-supplied buffer can hold a full 4 KiB page). + */ + adl::uint8_t staging[MNEMOSYNE_BLOCK_SIZE] + __attribute__((aligned(4096))) = {0}; +}; + + +/* ====================================================================== */ +/* Engine */ +/* ====================================================================== */ + +extern "C" int mnemosyne_engine_init(const char *server_ip, + uint16_t server_port, + const char *auth_key, + uint8_t channel_id, + uint8_t role) +{ + if (g_engine.inited) { + return 0; /* idempotent */ + } + if (!server_ip || !auth_key) { + return -1; + } + if (role != MNEMOSYNE_ROLE_ALLOC && role != MNEMOSYNE_ROLE_REF) { + return -2; + } + + /* + * STEP 1 — read the channel virtual addresses from the IPC message + * registers BEFORE doing anything that could trigger a syscall. + * + * The kernel boot loader publishes: + * msg[2..4] -> CH0 (TX, RX, Data) + * msg[5..7] -> CH1 (TX, RX, Data) + * msg[8..10] -> CH2 (TX, RX, Data) + * (See HYPERAMP_MULTI_CHANNEL_DESIGN.md and kernel/src/arch/arm/ + * kernel/boot.c.) The IPC buffer is volatile — every syscall + * overwrites it — so we capture the values up-front and only + * perform syscalls afterwards. + */ + const adl::uint8_t resolvedChannel = + (channel_id == 0) ? MNEMOSYNE_CHANNEL_DEFAULT : channel_id; + + seL4_Word txVa = 0, rxVa = 0, dtVa = 0; + switch (resolvedChannel) { + case 2: + txVa = seL4_GetMR(MNEMOSYNE_MR_SLOT_CH2_TX); + rxVa = seL4_GetMR(MNEMOSYNE_MR_SLOT_CH2_RX); + dtVa = seL4_GetMR(MNEMOSYNE_MR_SLOT_CH2_DATA); + break; + case 1: + /* Debug / experimental: bind to CH1 instead. The integrator + * is responsible for ensuring nothing else (front, + * hyperamp-server) is using CH1 in the same image. */ + txVa = seL4_GetMR(5); + rxVa = seL4_GetMR(6); + dtVa = seL4_GetMR(7); + break; + default: + return -3; + } + + /* + * STEP 2 — record engine config and bring up the ADL allocator. + */ + strncpy(g_engine.ip, server_ip, sizeof(g_engine.ip) - 1); + strncpy(g_engine.authKey, auth_key, sizeof(g_engine.authKey) - 1); + g_engine.port = server_port; + g_engine.channelId = resolvedChannel; + g_engine.role = role; + + initAdlAllocOnce(); + + /* + * STEP 3 — initialise the HyperAMP bridge with the captured vaddrs. + */ + if (!txVa || !rxVa || !dtVa) { + MONKEY_LOG_ERROR("[mnemosyne_api] IPC MR returned null vaddr " + "(boot loader did not publish channel ", + (int)resolvedChannel, "?)"); + return -4; + } + + monkey::net::HyperAmpBridge::instance().init( + resolvedChannel, + static_cast(txVa), + static_cast(rxVa), + static_cast(dtVa)); + + if (!monkey::net::HyperAmpBridge::instance().isInitialized()) { + MONKEY_LOG_ERROR("[mnemosyne_api] HyperAmpBridge init failed"); + return -5; + } + + g_engine.inited = true; + MONKEY_LOG_INFO("[mnemosyne_api] engine initialised, server=", + g_engine.ip, ":", (unsigned int)g_engine.port, + " channel=", (int)g_engine.channelId, + " role=", (int)g_engine.role); + return 0; +} + + +extern "C" void mnemosyne_engine_run_hyperamp_once(void) +{ + /* + * monkey-mnemosyne does not maintain an event loop of its own — + * every send / recv call drives the HyperAMP queue itself, and the + * upper-layer application is expected to call mnemosyne_sess_recv() + * in its own loop to detect updates (see lab-main.cc::waitDataUpdate + * for the equivalent pattern). This function is therefore an + * intentional no-op, provided only so that callers can use a unified + * loop body alongside frontend_engine_run_hyperamp_once(). + */ +} + + +/* ====================================================================== */ +/* Session */ +/* ====================================================================== */ + +extern "C" mnemosyne_session_t *mnemosyne_sess_new(void) +{ + if (!g_engine.inited) { + MONKEY_LOG_ERROR("[mnemosyne_api] sess_new before engine_init"); + return nullptr; + } + + auto* sess = new mnemosyne_session_s(); + if (!sess) return nullptr; + + sess->client = new monkey::net::Protocol2Connection(); + if (!sess->client) { + delete sess; + return nullptr; + } + + /* Pre-fill with engine-default endpoint; overridden in connect(). */ + sess->client->ip.set(g_engine.ip); + sess->client->port = g_engine.port; + + return sess; +} + + +extern "C" int mnemosyne_sess_connect_by_addrstr_devid( + mnemosyne_session_t *sess, + int proto, + const char *addr_str, + uint16_t dev_id) +{ + if (!sess || !sess->client) { + return -1; + } + + /* + * monkey-mnemosyne speaks TCP only. We accept the proto parameter + * for API symmetry with frontend_sess_connect_by_addrstr_devid but + * refuse anything other than TCP. + */ + if (proto != MNEMOSYNE_PROTO_TCP) { + MONKEY_LOG_ERROR("[mnemosyne_api] only TCP supported, got proto=", + proto); + return -2; + } + + auto& client = *sess->client; + + /* + * Parse "ip:port". An empty / NULL addr_str means "reuse engine + * defaults" (mirrors the convenience knob lab-main.cc had). + */ + if (addr_str && addr_str[0] != '\0') { + char ip[64]; + adl::uint16_t port = 0; + if (parseIpPort(addr_str, ip, sizeof(ip), &port) != 0) { + MONKEY_LOG_ERROR("[mnemosyne_api] bad addr_str (need 'ip:port'): ", + addr_str); + return -3; + } + client.ip.set(ip); + client.port = port; + } + + using HelloMode = monkey::net::ProtocolConnection::HelloMode; + + /* + * connect() goes through Socket4::connect() -> HyperAmpBridge.connect() + * which builds the SESS_CREATE message. We need to forward dev_id + * into SessIPv4Params.device_selection — the existing Socket4 path + * uses the bridge's default 0xFF, so for non-default dev_id we have + * to call the bridge directly. + */ + monkey::Status st; + + if (dev_id == MNEMOSYNE_DEV_AUTO) { + st = client.connect(); /* legacy path: bridge.connect uses 0xFF */ + } else { + /* + * Bypass Socket4::connect()'s hard-coded auto dev_id by talking + * to the bridge ourselves with the explicit dev_id, then mark + * the socket "valid" the same way Socket4::connect() does. + */ + client.close(); + auto& bridge = monkey::net::HyperAmpBridge::instance(); + /* Bridge must already be up (mnemosyne_engine_init brought it + * up via the 4-arg init overload); no need to call init() again. */ + bool ok = bridge.connect(client.ip, client.port, dev_id); + if (!ok) { + MONKEY_LOG_ERROR("[mnemosyne_api] bridge.connect (dev_id=", + (int)dev_id, ") failed"); + return -4; + } + client.socketFd = 9999; /* same sentinel Socket4::connect uses */ + st = monkey::Status::SUCCESS; + } + + if (st != monkey::Status::SUCCESS) { + MONKEY_LOG_ERROR("[mnemosyne_api] connect failed"); + return -4; + } + + st = client.hello(monkey::net::Protocol2Connection::VERSION, + HelloMode::CLIENT); + if (st != monkey::Status::SUCCESS) { + MONKEY_LOG_ERROR("[mnemosyne_api] hello failed"); + client.close(); + return -5; + } + + st = client.auth(g_engine.authKey); + if (st != monkey::Status::SUCCESS) { + MONKEY_LOG_ERROR("[mnemosyne_api] auth failed"); + client.close(); + return -6; + } + + sess->connected = true; + + /* Acquire the shared 4 KiB block according to role. */ + if (g_engine.role == MNEMOSYNE_ROLE_ALLOC) { + adl::int64_t bid = -1; + st = client.tryAlloc(&bid); + if (st != monkey::Status::SUCCESS) { + MONKEY_LOG_ERROR("[mnemosyne_api] tryAlloc failed"); + client.close(); + sess->connected = false; + return -7; + } + sess->blockId = bid; + sess->hasBlock = true; + MONKEY_LOG_INFO("[mnemosyne_api] alloc-side blockId=", + (long)sess->blockId); + } else { + /* + * Ref-side: try once. Mirrors the loop in lab-main.cc::doLabApp2, + * but only one attempt — the upper-layer application is expected + * to retry mnemosyne_sess_connect_by_addrstr_devid() if refBlock + * isn't ready yet (alloc-side hasn't run). This keeps the API + * non-blocking-ish. + */ + adl::int64_t bid = -1; + st = client.refBlock(predictableWriteKey(), &bid); + if (st != monkey::Status::SUCCESS) { + MONKEY_LOG_WARN("[mnemosyne_api] refBlock not ready yet " + "(alloc-side may not have run)"); + client.close(); + sess->connected = false; + return -8; + } + sess->blockId = bid; + sess->hasBlock = true; + MONKEY_LOG_INFO("[mnemosyne_api] ref-side blockId=", + (long)sess->blockId); + } + + return 0; +} + + +extern "C" int mnemosyne_sess_send(mnemosyne_session_t *sess, + const void *data, uint32_t size) +{ + if (!sess || !sess->client || !sess->connected || !sess->hasBlock) { + return -1; + } + if (!data || size == 0) { + return -2; + } + if (size > MNEMOSYNE_BLOCK_SIZE) { + MONKEY_LOG_ERROR("[mnemosyne_api] send size > 4096 not supported"); + return -3; + } + + /* Stage the payload into our 4 KiB page (zero-pad the tail). */ + memcpy(sess->staging, data, size); + if (size < MNEMOSYNE_BLOCK_SIZE) { + memset(sess->staging + size, 0, MNEMOSYNE_BLOCK_SIZE - size); + } + + monkey::Status st = sess->client->writeBlock(sess->blockId, sess->staging); + if (st != monkey::Status::SUCCESS) { + MONKEY_LOG_ERROR("[mnemosyne_api] writeBlock failed"); + return -4; + } + return (int)size; +} + + +extern "C" int mnemosyne_sess_recv(mnemosyne_session_t *sess, + void *buf, uint32_t size) +{ + if (!sess || !sess->client || !sess->connected || !sess->hasBlock) { + return -1; + } + if (!buf || size == 0) { + return -2; + } + + monkey::Status st = sess->client->readBlock(sess->blockId, sess->staging); + if (st != monkey::Status::SUCCESS) { + MONKEY_LOG_ERROR("[mnemosyne_api] readBlock failed"); + return -3; + } + + uint32_t copy = (size < MNEMOSYNE_BLOCK_SIZE) ? size : MNEMOSYNE_BLOCK_SIZE; + memcpy(buf, sess->staging, copy); + return (int)copy; +} + + +extern "C" void mnemosyne_sess_close(mnemosyne_session_t *sess) +{ + if (!sess) return; + + if (sess->client) { + if (sess->hasBlock) { + /* Best-effort unref; we ignore errors because we're tearing + * the session down regardless. */ + sess->client->unrefBlock(sess->blockId); + } + if (sess->connected) { + sess->client->close(); + } + delete sess->client; + sess->client = nullptr; + } + + delete sess; +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/Status.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/Status.h new file mode 100644 index 0000000000000000000000000000000000000000..ab50ef44f966816d062cce4c5835286c6ee43986 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/Status.h @@ -0,0 +1,29 @@ +/* + Monkey Status + + Created on 2025.1.5 + + gongty [at] tongji [dot] edu [dot] cn + +*/ + +#pragma once + +#include + +namespace monkey { + +enum class Status : adl::int32_t { + SUCCESS = 0, + OUT_OF_RESOURCE, + NETWORK_ERROR, + PROTOCOL_ERROR, + INVALID_PARAMETERS, + NOT_FOUND, + NOT_INITIALIZED, + FAILED +}; + +} + + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/config.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/config.h new file mode 100644 index 0000000000000000000000000000000000000000..7bbd0cb8426f58e7b753998045135ff4fa5dae34 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/config.h @@ -0,0 +1,12 @@ +/* + + monkey system global config + + by gty + created on 2025.10.30, Shanghai +*/ + + +#pragma once + +#define MONKEY_CFG_ENABLE_DEBUG_LOGS 0 diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/crypto/rc4.cc b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/crypto/rc4.cc new file mode 100644 index 0000000000000000000000000000000000000000..bf030d70b54ea7c8ab9ebb99c5182a80526ccb93 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/crypto/rc4.cc @@ -0,0 +1,86 @@ +/* + + RC4 + + + Created on 2025.1.6 at Jiangchuan, Minhang, Shanghai + + gongty [at] tongji [dot] edu [dot] cn + feng.yt [at] sjtu [dot] edu [dot] cn + + + Reference: + https://blog.csdn.net/weixin_45582916/article/details/121429688 + +*/ + +#include +#include + + +using namespace adl; + + +static void rc4Init(unsigned char* s, const ByteArray& key) { + int j = 0; + char k[256] = { 0 }; + unsigned char tmp = 0; + for (unsigned int i = 0; i < 256; i++) { + s[i] = (unsigned char) i; + k[i] = (char) key[i % key.size()]; + } + for (unsigned int i = 0; i < 256; i++) { + j = (j + s[i] + k[i]) % 256; + tmp = s[i]; + s[i] = s[j]; + s[j] = tmp; + } +} + + +namespace monkey::crypto { + +ByteArray rc4(const ByteArray& dataIn, const ByteArray& key) { + ByteArray data = dataIn; + rc4Inplace(data, key); + return data; +} + + +void rc4Inplace(ByteArray& data, const ByteArray& key) { + unsigned char s[256]; + rc4Init(s, key); + int i = 0, j = 0, t = 0; + unsigned long k = 0; + unsigned char tmp; + for (k = 0; k < data.size(); k++) { + i = (i + 1) % 256; + j = (j + s[i]) % 256; + tmp = s[i]; + s[i] = s[j]; + s[j] = tmp; + t = (s[i] + s[j]) % 256; + data[k] = data[k] ^ s[t]; + } + +} + + +bool rc4Verify(const ByteArray& key, const ByteArray& challenge, const ByteArray& cipher) { + ByteArray data = rc4(challenge, key); + return data == cipher; +} + + + +int64_t rc4Verify(const ArrayList& keyring, const ByteArray& challenge, const ByteArray& cipher) { + for (size_t i = 0; i < keyring.size(); i++) { + if (rc4Verify(keyring[i], challenge, cipher)) + return int64_t(i); + } + + return -1; +} + + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/crypto/rc4.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/crypto/rc4.h new file mode 100644 index 0000000000000000000000000000000000000000..b8826ada78154db4e684c76ceb75d2a74e47b914 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/crypto/rc4.h @@ -0,0 +1,47 @@ +/* + + RC4 + + + Created on 2025.1.6 at Jiangchuan, Minhang, Shanghai + + gongty [at] tongji [dot] edu [dot] cn + feng.yt [at] sjtu [dot] edu [dot] cn + + +*/ + + +#pragma once + +#include +#include + + + +namespace monkey::crypto { + + +adl::ByteArray rc4(const adl::ByteArray& dataIn, const adl::ByteArray& key); + + +void rc4Inplace(adl::ByteArray& data, const adl::ByteArray& key); + + +bool rc4Verify(const adl::ByteArray& key, const adl::ByteArray& challenge, const adl::ByteArray& cipher); + + +/** + * + * @return -1 if not found. + */ +adl::int64_t rc4Verify( + const adl::ArrayList& keyring, + const adl::ByteArray& challenge, + const adl::ByteArray& cipher +); + + + +} + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/log.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/log.h new file mode 100644 index 0000000000000000000000000000000000000000..1e585c81ec79a1b0b86c7da93256335b8e8c2bf5 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/log.h @@ -0,0 +1,22 @@ +/* + + monkey logging + + by gty + created on 2025.10.30, Shanghai +*/ + +#pragma once + +#include +#include "./config.h" + +#define MONKEY_LOG_INFO(...) Genode::log("[MONKEY][INFO] ", __VA_ARGS__) +#define MONKEY_LOG_WARN(...) Genode::warning("[MONKEY][WARN] ", __VA_ARGS__) +#define MONKEY_LOG_ERROR(...) Genode::error("[MONKEY][ERROR] ", __VA_ARGS__) + +#if MONKEY_CFG_ENABLE_DEBUG_LOGS + #define MONKEY_LOG_DEBUG(...) Genode::log("[MONKEY][DEBUG] ", __VA_ARGS__) +#else + #define MONKEY_LOG_DEBUG(...) do { } while (0) +#endif diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/HyperAmpBridge.cc b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/HyperAmpBridge.cc new file mode 100644 index 0000000000000000000000000000000000000000..e8156baa1fcf27d6a336c6b3cc40296b3fa42414 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/HyperAmpBridge.cc @@ -0,0 +1,650 @@ +/* + * HyperAMP Network Bridge – Implementation + * + * See HyperAmpBridge.h for the design rationale. + * + * Created 2025 – Monkey-Mnemosyne / seL4 HyperAMP integration + * + * gongty [at] tongji [dot] edu [dot] cn + */ + +#include +#include +#include + +#include + +extern "C" { +#include "hyperamp_shm_queue.h" +#include "hyperamp_protocol_defs.h" +#include +} + +namespace monkey::net { + + +/* ======================================================================== */ +/* Singleton */ +/* ======================================================================== */ + +HyperAmpBridge::HyperAmpBridge() { + memset(spillBuf_, 0, sizeof(spillBuf_)); +} + +HyperAmpBridge& HyperAmpBridge::instance() { + static HyperAmpBridge inst; + return inst; +} + + +/* ======================================================================== */ +/* Initialisation */ +/* ======================================================================== */ + +/* + * Internal helper – performs the queue-init dance once we have all the + * vaddrs / paddrs / capacity figured out. Both public init() overloads + * funnel through here. + */ +namespace { + +bool initQueuesImpl(volatile HyperampShmQueue* tx, + volatile HyperampShmQueue* rx, + uint64_t txPhys, + uint64_t rxPhys, + uint16_t capacity) +{ + HyperampQueueConfig txCfg; + memset(&txCfg, 0, sizeof(txCfg)); + txCfg.map_mode = HYPERAMP_MAP_MODE_CONTIGUOUS_BOTH; + txCfg.capacity = capacity; + txCfg.block_size = 4096; + txCfg.phy_addr = txPhys; + txCfg.virt_addr = reinterpret_cast(tx); + + if (hyperamp_queue_init(tx, &txCfg, 1) != HYPERAMP_OK) { + MONKEY_LOG_ERROR("[HyperAmpBridge] Failed to init TX queue"); + return false; + } + + HyperampQueueConfig rxCfg; + memset(&rxCfg, 0, sizeof(rxCfg)); + rxCfg.map_mode = HYPERAMP_MAP_MODE_CONTIGUOUS_BOTH; + rxCfg.capacity = capacity; + rxCfg.block_size = 4096; + rxCfg.phy_addr = rxPhys; + rxCfg.virt_addr = reinterpret_cast(rx); + + if (hyperamp_queue_init(rx, &rxCfg, 1) != HYPERAMP_OK) { + MONKEY_LOG_ERROR("[HyperAmpBridge] Failed to init RX queue"); + return false; + } + + return true; +} + +} // namespace + + +void HyperAmpBridge::init(adl::uint8_t channelId) { + if (initialized_) { + return; + } + + /* + * Legacy entry point — uses the compile-time vaddrs from + * hyperamp_shm_queue.h. These were correct before the kernel boot + * loader was reworked to map the 4 MiB shm window dynamically and + * publish vaddrs through IPC MRs; they are kept here only so that + * lab-main.cc keeps compiling. Production callers must use the + * four-argument overload below. + */ + volatile HyperampShmQueue* tx = SHM_TX_QUEUE_VADDR; + volatile HyperampShmQueue* rx = SHM_RX_QUEUE_VADDR; + volatile void* dr = SHM_DATA_REGION_VA; + uint64_t txPhys = SHM_TX_QUEUE_PADDR; + uint64_t rxPhys = SHM_RX_QUEUE_PADDR; + + if (channelId == 2) { + /* Apply the CH2 paddr offset against the legacy base. The + * vaddr is still the legacy CH1 vaddr — this overload cannot + * provide a correct CH2 vaddr because it has no access to the + * IPC MRs; if you want a working CH2 setup, use the four-arg + * overload from mnemosyne_engine_init() instead. */ + txPhys = SHM_TX_QUEUE_PADDR + 0x300000UL; + rxPhys = SHM_RX_QUEUE_PADDR + 0x300000UL; + MONKEY_LOG_WARN("[HyperAmpBridge] init(channelId=2) used the " + "legacy compile-time vaddrs; this only works on " + "kernels that still map the CH1 region at the " + "old 0x55E000 vaddr. Prefer init(2, tx, rx, dt) " + "with vaddrs from seL4_GetMR(8/9/10)."); + } + + txQueue_ = tx; + rxQueue_ = rx; + dataRegion_ = dr; + + MONKEY_LOG_INFO("[HyperAmpBridge] (legacy) TX : ", + (unsigned long)(uintptr_t)txQueue_); + MONKEY_LOG_INFO("[HyperAmpBridge] (legacy) RX : ", + (unsigned long)(uintptr_t)rxQueue_); + MONKEY_LOG_INFO("[HyperAmpBridge] (legacy) DT : ", + (unsigned long)(uintptr_t)dataRegion_); + + if (!initQueuesImpl(txQueue_, rxQueue_, txPhys, rxPhys, /*capacity=*/256)) { + return; + } + + initialized_ = true; + MONKEY_LOG_INFO("[HyperAmpBridge] Initialised OK (legacy path, ch=", + (int)channelId, ")"); +} + + +void HyperAmpBridge::init(adl::uint8_t channelId, + adl::uint64_t txVa, + adl::uint64_t rxVa, + adl::uint64_t dataVa) +{ + if (initialized_) { + return; + } + if (!txVa || !rxVa || !dataVa) { + MONKEY_LOG_ERROR("[HyperAmpBridge] init(): null vaddr from MR"); + return; + } + + /* + * Pick paddr offset & queue capacity from the channel id. + * + * The platform shm base lives in hyperamp_shm_queue.h as + * SHM_TX_QUEUE_PADDR / SHM_RX_QUEUE_PADDR (per CONFIG_PLAT_*); the + * per-channel offset matches the layout enforced by the kernel boot + * loader (CH1 = +0x200000, CH2 = +0x300000). + */ + uint64_t paddrOffset = 0; + uint16_t capacity = 256; + + switch (channelId) { + case 2: + paddrOffset = MNEMOSYNE_CH2_PADDR_OFFSET; + capacity = MNEMOSYNE_CH2_QUEUE_CAPACITY; + MONKEY_LOG_INFO("[HyperAmpBridge] Using channel 2 (mnemosyne)"); + break; + case 1: + default: + paddrOffset = 0x200000UL; /* matches HYPERAMP_CH1_OFFSET_PADDR */ + capacity = 253; + MONKEY_LOG_INFO("[HyperAmpBridge] Using channel 1"); + break; + } + + txQueue_ = reinterpret_cast(txVa); + rxQueue_ = reinterpret_cast(rxVa); + dataRegion_ = reinterpret_cast(dataVa); + + uint64_t txPhys = SHM_TX_QUEUE_PADDR + paddrOffset; + uint64_t rxPhys = SHM_RX_QUEUE_PADDR + paddrOffset; + + MONKEY_LOG_INFO("[HyperAmpBridge] TX vaddr=", + (unsigned long)(uintptr_t)txQueue_, + " paddr=", (unsigned long)txPhys); + MONKEY_LOG_INFO("[HyperAmpBridge] RX vaddr=", + (unsigned long)(uintptr_t)rxQueue_, + " paddr=", (unsigned long)rxPhys); + MONKEY_LOG_INFO("[HyperAmpBridge] DT vaddr=", + (unsigned long)(uintptr_t)dataRegion_); + + if (!initQueuesImpl(txQueue_, rxQueue_, txPhys, rxPhys, capacity)) { + return; + } + + initialized_ = true; + MONKEY_LOG_INFO("[HyperAmpBridge] Initialised OK (ch=", (int)channelId, + ", capacity=", (int)capacity, ")"); +} + + +/* ======================================================================== */ +/* Internal helpers */ +/* ======================================================================== */ + +adl::uint16_t HyperAmpBridge::nextFrontendSessId() { + adl::uint16_t id = sessIdCounter_++; + if (sessIdCounter_ == 0xFFFF) { // 0xFFFF is the handover sentinel + sessIdCounter_ = 1; + } + return id; +} + + +bool HyperAmpBridge::txEnqueue(const void* data, adl::size_t len) { + /* + * Retry loop: the queue may be momentarily full (HYPERAMP_AGAIN). + * Since performance is not a concern we simply spin. + */ + for (;;) { + int rc = hyperamp_queue_enqueue( + txQueue_, + HYPERAMP_ZONE_ID_SEL4, + data, + len, + dataRegion_ + ); + + if (rc == HYPERAMP_OK) { + return true; + } + if (rc == HYPERAMP_AGAIN) { + /* Queue full – spin. */ + continue; + } + /* Hard error. */ + MONKEY_LOG_ERROR("[HyperAmpBridge] txEnqueue error, rc=", rc); + return false; + } +} + + +bool HyperAmpBridge::rxDequeueBlocking(void* buf, adl::size_t* outLen) { + /* + * Busy-wait until HYPERAMP_OK. + */ + for (;;) { + /* + * Invalidate the cache covering the queue control block before + * every poll attempt so we see the latest writes from Linux. + */ + hyperamp_cache_invalidate(rxQueue_, 64); + + size_t actual = 0; + int rc = hyperamp_queue_dequeue( + rxQueue_, + HYPERAMP_ZONE_ID_SEL4, + buf, + kHyperAmpBlockSize, + &actual, + dataRegion_ + ); + + if (rc == HYPERAMP_OK) { + if (outLen) { + *outLen = actual; + } + return true; + } + if (rc == HYPERAMP_AGAIN) { + /* Queue empty – keep polling. */ + continue; + } + MONKEY_LOG_ERROR("[HyperAmpBridge] rxDequeueBlocking error, rc=", rc); + return false; + } +} + + +/* ======================================================================== */ +/* Session create (connect) */ +/* ======================================================================== */ + +bool HyperAmpBridge::connect(const IP4Addr& ip, + adl::uint16_t port, + adl::uint16_t devId) { + if (!initialized_) { + MONKEY_LOG_ERROR("[HyperAmpBridge] connect() called before init()"); + return false; + } + + if (sessionActive_) { + MONKEY_LOG_WARN("[HyperAmpBridge] connect() – closing existing session first"); + close(); + } + + /* Reset spill buffer. */ + spillLen_ = 0; + spillOffset_ = 0; + + frontendSessId_ = nextFrontendSessId(); + + /* + * Build the session-create message. + * + * Wire format (big picture): + * [ HyperampMsgHeader (8B) ][ SessMsgHeader (10B) ][ SessIPv4Params (10B) ] + * + * Total = 28 bytes, well within a single 4096-byte queue slot. + */ + + /* --- SessMsgHeader --- */ + SessMsgHeader sessHdr; + memset(&sessHdr, 0, sizeof(sessHdr)); + sessHdr.version = PROXY_PROTO_SESS_VERSION_1; + sessHdr.msg_type = SESS_MSG_CREATE; + sessHdr.action_type = ACTION_TYPE_COMMAND; + sessHdr.ip_version = SESS_IPV4_PROTO; + sessHdr.payload_len = static_cast(sizeof(SessIPv4Params)); + + /* --- SessIPv4Params --- */ + SessIPv4Params sessParams; + memset(&sessParams, 0, sizeof(sessParams)); + sessParams.device_selection = devId; // 0xFF = DEV_ID_AUTO_HANDOVER + sessParams.transport_layer_proto = SESS_TCP_PROTO; + sessParams.dest_endpoint.ipv4_addr.data[0] = ip.ui8arr[0]; + sessParams.dest_endpoint.ipv4_addr.data[1] = ip.ui8arr[1]; + sessParams.dest_endpoint.ipv4_addr.data[2] = ip.ui8arr[2]; + sessParams.dest_endpoint.ipv4_addr.data[3] = ip.ui8arr[3]; + sessParams.dest_endpoint.port = port; + + /* --- Assemble the full message --- */ + uint8_t msgBuf[kHyperAmpBlockSize]; + memset(msgBuf, 0, sizeof(msgBuf)); + + /* Outer HyperAMP header. */ + HyperampMsgHeader* outerHdr = reinterpret_cast(msgBuf); + outerHdr->version = PROXY_PROTO_VERSION_1; + outerHdr->proxy_msg_type = static_cast(HYPERAMP_MSG_TYPE_SESS); + outerHdr->frontend_sess_id = frontendSessId_; + outerHdr->backend_sess_id = 0xFFFF; // handover sentinel + outerHdr->payload_len = static_cast(sizeof(SessMsgHeader) + sizeof(SessIPv4Params)); + + /* Session sub-header + params follow the outer header. */ + memcpy(msgBuf + HYPERAMP_MSG_HDR_SIZE, + &sessHdr, sizeof(sessHdr)); + memcpy(msgBuf + HYPERAMP_MSG_HDR_SIZE + sizeof(SessMsgHeader), + &sessParams, sizeof(sessParams)); + + adl::size_t totalLen = HYPERAMP_MSG_HDR_SIZE + sizeof(SessMsgHeader) + sizeof(SessIPv4Params); + + MONKEY_LOG_INFO("[HyperAmpBridge] Sending SESS_CREATE fe_id=", frontendSessId_, + " ip=", ip.toString().c_str(), ":", port); + + if (!txEnqueue(msgBuf, totalLen)) { + MONKEY_LOG_ERROR("[HyperAmpBridge] connect() – txEnqueue failed"); + return false; + } + + /* + * Poll for the session-create response. + * + * The response is a HYPERAMP_MSG_TYPE_SESS message whose + * frontend_sess_id matches ours. The backend_sess_id in the + * response header gives us the server-side session handle. + * + * Payload is a SessOpRespData (2 bytes: status + code). + */ + uint8_t rxBuf[kHyperAmpBlockSize]; + for (;;) { + adl::size_t rxLen = 0; + if (!rxDequeueBlocking(rxBuf, &rxLen)) { + MONKEY_LOG_ERROR("[HyperAmpBridge] connect() – rxDequeue failed"); + return false; + } + + if (rxLen < HYPERAMP_MSG_HDR_SIZE) { + continue; // Malformed – skip. + } + + const HyperampMsgHeader* rspHdr = + reinterpret_cast(rxBuf); + + /* Is this our session response? */ + if (rspHdr->proxy_msg_type != static_cast(HYPERAMP_MSG_TYPE_SESS)) { + MONKEY_LOG_WARN("[HyperAmpBridge] connect() – ignoring non-SESS msg, type=", + (int)rspHdr->proxy_msg_type); + continue; + } + if (rspHdr->frontend_sess_id != frontendSessId_) { + MONKEY_LOG_WARN("[HyperAmpBridge] connect() – fe_id mismatch, got ", + rspHdr->frontend_sess_id, " want ", frontendSessId_); + continue; + } + + backendSessId_ = rspHdr->backend_sess_id; + + /* Check the embedded SessOpRespData if present. */ + adl::size_t payloadOff = HYPERAMP_MSG_HDR_SIZE + sizeof(SessMsgHeader); + if (rxLen >= payloadOff + sizeof(SessOpRespData)) { + const SessOpRespData* resp = + reinterpret_cast(rxBuf + payloadOff); + if (resp->status != SESS_OP_STATUS_SUCCESS) { + MONKEY_LOG_ERROR("[HyperAmpBridge] connect() – backend refused, status=", + (int)resp->status, " code=", (int)resp->code); + return false; + } + } + + sessionActive_ = true; + MONKEY_LOG_INFO("[HyperAmpBridge] Session established fe=", frontendSessId_, + " be=", backendSessId_); + return true; + } +} + + +/* ======================================================================== */ +/* Data send */ +/* ======================================================================== */ + +adl::int64_t HyperAmpBridge::send(const void* buf, adl::size_t len) { + if (!initialized_ || !sessionActive_) { + MONKEY_LOG_ERROR("[HyperAmpBridge] send() – no active session"); + return -1; + } + + const uint8_t* src = static_cast(buf); + adl::size_t remaining = len; + adl::size_t totalSent = 0; + + while (remaining > 0) { + adl::size_t chunk = remaining; + if (chunk > kHyperAmpMaxPayload) { + chunk = kHyperAmpMaxPayload; + } + + /* Build a HYPERAMP_MSG_TYPE_DATA message. */ + uint8_t msgBuf[kHyperAmpBlockSize]; + memset(msgBuf, 0, sizeof(msgBuf)); + + HyperampMsgHeader* hdr = reinterpret_cast(msgBuf); + hdr->version = PROXY_PROTO_VERSION_1; + hdr->proxy_msg_type = static_cast(HYPERAMP_MSG_TYPE_DATA); + hdr->frontend_sess_id = frontendSessId_; + hdr->backend_sess_id = backendSessId_; + hdr->payload_len = static_cast(chunk); + + memcpy(msgBuf + HYPERAMP_MSG_HDR_SIZE, src, chunk); + + if (!txEnqueue(msgBuf, HYPERAMP_MSG_HDR_SIZE + chunk)) { + MONKEY_LOG_ERROR("[HyperAmpBridge] send() – txEnqueue failed after ", + totalSent, " bytes"); + return (totalSent > 0) ? static_cast(totalSent) : -1; + } + + src += chunk; + remaining -= chunk; + totalSent += chunk; + } + + return static_cast(totalSent); +} + + +/* ======================================================================== */ +/* Data recv (blocking / polling) */ +/* ======================================================================== */ + +adl::int64_t HyperAmpBridge::recv(void* buf, adl::size_t len) { + if (!initialized_ || !sessionActive_) { + MONKEY_LOG_ERROR("[HyperAmpBridge] recv() – no active session"); + return -1; + } + + uint8_t* dst = static_cast(buf); + adl::size_t needed = len; + adl::size_t got = 0; + + /* 1. Drain any leftover bytes from the spill buffer first. */ + if (spillLen_ > spillOffset_) { + adl::size_t avail = spillLen_ - spillOffset_; + adl::size_t take = (avail <= needed) ? avail : needed; + memcpy(dst, spillBuf_ + spillOffset_, take); + spillOffset_ += take; + dst += take; + needed -= take; + got += take; + + if (spillOffset_ >= spillLen_) { + spillLen_ = 0; + spillOffset_ = 0; + } + } + + /* 2. Keep pulling messages until we have enough. */ + uint8_t rxBuf[kHyperAmpBlockSize]; + + while (needed > 0) { + adl::size_t rxLen = 0; + if (!rxDequeueBlocking(rxBuf, &rxLen)) { + MONKEY_LOG_ERROR("[HyperAmpBridge] recv() – rxDequeue failed"); + return (got > 0) ? static_cast(got) : -1; + } + + if (rxLen < HYPERAMP_MSG_HDR_SIZE) { + continue; + } + + const HyperampMsgHeader* hdr = + reinterpret_cast(rxBuf); + + /* Only accept DATA messages for our session. */ + if (hdr->proxy_msg_type != static_cast(HYPERAMP_MSG_TYPE_DATA)) { + MONKEY_LOG_WARN("[HyperAmpBridge] recv() – ignoring non-DATA msg, type=", + (int)hdr->proxy_msg_type); + continue; + } + + /* + * Accept if either session ID matches – the backend may use + * either the frontend or backend session ID. + */ + if (hdr->frontend_sess_id != frontendSessId_ && + hdr->backend_sess_id != backendSessId_) { + MONKEY_LOG_WARN("[HyperAmpBridge] recv() – session id mismatch"); + continue; + } + + adl::size_t payloadLen = hdr->payload_len; + const uint8_t* payload = rxBuf + HYPERAMP_MSG_HDR_SIZE; + + if (payloadLen == 0) { + continue; + } + + /* Copy as much as the caller still needs. */ + adl::size_t take = (payloadLen <= needed) ? payloadLen : needed; + memcpy(dst, payload, take); + dst += take; + needed -= take; + got += take; + + /* Stash surplus in the spill buffer. */ + if (take < payloadLen) { + adl::size_t surplus = payloadLen - take; + memcpy(spillBuf_, payload + take, surplus); + spillLen_ = surplus; + spillOffset_ = 0; + } + } + + return static_cast(got); +} + + +/* ======================================================================== */ +/* Session close */ +/* ======================================================================== */ + +void HyperAmpBridge::close() { + if (!initialized_ || !sessionActive_) { + return; + } + + MONKEY_LOG_INFO("[HyperAmpBridge] Closing session fe=", frontendSessId_, + " be=", backendSessId_); + + /* + * Build a SESS_MSG_CLOSE command. + * + * Wire format: + * [ HyperampMsgHeader (8B) ][ SessMsgHeader (10B) ] + * + * The CLOSE command has no payload beyond the session sub-header. + */ + SessMsgHeader sessHdr; + memset(&sessHdr, 0, sizeof(sessHdr)); + sessHdr.version = PROXY_PROTO_SESS_VERSION_1; + sessHdr.msg_type = SESS_MSG_CLOSE; + sessHdr.action_type = ACTION_TYPE_COMMAND; + sessHdr.ip_version = SESS_IPV4_PROTO; + sessHdr.payload_len = 0; + + uint8_t msgBuf[kHyperAmpBlockSize]; + memset(msgBuf, 0, sizeof(msgBuf)); + + HyperampMsgHeader* outerHdr = reinterpret_cast(msgBuf); + outerHdr->version = PROXY_PROTO_VERSION_1; + outerHdr->proxy_msg_type = static_cast(HYPERAMP_MSG_TYPE_SESS); + outerHdr->frontend_sess_id = frontendSessId_; + outerHdr->backend_sess_id = backendSessId_; + outerHdr->payload_len = static_cast(sizeof(SessMsgHeader)); + + memcpy(msgBuf + HYPERAMP_MSG_HDR_SIZE, &sessHdr, sizeof(sessHdr)); + + adl::size_t totalLen = HYPERAMP_MSG_HDR_SIZE + sizeof(SessMsgHeader); + txEnqueue(msgBuf, totalLen); + + /* + * Wait for the close response. We give it a bounded attempt count so + * that a non-responsive backend doesn't hang the system forever. + */ + uint8_t rxBuf[kHyperAmpBlockSize]; + static constexpr int kMaxCloseAttempts = 50000; + + for (int attempt = 0; attempt < kMaxCloseAttempts; ++attempt) { + hyperamp_cache_invalidate(rxQueue_, 64); + + size_t actual = 0; + int rc = hyperamp_queue_dequeue( + rxQueue_, HYPERAMP_ZONE_ID_SEL4, + rxBuf, kHyperAmpBlockSize, &actual, dataRegion_ + ); + + if (rc == HYPERAMP_AGAIN) { + continue; + } + if (rc != HYPERAMP_OK) { + break; + } + if (actual < HYPERAMP_MSG_HDR_SIZE) { + continue; + } + + const HyperampMsgHeader* rsp = + reinterpret_cast(rxBuf); + + if (rsp->proxy_msg_type == static_cast(HYPERAMP_MSG_TYPE_SESS) && + rsp->frontend_sess_id == frontendSessId_) { + /* Got the close ack. */ + break; + } + } + + sessionActive_ = false; + spillLen_ = 0; + spillOffset_ = 0; + + MONKEY_LOG_INFO("[HyperAmpBridge] Session closed"); +} + + +} // namespace monkey::net diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/HyperAmpBridge.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/HyperAmpBridge.h new file mode 100644 index 0000000000000000000000000000000000000000..62f72b145a6e9b44e3a6428bbb2f4a1847071ed5 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/HyperAmpBridge.h @@ -0,0 +1,238 @@ +/* + * HyperAMP Network Bridge – Singleton + * + * Proxies POSIX-style connect/send/recv/close over HyperAMP shared-memory + * queues so that monkey-mnemosyne (running on seL4, which has no network + * stack) can reach the Linux backend transparently. + * + * Design principles + * ----------------- + * - Direct use of the HyperAMP queue primitives (hyperamp_queue_enqueue / + * hyperamp_queue_dequeue). No dependency on apps/front beyond the + * shared C headers that define the queue and message structures. + * - Synchronous / polling semantics: every receive busy-waits until data + * arrives. This is intentional – performance is not a concern for the + * monkey protocol's low-bandwidth control traffic. + * - One active session at a time (sufficient for monkey-mnemosyne). + * + * Created 2025 – Monkey-Mnemosyne / seL4 HyperAMP integration + * + * gongty [at] tongji [dot] edu [dot] cn + */ + +#pragma once + +#include +#include + +/* HyperAMP C headers (local copies – see hyperamp_shm_queue.h comment) --- */ +extern "C" { +#include "hyperamp_shm_queue.h" +#include "hyperamp_protocol_defs.h" +} + +namespace monkey::net { + + +/** + * Maximum payload that fits in a single HyperAMP queue slot after + * subtracting the 8-byte HyperampMsgHeader. + */ +static constexpr adl::size_t kHyperAmpMaxPayload = HYPERAMP_MSG_MAX_SIZE; + +/** + * Combined header + max-payload – equals one queue block. + */ +static constexpr adl::size_t kHyperAmpBlockSize = HYPERAMP_MSG_HDR_PLUS_MAX_SIZE; + + +/** + * @brief Singleton bridge that replaces POSIX sockets with HyperAMP + * shared-memory queue operations. + * + * Typical usage (called from Socket4::connect, PromisedSocketIo::send, etc.): + * + * auto& bridge = HyperAmpBridge::instance(); + * bridge.init(); // once, at boot + * bridge.connect(ip, port); // session create + * bridge.send(data, len); // data transfer + * bridge.recv(buf, len); // blocking poll + * bridge.close(); // session close + */ +class HyperAmpBridge { +public: + /* ---- Singleton access ----------------------------------------------- */ + + static HyperAmpBridge& instance(); + + /* non-copyable, non-movable */ + HyperAmpBridge(const HyperAmpBridge&) = delete; + HyperAmpBridge& operator=(const HyperAmpBridge&) = delete; + HyperAmpBridge(HyperAmpBridge&&) = delete; + HyperAmpBridge& operator=(HyperAmpBridge&&) = delete; + + /* ---- Lifecycle ------------------------------------------------------ */ + + /** + * Initialise the HyperAMP TX/RX queues — legacy entry point. + * + * Reads queue virtual addresses from the compile-time constants in + * hyperamp_shm_queue.h, the way this bridge originally worked. + * + * IMPORTANT: after the multi-channel rework in the kernel boot + * loader those compile-time vaddrs are no longer guaranteed to + * match the actual mapping. This entry point is preserved purely + * so that the legacy lab-main.cc test path keeps compiling; in + * production code use the four-argument overload below and pass + * vaddrs read from seL4_GetMR() instead. + * + * @param channelId 1 = ch1 layout (default), 2 = ch2 layout. + */ + void init(adl::uint8_t channelId = 1); + + /** + * Initialise the HyperAMP TX/RX queues with explicit virtual + * addresses obtained from the IPC message registers. + * + * This is the modern entry point used by mnemosyne_engine_init(). + * The caller is responsible for reading seL4_GetMR(...) BEFORE + * issuing any other syscall (the IPC buffer is volatile across + * syscalls; see apps/hyperamp-server/src/main.c for the exact + * ordering rule). + * + * Idempotent: subsequent calls are no-ops. + * + * @param channelId Channel id, used only for paddr-offset selection + * (1 = CH1, 2 = CH2). Determines the `phy_addr` + * passed into hyperamp_queue_init() so that the + * underlying mapping table is consistent with what + * the kernel actually mapped. + * @param txVa Virtual address of the TX queue control block, + * as published in the IPC MR by the boot loader. + * @param rxVa Virtual address of the RX queue control block. + * @param dataVa Virtual address of the shared data region. + */ + void init(adl::uint8_t channelId, + adl::uint64_t txVa, + adl::uint64_t rxVa, + adl::uint64_t dataVa); + + /** + * @return true after a successful init(). + */ + bool isInitialized() const { return initialized_; } + + /* ---- Session management --------------------------------------------- */ + + /** + * Create a TCP session via the HyperAMP proxy. + * + * Builds a HYPERAMP_MSG_TYPE_SESS / SESS_MSG_CREATE command, enqueues + * it on the TX queue, then busy-waits on the RX queue for the + * matching session-create response. On success, stores the + * backend_sess_id for subsequent send/recv calls. + * + * @param ip Destination IPv4 address (network byte order). + * @param port Destination port (host byte order). + * @param devId Backend device selector written into + * SessIPv4Params.device_selection. 0xFF (default) is + * the "auto handover" sentinel which lets the backend + * pick a NIC; pass an explicit id to force a specific + * interface in multi-NIC deployments. + * @return true on success, false on failure. + */ + bool connect(const IP4Addr& ip, + adl::uint16_t port, + adl::uint16_t devId = 0xFF); + + /** + * Send `len` bytes through the established session. + * + * Data is fragmented into HyperAMP queue slots as needed (each slot + * carries up to kHyperAmpMaxPayload bytes of user data after the 8-byte + * header). Enqueue is retried on HYPERAMP_AGAIN (queue full). + * + * @return Number of bytes sent, or -1 on error. + */ + adl::int64_t send(const void* buf, adl::size_t len); + + /** + * Receive exactly `len` bytes from the established session. + * + * Busy-waits (polls) the RX queue until enough HYPERAMP_MSG_TYPE_DATA + * messages have arrived to fill the caller's buffer. Partial trailing + * data from oversized messages is kept in an internal spill buffer for + * the next call. + * + * @return Number of bytes received (always == len on success), or -1. + */ + adl::int64_t recv(void* buf, adl::size_t len); + + /** + * Close the active session. + * + * Sends a HYPERAMP_MSG_TYPE_SESS / SESS_MSG_CLOSE command and waits for + * the response. + */ + void close(); + + /** + * @return true if a session is currently active. + */ + bool hasSession() const { return sessionActive_; } + +private: + /* ---- Construction (private – singleton) ------------------------------ */ + HyperAmpBridge(); + ~HyperAmpBridge() = default; + + /* ---- Internal helpers ------------------------------------------------ */ + + /** + * Enqueue raw bytes to the TX queue. Retries on HYPERAMP_AGAIN. + * @return true on success. + */ + bool txEnqueue(const void* data, adl::size_t len); + + /** + * Blocking dequeue from the RX queue. + * Polls until HYPERAMP_OK. + * + * @param[out] buf Destination buffer (must be >= kHyperAmpBlockSize). + * @param[out] outLen Actual number of bytes dequeued. + * @return true on success. + */ + bool rxDequeueBlocking(void* buf, adl::size_t* outLen); + + /** + * Allocate a fresh frontend_sess_id. Simple monotonic counter. + */ + adl::uint16_t nextFrontendSessId(); + + /* ---- State ---------------------------------------------------------- */ + + bool initialized_ = false; + bool sessionActive_ = false; + + volatile HyperampShmQueue* txQueue_ = nullptr; + volatile HyperampShmQueue* rxQueue_ = nullptr; + volatile void* dataRegion_ = nullptr; + + adl::uint16_t frontendSessId_ = 0; + adl::uint16_t backendSessId_ = 0; + adl::uint16_t sessIdCounter_ = 1; // monotonic counter for session IDs + + /* + * Spill buffer for recv(). + * + * When an RX dequeue delivers more payload bytes than the caller + * requested, the surplus is stashed here and drained on the next recv(). + */ + static constexpr adl::size_t kSpillCapacity = kHyperAmpMaxPayload; + adl::uint8_t spillBuf_[kSpillCapacity]; + adl::size_t spillLen_ = 0; // valid bytes in spillBuf_ + adl::size_t spillOffset_ = 0; // read cursor within spillBuf_ +}; + + +} // namespace monkey::net diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/IP4Addr.cc b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/IP4Addr.cc new file mode 100644 index 0000000000000000000000000000000000000000..4d201fdc70a42a9c8598e144914a6de3bef669e6 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/IP4Addr.cc @@ -0,0 +1,53 @@ +/* + * IPv4 Addr + * + * Created on 2025.1.20 at Zhuhai, Guangdong + * + * + * gongty [at] tongji [dot] edu [dot] cn + * + */ + +#include +#include +#include + +using namespace adl; + +namespace monkey::net { + + +TString IP4Addr::toString() const { + adl::TString ipStr; + + ipStr += adl::TString::to_string((adl::uint32_t) ui8arr[0]); + ipStr += '.'; + ipStr += adl::TString::to_string((adl::uint32_t) ui8arr[1]); + ipStr += '.'; + ipStr += adl::TString::to_string((adl::uint32_t) ui8arr[2]); + ipStr += '.'; + ipStr += adl::TString::to_string((adl::uint32_t) ui8arr[3]); + return ipStr; +} + + +monkey::Status IP4Addr::set(const adl::TString& str) { + ArrayList segments; + str.split(".", segments); + + if (segments.size() != 4) + return Status::INVALID_PARAMETERS; + + for (adl::size_t i = 0; i < 4; i++) { + + // todo: check errors like "192.16aaa.1.1" + + ui8arr[i] = (adl::uint8_t) segments[i].toInt64(); + } + + return Status::SUCCESS; +} + + + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/IP4Addr.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/IP4Addr.h new file mode 100644 index 0000000000000000000000000000000000000000..d195482d3c716829f7e7c11d5716344c6cffb5e7 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/IP4Addr.h @@ -0,0 +1,50 @@ +/* + * IPv4 Addr + * + * Created on 2025.1.20 at Zhuhai, Guangdong + * + * + * gongty [at] tongji [dot] edu [dot] cn + * + */ + +#pragma once + +#include +#include +#include +#include + + +namespace monkey::net { + +class IP4Addr { +public: + union { + adl::uint32_t ui32; // in net order + adl::int32_t i32; // in net order + adl::uint8_t ui8arr[4]; + adl::int8_t i8arr[4]; + }; + +public: + IP4Addr() { i32 = 0; } + IP4Addr(adl::uint8_t ip[4]) { ui32 = * (adl::uint32_t*) ip; } + IP4Addr(adl::int8_t ip[4]) { ui32 = * (adl::uint32_t*) ip; } + IP4Addr(adl::uint32_t ip, bool netOrder = true) { ui32 = netOrder ? ip : adl::htonl(ip); } + IP4Addr(adl::int32_t ip, bool netOrder = true) { i32 = netOrder ? ip : adl::htonl(ip); } + + adl::TString toString() const; + monkey::Status set(const adl::TString&); // like "192.168.1.1" + + bool operator == (const IP4Addr& other) const { return ui32 == other.ui32; } + bool operator != (const IP4Addr& other) const { return ui32 != other.ui32; } + bool operator <= (const IP4Addr& other) const { return ui32 <= other.ui32; } + bool operator >= (const IP4Addr& other) const { return ui32 >= other.ui32; } + bool operator < (const IP4Addr& other) const { return ui32 < other.ui32; } + bool operator > (const IP4Addr& other) const { return ui32 > other.ui32; } + +}; + + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/Socket4.cc b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/Socket4.cc new file mode 100644 index 0000000000000000000000000000000000000000..4d9fa8e9f1572f8fc7a5aaa838e077a5fdeff5df --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/Socket4.cc @@ -0,0 +1,111 @@ +/* + TCP/IPv4 Socket + + created on 2025.2.6 at Xiangzhou, Zhuhai, Guangdong + + Modified 2025 – connect() now goes through HyperAmpBridge instead of + POSIX sockets. start() and accept() are kept as-is (server-side logic + still uses the seL4 POSIX shim for the local listening socket). + + gongty [at] tongji [dot] edu [dot] cn +*/ + +#include +#include + +#include + +#include +#include + + +using namespace monkey; + + +namespace monkey::net { + + +Status Socket4::connect() { + close(); + + Genode::log("[Socket4] Connecting via HyperAmpBridge to ", + ip.toString().c_str(), " : ", port); + + auto& bridge = HyperAmpBridge::instance(); + + /* Ensure the bridge is initialised (idempotent). */ + bridge.init(); + + if (!bridge.connect(ip, port)) { + Genode::error("[Socket4] HyperAmpBridge connect failed"); + return Status::NETWORK_ERROR; + } + + /* + * Set socketFd to a sentinel value > 1 so that valid() returns true. + * The actual I/O bypasses this fd entirely (routed through the bridge + * in PromisedSocketIo::recv / send / close), but upper layers check + * valid() before proceeding. + */ + socketFd = 9999; + + return Status::SUCCESS; +} + + +Status Socket4::start(adl::size_t maxClients) { + if (maxClients > INT32_MAX) { + return Status::INVALID_PARAMETERS; + } + + close(); + + socketFd = socket(AF_INET, SOCK_STREAM, 0); + + if (socketFd == 0) { + return Status::NETWORK_ERROR; + } + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = ip.ui32; + addr.sin_port = adl::htons(port); + + if (bind(socketFd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + close(); + return Status::NETWORK_ERROR; + } + + if (listen(socketFd, adl::int32_t(maxClients)) < 0) { + close(); + return Status::NETWORK_ERROR; + } + + Genode::log("Server started. Addr: ", ip.toString().c_str(), " : ", port); + return Status::SUCCESS; +} + + +Socket4 Socket4::accept(bool ignoreError) { + while (true) { + Genode::log("Waiting for connection..."); + + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + auto fd = ::accept(socketFd, (struct sockaddr*)&addr, &addrlen); + + if (fd != -1) { + Socket4 s; + s.socketFd = fd; + s.ip.ui32 = addr.sin_addr.s_addr; + s.port = adl::ntohs(addr.sin_port); + return s; + } + else if (!ignoreError) { + return Socket4 {}; + } + } +} + + +} // namespace monkey::net diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/Socket4.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/Socket4.h new file mode 100644 index 0000000000000000000000000000000000000000..942ac02806962781664c8b2beffe6cd67c6536b0 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/Socket4.h @@ -0,0 +1,67 @@ +/* + TCP/IPv4 Socket + + created on 2025.2.5 at Xiangzhou, Zhuhai, Guangdong + + gongty [at] tongji [dot] edu [dot] cn +*/ + +#pragma once + +#include +#include +#include + +#include + +namespace monkey::net { + + +/** + * TCP/IPv4 Socket. + * + * Set `ip` and `port` accrodingly, then call `connect` to `start` open connection(s). + */ +struct Socket4 : public monkey::net::PromisedSocketIo { + + /** + * In server mode, this is which ip to listen. + * In client mode, this is which ip to connect to. + */ + IP4Addr ip = { 0 }; + + + /** + * In server mode, this is our port listens for connections. + * In client mode, this is server's port. + */ + adl::uint16_t port = 0; + + + /** + * For client mode. + * + * This should do 2 things: + * 1. Create socket + * 2. Connect to server + */ + virtual monkey::Status connect(); + + + // For server mode. + virtual monkey::Status start(adl::size_t maxClients = 32); + + + /** + * For server mode. + * + * Socket4 returned is a connection client to the other side. It might be INVALID. + * So check whether `valid()` before using that connection. + */ + Socket4 accept(bool ignoreError = false); + +}; + + +} // namespace monkey::net + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/SunflowerLounge.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/SunflowerLounge.h new file mode 100644 index 0000000000000000000000000000000000000000..ba54feab8bbbf1654459b2b6696975606d2e348a --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/SunflowerLounge.h @@ -0,0 +1,41 @@ +/* + * Sunflower Lounge : Base type for various lounges like App Lounge and Memory Lounge. + * + * Using lounge, you can let clients access only specified APIs. + * + * You should ensure client is authenticated before invited to the lounge. + * + * The name `Sunflower` is inspired by CMB. + * https://www.cmbchina.com/personal/allinonecard/cardinfo.aspx?guid=be48a9ab-300f-4393-b80e-02308ddf1a7f + * + * gongty [at] tongji [dot] edu [dot] cn + * Created on 2025.1.25 at Xiangzhou, Zhuhai, Guangdong + */ + +#pragma once + +#include + +namespace monkey::net { + +template +struct SunflowerLounge { + + ContextType& context; + ProtocolConnectionType client; + + SunflowerLounge( + ContextType& context, + ProtocolConnectionType& client + ) + : context {context}, client {client} + {} + + virtual ~SunflowerLounge() {} + + virtual monkey::Status serve() = 0; +}; + + +} // namespace monkey::net + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/TcpIo.cc b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/TcpIo.cc new file mode 100644 index 0000000000000000000000000000000000000000..ed617448a7129944be4dcc29ff5e4bacafce1e7e --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/TcpIo.cc @@ -0,0 +1,36 @@ +/* + * Monkey Net :: TcpIo + * + * Created on 2025.1.21 at Xiangzhou, Zhuhai, Guangdong + * + * Modified 2025 – route all I/O through HyperAmpBridge instead of POSIX + * sockets so that monkey-mnemosyne can operate on seL4 (no network stack). + * + * gongty [at] tongji [dot] edu [dot] cn + */ + + +#include +#include +#include + +namespace monkey::net { + + +void PromisedSocketIo::close() { + HyperAmpBridge::instance().close(); + socketFd = -1; +} + + +adl::int64_t PromisedSocketIo::recv(void* buf, adl::size_t len) { + return HyperAmpBridge::instance().recv(buf, len); +} + + +adl::int64_t PromisedSocketIo::send(const void* buf, adl::size_t len) { + return HyperAmpBridge::instance().send(buf, len); +} + + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/TcpIo.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/TcpIo.h new file mode 100644 index 0000000000000000000000000000000000000000..4dd716aa10224ed3960e7ed6851152be3c25e51b --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/TcpIo.h @@ -0,0 +1,75 @@ +/* + * Monkey Net :: TcpIo + * + * Created on 2024.12.27 at Minhang, Shanghai + * + * + * feng.yt [at] sjtu [dot] edu [dot] cn + * gongty [at] tongji [dot] edu [dot] cn + * + */ + + +#pragma once + +#include +#include +#include + +namespace monkey::net { + + +class TcpIo { + +public: + virtual ~TcpIo() {} + + /** + * + * @param len Buffer size. + */ + virtual adl::int64_t recv(void* buf, adl::size_t len) = 0; + + + /** + * + * @param len Size of data. + */ + virtual adl::int64_t send(const void* buf, adl::size_t len) = 0; + +}; + + +/** + * Promised Socket IO is NOT closed automatically. + * You should call `close` as soon as you are finished with it. + */ +class PromisedSocketIo : public TcpIo { +public: + int socketFd = -1; + + + /** + * This method is reentrant. + * + * This method is a no-op if the socket has already been closed. + * + * You should call this as soon as you doesn't use this socket. + */ + virtual void close(); + + virtual ~PromisedSocketIo() override { + + } + + virtual bool valid() { return socketFd > 1; } + + virtual adl::int64_t recv(void* buf, adl::size_t len) override; + + virtual adl::int64_t send(const void* buf, adl::size_t len) override; + +}; + + + +} // namespace monkey::net diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/channel_ch2.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/channel_ch2.h new file mode 100644 index 0000000000000000000000000000000000000000..4b5a2d2306d703905bed0988fff8b066db30e6ba --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/channel_ch2.h @@ -0,0 +1,79 @@ +/** + * @file channel_ch2.h + * @brief HyperAMP channel-2 layout constants for monkey-mnemosyne. + * + * Background + * ---------- + * Per HYPERAMP_MULTI_CHANNEL_DESIGN.md (top of repo) and + * apps/hyperamp-server/src/{channel.c,main.c}, the seL4 boot loader + * maps a single 4 MiB window of shared memory and publishes the per- + * channel virtual addresses through IPC message registers: + * + * msg[2..4] -> CH0 (TX, RX, Data) -- hyperamp-server + * msg[5..7] -> CH1 (TX, RX, Data) -- front / HighSpeedCProxy + * msg[8..10] -> CH2 (TX, RX, Data) -- monkey-mnemosyne (us) + * + * Physical addresses are a fixed offset from a per-platform base: + * + * CH0 base = base + 0x000000 + * CH1 base = base + 0x200000 + * CH2 base = base + 0x300000 + * + * The virtual addresses are NOT compile-time constants; they are + * computed at boot by the kernel and must be read from the IPC MRs + * *before any seL4 syscall is issued*, otherwise the IPC buffer is + * overwritten and the values are lost. See mnemosyne_api.cc for the + * single authoritative reader. + * + * This header therefore exposes only the bits that ARE compile-time + * constant: the MR slot indices, the physical address offsets, and + * the queue capacity. + * + * Why a separate header + * --------------------- + * Mirrors the apps/hyperamp-server/include/channel_ch1.h pattern + * (one tiny header per channel) so that integrators can cross- + * reference the two cleanly when wiring up a unified application. + * + * gongty [at] tongji [dot] edu [dot] cn + */ + +#ifndef MONKEY_NET_CHANNEL_CH2_H +#define MONKEY_NET_CHANNEL_CH2_H + +/* ---- IPC message-register slots ---------------------------------------- */ +/* + * NOTE: seL4 message registers are read with seL4_GetMR(i). The kernel + * boot loader writes ipcBuf[i + 1] = vaddr, hence MR_SLOT_CH2_TX = 8 + * corresponds to seL4_GetMR(8) returning the CH2 TX queue vaddr. + */ +#define MNEMOSYNE_MR_SLOT_CH2_TX 8 +#define MNEMOSYNE_MR_SLOT_CH2_RX 9 +#define MNEMOSYNE_MR_SLOT_CH2_DATA 10 + +/* ---- Physical address offsets (relative to the platform shm base) ------ */ +/* + * The platform shm base lives in hyperamp_shm_queue.h as + * SHM_TX_QUEUE_PADDR (defined per CONFIG_PLAT_*). We derive the CH2 + * physical addresses from that base + this offset, exactly the way + * apps/front/src/engine.c derives CH1 from base + HYPERAMP_CH1_OFFSET_PADDR. + */ +#define MNEMOSYNE_CH2_PADDR_OFFSET 0x300000UL +#define MNEMOSYNE_CH2_TX_PADDR_OFFSET (MNEMOSYNE_CH2_PADDR_OFFSET + 0x000UL) +#define MNEMOSYNE_CH2_RX_PADDR_OFFSET (MNEMOSYNE_CH2_PADDR_OFFSET + 0x1000UL) + +/* ---- Queue capacity ---------------------------------------------------- */ +/* + * Same as apps/hyperamp-server CH1 (253): + * total CH2 area = 1 MiB + * minus 2 control pages (TX queue + RX queue header), one block each + * data region = 1 MiB - 8 KiB = 254 * 4 KiB blocks + * enqueue uses (idx + 1) * block_size, so capacity must be <= 253. + * + * monkey-mnemosyne actually only needs a single 4 KiB block (one shared + * page); 253 is intentionally generous to leave headroom and to keep + * the layout consistent with CH1 when the integrator visualises it. + */ +#define MNEMOSYNE_CH2_QUEUE_CAPACITY 253 + +#endif /* MONKEY_NET_CHANNEL_CH2_H */ diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/config.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/config.h new file mode 100644 index 0000000000000000000000000000000000000000..f831dee7bc3ab509587b2b7cb2d425a0435e830c --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/config.h @@ -0,0 +1,14 @@ +/* + Monkey Net Config + + Created by gongty on 2025.6.29 at Minhang +*/ + +#pragma once + +#include + +/** + * @deprecated + */ +#define VESPER_PROTOCOL_DEBUG MONKEY_CFG_ENABLE_DEBUG_LOGS diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/hexview.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/hexview.h new file mode 100644 index 0000000000000000000000000000000000000000..01549012a8796672a3549059a15bc2ae7a9651f6 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/hexview.h @@ -0,0 +1,145 @@ +/* + * Portable hexdump util for C/C++ + * + * gongty [at] tongji [dot] edu [dot] cn + * Created on 2024.12.8 at Jiangchuan, Minhang, Shanghai + * + * + * Usage: + * You can change ReadFunc and WriteFunc to any type you want. + * But you should ensure their prototype: + * - ReadFunc : Callable. Returning one character as int. EOF for end. + * - WriteFunc: Callable. Receive one int as character. + */ + + +#include +#include +#include + +namespace monkey::net::debug { + +/** + * @param num + * @param width Pass 0 for non-fixed width. + * If num's hex width is wider, this argument is ignored. + * @param upperCase Pass non-zero for upper-case. Pass zero for lower-case. + */ +template +static inline void writeHex(unsigned long num, int width, int upperCase, WriteFunc out) { + int digitWidth = 1; + + { + unsigned long needle = 0xF; + while (needle < num) { + needle <<= 4; + needle |= 0xF; + digitWidth ++; + } + + if (width > 0) { + for (int w = digitWidth; w < width; w++) + out('0'); + } + } + + + while (digitWidth) { + unsigned long digit = num >> ((digitWidth - 1) * 4); + digit &= 0xF; + if (digit >= 0 && digit <= 9) { + out(digit + '0'); + } else { // digit is in [a, f] + out(digit - 0xA + (upperCase ? 'A' : 'a')); + } + + digitWidth --; + } + +} + + +template +static inline void writeLine(int addr, int buf[], int len, int upperCase, WriteFunc out) { + writeHex(addr, 8, upperCase, out); + out(' '); + out(' '); + + for (int i = 0; i < 16; i++) { + if (i == 8) { + out(i < len ? '-' : ' '); + out(' '); + } + if (i < len) { + writeHex(buf[i] & 0xFF, 2, upperCase, out); + } else { + out(' '); + out(' '); + } + out(' '); + } + + out(' '); + out(' '); + out(' '); + out(' '); + for (int i = 0; i < len; i++) { + if (buf[i] >= 33 && buf[i] <= 126) + out(buf[i]); + else + out('.'); + } + + out('\n'); +} + + +template +inline adl::size_t hexView(int upperCase, ReadFunc in, WriteFunc out) { + int buf[16]; + int pos = 0; // One after last occupied. + + adl::size_t addr = 0; + adl::size_t bytesRead = 0; + + while (true) { + int ch = in(); + if (ch == EOF) + break; + + buf[pos++] = ch; + if (pos == 16) { + writeLine(addr, buf, 16, upperCase, out); + addr += 16; + pos = 0; + } + } + + + if (pos) + writeLine(addr, buf, pos, upperCase, out); + return bytesRead; +} + + +inline adl::size_t hexView(const void* data, adl::size_t len) { + adl::TString hex; + adl::size_t count = 0; + auto ret = hexView( + true, + [&] () { + if (count < len) + return (int) ((const char*) data)[count++]; + else + return EOF; + }, + [&] (int ch) { + hex += char(ch); + } + ); + MONKEY_LOG_INFO(hex.c_str()); + return ret; +} + + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/hyperamp_protocol_defs.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/hyperamp_protocol_defs.h new file mode 100644 index 0000000000000000000000000000000000000000..fc70bc057a17c054c4d19265e76e412701c052e4 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/hyperamp_protocol_defs.h @@ -0,0 +1,95 @@ +/** + * @file hyperamp_protocol_defs.h + * @brief HyperAMP proxy protocol definitions – monkey-mnemosyne subset + * + * Extracted from apps/front/include/message.h. Contains only the structures + * and enumerations required by HyperAmpBridge: + * + * - SessMsgHeader (session create/close sub-header) + * - SessIPv4Params (IPv4 session parameters) + * - SessOpRespData (session operation response) + * - Related enums (SessMsgType, SessIpProtoVersion, SessTranProto, + * SessOpStatus, ActionType) + * - PROXY_PROTO_VERSION_1 / PROXY_PROTO_SESS_VERSION_1 + * + * This file has NO dependency on common_utils.h, message.h, or any other + * apps/front header. All definitions are self-contained. + */ + +#ifndef HYPERAMP_PROTOCOL_DEFS_LOCAL_H +#define HYPERAMP_PROTOCOL_DEFS_LOCAL_H + +#include + +/* ==================== Protocol version constants ==================== */ + +#define PROXY_PROTO_VERSION_1 1 +#define PROXY_PROTO_SESS_VERSION_1 1 + +/* ==================== Action type ==================== */ + +typedef enum { + ACTION_TYPE_COMMAND = 0, + ACTION_TYPE_RESPONSE = 1 +} ActionType; + +/* ==================== Session message header ==================== */ + +typedef struct { + uint16_t version; + uint16_t msg_type; + uint16_t action_type; + uint16_t ip_version; + uint16_t payload_len; +} __attribute__((packed)) SessMsgHeader; + +typedef enum { + SESS_MSG_CLOSE = 0, + SESS_MSG_CREATE = 1 +} SessMsgType; + +typedef enum { + SESS_NON_IP_PROTO = 0, + SESS_IPV4_PROTO = 4, + SESS_IPV6_PROTO = 6 +} SessIpProtoVersion; + +typedef enum { + SESS_UDP_PROTO = 0, + SESS_TCP_PROTO = 1, + SESS_FASTPATH_PROTO = 2 +} SessTranProto; + +/* ==================== IPv4 address / endpoint ==================== */ + +struct IPv4Address { + uint8_t data[4]; +} __attribute__((packed)); + +typedef struct { + struct IPv4Address ipv4_addr; + uint16_t port; +} __attribute__((packed)) IPv4PortTuple; + +/* ==================== Session IPv4 parameters ==================== */ + +typedef struct { + uint16_t device_selection; + uint16_t transport_layer_proto; + IPv4PortTuple dest_endpoint; +} __attribute__((packed)) SessIPv4Params; + +/* ==================== Session operation response ==================== */ + +typedef enum { + SESS_OP_STATUS_SUCCESS = 0, + SESS_OP_STATUS_FAIL = 1 +} SessOpStatus; + +typedef struct SessOpRespData_ { + uint8_t status; + uint8_t code; +} __attribute__((packed)) SessOpRespData; + + +#endif /* HYPERAMP_PROTOCOL_DEFS_LOCAL_H */ diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/hyperamp_shm_queue.c b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/hyperamp_shm_queue.c new file mode 100644 index 0000000000000000000000000000000000000000..adcfcc743bea7dd3bedec8c605ff3606ce5e7de9 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/hyperamp_shm_queue.c @@ -0,0 +1,406 @@ +/** + * @file hyperamp_shm_queue.c + * @brief HyperAMP Shared Memory Queue – monkey-mnemosyne local copy + * + * Copied from apps/front/src/hyperamp_shm_queue.c and cleaned up: + * + * 1. Removed #include "common_utils.h" and all calls to + * parse_proxy_protocol_and_print() / DUMP_PROXY_MSG_HEADER(). + * 2. Removed print_hex() / print_string() debug helpers (not needed). + * 3. Fixed the original bug on line 343 where print_hex() was called with + * a uint64_t instead of a const uint8_t*. + * 4. Removed the TRUSTED_PUBKEY_DER array (signature verification is + * not used by monkey-mnemosyne). + * 5. Retained all queue operation logic verbatim. + */ + +#include "hyperamp_shm_queue.h" + + +/* ==================== Software Spinlock ==================== */ + +static inline void hyperamp_spinlock_init(volatile HyperampSpinlock *lock) +{ + if (!lock) return; + lock->lock_value = 0; + lock->owner_zone_id = 0; + lock->lock_count = 0; + lock->contention_count = 0; + HYPERAMP_BARRIER(); +} + +static inline void hyperamp_spinlock_lock(volatile HyperampSpinlock *lock, uint32_t zone_id) +{ + if (!lock) return; +#if defined(__aarch64__) + uint32_t tmp, newval; + __asm__ volatile( + "1: ldaxr %w0, %2\n" + " cbnz %w0, 1b\n" + " mov %w1, #1\n" + " stlxr %w0, %w1, %2\n" + " cbnz %w0, 1b\n" + : "=&r" (tmp), "=&r" (newval), "+Q" (lock->lock_value) + : + : "memory" + ); + lock->owner_zone_id = zone_id; + lock->lock_count++; +#else + while (1) { + HYPERAMP_BARRIER(); + if (lock->lock_value == 0) { + lock->lock_value = 1; + HYPERAMP_BARRIER(); + if (lock->lock_value == 1) { + lock->owner_zone_id = zone_id; + lock->lock_count++; + return; + } + } + lock->contention_count++; + __asm__ volatile("pause" ::: "memory"); + } +#endif +} + +static inline void hyperamp_spinlock_unlock(volatile HyperampSpinlock *lock) +{ + if (!lock) return; + lock->owner_zone_id = 0; +#if defined(__aarch64__) + __asm__ volatile( + "stlr wzr, %0\n" + : "+Q" (lock->lock_value) + : + : "memory" + ); +#else + HYPERAMP_BARRIER(); + lock->lock_value = 0; + HYPERAMP_BARRIER(); +#endif +} + + +/* Verify queue_lock alignment for LDAXR/STXR on aarch64. */ +_Static_assert(offsetof(HyperampShmQueue, queue_lock) % 4 == 0, + "HyperampShmQueue.queue_lock must be 4-byte aligned for LDAXR/STXR"); +_Static_assert(sizeof(HyperampSpinlock) == 16, + "HyperampSpinlock size must be 16 bytes"); + +/* Safe pointer to queue_lock (avoids -Waddress-of-packed-member). */ +#define HYPERAMP_QUEUE_LOCK(q) \ + ((volatile HyperampSpinlock *)((volatile uint8_t *)(q) + offsetof(HyperampShmQueue, queue_lock))) + + +/* ==================== Secure Memory Operations ==================== */ + +static inline void hyperamp_safe_memset(volatile void *dst, uint8_t val, size_t len) +{ + volatile uint8_t *p = (volatile uint8_t *)dst; + for (size_t i = 0; i < len; i++) { + p[i] = val; + } + HYPERAMP_BARRIER(); +} + +static inline void hyperamp_safe_memcpy(volatile void *dst, const volatile void *src, size_t len) +{ + volatile uint8_t *d = (volatile uint8_t *)dst; + const volatile uint8_t *s = (const volatile uint8_t *)src; + for (size_t i = 0; i < len; i++) { + d[i] = s[i]; + } + HYPERAMP_BARRIER(); +} + +uint16_t hyperamp_safe_read_u16(const volatile void *addr, size_t offset) +{ + const volatile uint8_t *p = (const volatile uint8_t *)addr; + uint16_t val = 0; + for (int i = 0; i < 2; i++) { + val |= ((uint16_t)p[offset + i]) << (i * 8); + } + HYPERAMP_BARRIER(); + return val; +} + +static inline uint32_t hyperamp_safe_read_u32(const volatile void *addr, size_t offset) +{ + const volatile uint8_t *p = (const volatile uint8_t *)addr; + uint32_t val = 0; + for (int i = 0; i < 4; i++) { + val |= ((uint32_t)p[offset + i]) << (i * 8); + } + HYPERAMP_BARRIER(); + return val; +} + +static inline uint64_t hyperamp_safe_read_u64(const volatile void *addr, size_t offset) +{ + const volatile uint8_t *p = (const volatile uint8_t *)addr; + uint64_t val = 0; + for (int i = 0; i < 8; i++) { + val |= ((uint64_t)p[offset + i]) << (i * 8); + } + HYPERAMP_BARRIER(); + return val; +} + + +/* ==================== Queue Operation Functions ==================== */ + +int hyperamp_queue_is_initialized(volatile HyperampShmQueue *queue) +{ + if (!queue) return 0; + + HYPERAMP_BARRIER(); + + size_t capacity_offset = offsetof(HyperampShmQueue, capacity); + volatile uint8_t *p = (volatile uint8_t *)queue; + + uint16_t capacity = 0; + for (int i = 0; i < 2; i++) { + capacity |= ((uint16_t)p[capacity_offset + i]) << (i * 8); + } + + HYPERAMP_BARRIER(); + return (capacity > 0); +} + + +int hyperamp_queue_enqueue(volatile HyperampShmQueue *queue, + uint32_t zone_id, + const void *data, + size_t data_len, + volatile void *virt_base) +{ + if (!queue || !data || data_len == 0) { + printf("hyperamp_queue_enqueue failed: queue=%p, data=%p, data_len=%zu\n", + (void *)queue, data, data_len); + return HYPERAMP_ERROR; + } + + uint16_t block_size = hyperamp_safe_read_u16(queue, offsetof(HyperampShmQueue, block_size)); + uint16_t capacity = hyperamp_safe_read_u16(queue, offsetof(HyperampShmQueue, capacity)); + + if (data_len > block_size) return HYPERAMP_ERROR; + + hyperamp_spinlock_lock(HYPERAMP_QUEUE_LOCK(queue), zone_id); + + uint16_t header = hyperamp_safe_read_u16(queue, offsetof(HyperampShmQueue, header)); + uint16_t tail = hyperamp_safe_read_u16(queue, offsetof(HyperampShmQueue, tail)); + + uint16_t new_header = header + 1; + if (new_header >= capacity) { + new_header -= capacity; + } + + /* Check if the queue is full. */ + if (new_header == tail) { + hyperamp_spinlock_unlock(HYPERAMP_QUEUE_LOCK(queue)); + return HYPERAMP_AGAIN; + } + + /* Compute the write address. */ + uint64_t write_addr = (uint64_t)(uintptr_t)virt_base + (uint64_t)(header + 1) * block_size; + + /* Write the data. */ + hyperamp_safe_memcpy((volatile void *)(uintptr_t)write_addr, data, data_len); + + /* Update the header (byte-by-byte). */ + volatile uint8_t *p = (volatile uint8_t *)queue; + size_t header_offset = offsetof(HyperampShmQueue, header); + p[header_offset] = new_header & 0xFF; + p[header_offset + 1] = (new_header >> 8) & 0xFF; + + /* Update enqueue_count. */ + size_t enqueue_offset = offsetof(HyperampShmQueue, enqueue_count); + uint32_t enqueue_count = hyperamp_safe_read_u32(queue, enqueue_offset); + enqueue_count++; + for (int i = 0; i < 4; i++) { + p[enqueue_offset + i] = (enqueue_count >> (i * 8)) & 0xFF; + } + + HYPERAMP_BARRIER(); + + hyperamp_cache_clean((volatile void *)(uintptr_t)write_addr, data_len); + hyperamp_cache_clean((volatile void *)queue, 64); + + hyperamp_spinlock_unlock(HYPERAMP_QUEUE_LOCK(queue)); + + return HYPERAMP_OK; +} + + +int hyperamp_queue_dequeue(volatile HyperampShmQueue *queue, + uint32_t zone_id, + void *data, + size_t max_len, + size_t *actual_len, + volatile void *virt_base) +{ + if (!queue || !data || max_len == 0) { + printf("hyperamp_queue_dequeue failed: queue=%p, data=%p, max_len=%zu\n", + (void *)queue, data, max_len); + return HYPERAMP_ERROR; + } + + hyperamp_cache_invalidate((volatile void *)queue, 64); + HYPERAMP_BARRIER(); + + hyperamp_spinlock_lock(HYPERAMP_QUEUE_LOCK(queue), zone_id); + + uint16_t header = hyperamp_safe_read_u16(queue, offsetof(HyperampShmQueue, header)); + uint16_t tail = hyperamp_safe_read_u16(queue, offsetof(HyperampShmQueue, tail)); + uint16_t block_size = hyperamp_safe_read_u16(queue, offsetof(HyperampShmQueue, block_size)); + uint16_t capacity = hyperamp_safe_read_u16(queue, offsetof(HyperampShmQueue, capacity)); + + /* Check if the queue is empty. */ + if (tail == header) { + hyperamp_spinlock_unlock(HYPERAMP_QUEUE_LOCK(queue)); + return HYPERAMP_AGAIN; + } + + /* Compute the read address. */ + uint64_t read_addr = (uint64_t)(uintptr_t)virt_base + (uint64_t)(tail + 1) * block_size; + + hyperamp_cache_invalidate((volatile void *)(uintptr_t)read_addr, block_size); + HYPERAMP_BARRIER(); + + size_t read_len = (max_len < block_size) ? max_len : block_size; + + hyperamp_safe_memcpy(data, (const volatile void *)(uintptr_t)read_addr, read_len); + + if (actual_len) { + *actual_len = read_len; + } + + /* Update tail. */ + uint16_t new_tail = tail + 1; + if (new_tail >= capacity) { + new_tail -= capacity; + } + + volatile uint8_t *p = (volatile uint8_t *)queue; + size_t tail_offset = offsetof(HyperampShmQueue, tail); + p[tail_offset] = new_tail & 0xFF; + p[tail_offset + 1] = (new_tail >> 8) & 0xFF; + + /* Update dequeue_count. */ + size_t dequeue_offset = offsetof(HyperampShmQueue, dequeue_count); + uint32_t dequeue_count = hyperamp_safe_read_u32(queue, dequeue_offset); + dequeue_count++; + for (int i = 0; i < 4; i++) { + p[dequeue_offset + i] = (dequeue_count >> (i * 8)) & 0xFF; + } + + HYPERAMP_BARRIER(); + + hyperamp_spinlock_unlock(HYPERAMP_QUEUE_LOCK(queue)); + + return HYPERAMP_OK; +} + + +int hyperamp_queue_init(volatile HyperampShmQueue *queue, + const HyperampQueueConfig *config, + int is_creator) +{ + if (!queue || !config) return HYPERAMP_ERROR; + if (config->block_size == 0 || config->capacity == 0) return HYPERAMP_ERROR; + + if (is_creator) { + volatile uint8_t *p = (volatile uint8_t *)queue; + + /* map_mode1, map_mode2 */ + p[0] = config->map_mode; + p[1] = config->map_mode; + HYPERAMP_BARRIER(); + + /* header (uint16_t, offset 2) */ + p[2] = 0; p[3] = 0; + HYPERAMP_BARRIER(); + + /* tail (uint16_t, offset 4) */ + p[4] = 0; p[5] = 0; + HYPERAMP_BARRIER(); + + /* capacity (uint16_t, offset 6) */ + uint16_t cap = config->capacity; + p[6] = cap & 0xFF; + p[7] = (cap >> 8) & 0xFF; + HYPERAMP_BARRIER(); + + /* block_size (uint16_t, offset 8) */ + uint16_t bs = config->block_size; + p[8] = bs & 0xFF; + p[9] = (bs >> 8) & 0xFF; + HYPERAMP_BARRIER(); + + /* _reserved (uint16_t, offset 10) */ + p[10] = 0; p[11] = 0; + HYPERAMP_BARRIER(); + + /* phy_addr (uint64_t, offset 12) */ + uint64_t pa = config->phy_addr; + for (int i = 0; i < 8; i++) { + p[12 + i] = (pa >> (i * 8)) & 0xFF; + } + HYPERAMP_BARRIER(); + + /* virt_addr1 (uint64_t, offset 20) */ + uint64_t va = config->virt_addr; + for (int i = 0; i < 8; i++) { + p[20 + i] = (va >> (i * 8)) & 0xFF; + } + HYPERAMP_BARRIER(); + + /* virt_addr2 (uint64_t, offset 28) – zero */ + for (int i = 0; i < 8; i++) { + p[28 + i] = 0; + } + HYPERAMP_BARRIER(); + + /* Initialize spinlock */ + size_t lock_offset = offsetof(HyperampShmQueue, queue_lock); + volatile HyperampSpinlock *lock = (volatile HyperampSpinlock *)&p[lock_offset]; + hyperamp_spinlock_init(lock); + + /* magic */ + size_t magic_offset = offsetof(HyperampShmQueue, magic); + uint32_t magic = HYPERAMP_QUEUE_MAGIC; + for (int i = 0; i < 4; i++) { + p[magic_offset + i] = (magic >> (i * 8)) & 0xFF; + } + HYPERAMP_BARRIER(); + + /* version */ + uint32_t ver = 1; + for (int i = 0; i < 4; i++) { + p[magic_offset + 4 + i] = (ver >> (i * 8)) & 0xFF; + } + HYPERAMP_BARRIER(); + + /* enqueue_count – zero */ + for (int i = 0; i < 4; i++) { + p[magic_offset + 8 + i] = 0; + } + HYPERAMP_BARRIER(); + + /* dequeue_count – zero */ + for (int i = 0; i < 4; i++) { + p[magic_offset + 12 + i] = 0; + } + HYPERAMP_BARRIER(); + + hyperamp_cache_clean((volatile void *)queue, sizeof(HyperampShmQueue)); + + } else { + queue->virt_addr2 = config->virt_addr; + HYPERAMP_BARRIER(); + } + + return HYPERAMP_OK; +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/hyperamp_shm_queue.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/hyperamp_shm_queue.h new file mode 100644 index 0000000000000000000000000000000000000000..19b813bf21b9e263420f1e21881cf3c96ef8ad2a --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/hyperamp_shm_queue.h @@ -0,0 +1,236 @@ +/** + * @file hyperamp_shm_queue.h + * @brief HyperAMP Shared Memory Queue – monkey-mnemosyne local copy + * + * Copied from apps/front/include/hyperamp_shm_queue.h and stripped down for + * use within monkey-mnemosyne. Changes from the original: + * + * 1. Removed #include "hardware_config.h" – platform physical addresses are + * guarded by __aarch64__ instead of CONFIG_PLAT_* so that x86_64 + * simulation builds work. + * 2. Removed __attribute__((packed)) from HyperampShmQueue to eliminate the + * -Wpacked-not-aligned warning when HyperampSpinlock (aligned(4)) is + * embedded. The struct layout is identical because all fields are + * naturally aligned or explicitly packed sub-structs. + * 3. Removed dependency on common_utils.h. + * + * All queue function signatures remain binary-compatible with the original. + */ + +#ifndef HYPERAMP_SHM_QUEUE_LOCAL_H +#define HYPERAMP_SHM_QUEUE_LOCAL_H + +#include +#include +#include + +/* ==================== Constant Definitions ==================== */ + +#define HYPERAMP_ERROR_ADDR UINT64_MAX +#define HYPERAMP_MAX_MAP_TABLE_ENTRIES 125 + +#define HYPERAMP_OK 0 +#define HYPERAMP_ERROR (-1) +#define HYPERAMP_AGAIN 1 + +#define HYPERAMP_ZONE_ID_ROOTLINUX 0 +#define HYPERAMP_ZONE_ID_SEL4 1 + +/* Memory mapping modes */ +typedef enum { + HYPERAMP_MAP_MODE_CONTIGUOUS_BOTH = 0, + HYPERAMP_MAP_MODE_CONTIGUOUS_PHYS_DISCRETE_LOGICAL +} HyperampMapMode; + +/* Message constants */ +#define HYPERAMP_MSG_HDR_SIZE 8 +#define HYPERAMP_MSG_MIN_SIZE 1 +#define HYPERAMP_MSG_MAX_SIZE 4088 +#define HYPERAMP_MSG_HDR_PLUS_MAX_SIZE (HYPERAMP_MSG_HDR_SIZE + HYPERAMP_MSG_MAX_SIZE) + +/* Queue magic number */ +#define HYPERAMP_QUEUE_MAGIC 0x48415150 /* "HAQP" */ + + +/* ==================== Memory Barriers and Cache Operations ==================== */ + +#if defined(__aarch64__) || defined(__arm__) + #define HYPERAMP_DMB() __asm__ volatile("dmb sy" ::: "memory") + #define HYPERAMP_ISB() __asm__ volatile("isb" ::: "memory") + + static inline void hyperamp_cache_clean(volatile void *addr, size_t size) { + volatile char *p = (volatile char *)addr; + volatile char *end = p + size; + for (; p < end; p += 64) { + __asm__ volatile("dc cvac, %0" : : "r"(p) : "memory"); + } + __asm__ volatile("dsb sy" ::: "memory"); + } + + static inline void hyperamp_cache_invalidate(volatile void *addr, size_t size) { + volatile char *p = (volatile char *)addr; + volatile char *end = p + size; + for (; p < end; p += 64) { + __asm__ volatile("dc civac, %0" : : "r"(p) : "memory"); + } + __asm__ volatile("dsb sy" ::: "memory"); + } +#else + /* x86 / simulation fallback */ + #define HYPERAMP_DMB() __asm__ volatile("mfence" ::: "memory") + #define HYPERAMP_ISB() __asm__ volatile("" ::: "memory") + + static inline void hyperamp_cache_clean(volatile void *addr, size_t size) { + (void)addr; (void)size; + __asm__ volatile("mfence" ::: "memory"); + } + + static inline void hyperamp_cache_invalidate(volatile void *addr, size_t size) { + (void)addr; (void)size; + __asm__ volatile("mfence" ::: "memory"); + } +#endif + +/* ==================== Platform addresses ==================== */ + +/* + * Physical addresses – only meaningful on real hardware. + * For simulation builds we still need the symbols to exist so that + * the code compiles; the actual values are irrelevant because the + * bridge uses the compile-time virtual addresses below. + */ +#if defined(__aarch64__) || defined(__arm__) + #define SHM_TX_QUEUE_PADDR 0x7E000000UL + #define SHM_RX_QUEUE_PADDR 0x7E001000UL + #define SHM_DATA_PADDR 0x7E002000UL +#else + /* x86_64 simulation – dummy physical addresses (never dereferenced). */ + #define SHM_TX_QUEUE_PADDR 0x7E000000UL + #define SHM_RX_QUEUE_PADDR 0x7E001000UL + #define SHM_DATA_PADDR 0x7E002000UL +#endif + +/* Virtual addresses – these are the fixed mappings set up by the seL4 boot + * loader (boot.c). Identical across platforms. */ +#define SHM_TX_QUEUE_VADDR ((volatile HyperampShmQueue *)0x55E000UL) +#define SHM_RX_QUEUE_VADDR ((volatile HyperampShmQueue *)0x55F000UL) +#define SHM_DATA_REGION_VA ((volatile void *)0x560000UL) + + +#define HYPERAMP_BARRIER() HYPERAMP_DMB() + +/* ==================== Software Spinlock ==================== */ + +typedef struct { + volatile uint32_t lock_value; + volatile uint32_t owner_zone_id; + volatile uint32_t lock_count; + volatile uint32_t contention_count; +} __attribute__((aligned(4))) HyperampSpinlock; + +/* ==================== Address Mapping Table Entry ==================== */ + +typedef struct { + uint64_t virt_addr; + uint64_t phy_addr; +} __attribute__((packed)) HyperampMapTableEntry; + +/* ==================== Shared Memory Pool Queue ==================== */ + +/* + * NOTE: __attribute__((packed)) has been intentionally REMOVED from + * HyperampShmQueue in this local copy. The original in apps/front uses + * __packed__, but when a C++ translation unit includes this header the + * compiler warns (-Wpacked-not-aligned) because HyperampSpinlock carries + * __attribute__((aligned(4))) inside a packed struct. + * + * Removing __packed__ is safe because: + * - All scalar fields are naturally aligned (uint8/16/64). + * - HyperampMapTableEntry is itself __packed__ (16 bytes, no padding). + * - HyperampSpinlock is 16 bytes, aligned(4), placed after the two + * table arrays (125 * 16 = 2000 bytes each), so the offset is even. + * - The overall sizeof(HyperampShmQueue) remains unchanged. + * + * The queue functions use byte-by-byte safe accessors anyway, so the + * in-memory layout is identical. + */ +typedef struct { + uint8_t map_mode1; + uint8_t map_mode2; + uint16_t header; + uint16_t tail; + uint16_t capacity; + uint16_t block_size; + uint16_t _reserved; + + uint64_t phy_addr; + uint64_t virt_addr1; + uint64_t virt_addr2; + + HyperampMapTableEntry table1[HYPERAMP_MAX_MAP_TABLE_ENTRIES]; + HyperampMapTableEntry table2[HYPERAMP_MAX_MAP_TABLE_ENTRIES]; + + HyperampSpinlock queue_lock; + + uint32_t magic; + uint32_t version; + uint32_t enqueue_count; + uint32_t dequeue_count; + +} HyperampShmQueue; + +/* ==================== Message Header Structure ==================== */ + +typedef struct { + uint8_t version; + uint8_t proxy_msg_type; + uint16_t frontend_sess_id; + uint16_t backend_sess_id; + uint16_t payload_len; +} __attribute__((packed)) HyperampMsgHeader; + +/* Message types */ +typedef enum { + HYPERAMP_MSG_TYPE_DEV = 0, + HYPERAMP_MSG_TYPE_STRGY = 1, + HYPERAMP_MSG_TYPE_SESS = 2, + HYPERAMP_MSG_TYPE_DATA = 3, + HYPERAMP_MSG_TYPE_SERVICE = 0x10, + HYPERAMP_MSG_TYPE_BULK = 0x20 +} HyperampMsgType; + +/* ==================== Queue Configuration Structure ==================== */ + +typedef struct { + uint16_t map_mode; + uint16_t capacity; + uint16_t block_size; + uint16_t _reserved; + uint64_t phy_addr; + uint64_t virt_addr; +} HyperampQueueConfig; + + +/* ==================== Function Declarations ==================== */ + +int hyperamp_queue_is_initialized(volatile HyperampShmQueue *queue); + +int hyperamp_queue_init(volatile HyperampShmQueue *queue, + const HyperampQueueConfig *config, + int is_creator); + +int hyperamp_queue_enqueue(volatile HyperampShmQueue *queue, + uint32_t zone_id, + const void *data, + size_t data_len, + volatile void *virt_base); + +int hyperamp_queue_dequeue(volatile HyperampShmQueue *queue, + uint32_t zone_id, + void *data, + size_t max_len, + size_t *actual_len, + volatile void *virt_base); + + +#endif /* HYPERAMP_SHM_QUEUE_LOCAL_H */ diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol.h new file mode 100644 index 0000000000000000000000000000000000000000..0084b500f0d47c7e559e05af0a720b0741d8a2b4 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol.h @@ -0,0 +1,24 @@ +/* + * Monkey Network Protocol + * + * Created on 2024.12 at Minhang, Shanghai + * + * + * feng.yt [at] sjtu [dot] edu [dot] cn + * gongty [at] tongji [dot] edu [dot] cn + * + */ + + +#pragma once + +#include + +namespace monkey::net::protocol { + const adl::int32_t LATEST_VERSION = 2; +} + +#include + +#include +#include diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/Protocol1Connection.cc b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/Protocol1Connection.cc new file mode 100644 index 0000000000000000000000000000000000000000..f53681ed455b0c837f74177d4acfade513c4d0b6 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/Protocol1Connection.cc @@ -0,0 +1,753 @@ +/* + * Protocol V1 Connection + * + * Created on 2025.1.21 at Xiangzhou, Zhuhai, Guangdong + * + * + * gongty [at] tongji [dot] edu [dot] cn + * + */ + + +#include +#include + +#define RECV_AND_HANDLE_RESPONSE MONKEY_PROTOCOL_RECV_AND_HANDLE_RESPONSE + +namespace monkey::net { + +using protocol::Msg; +using protocol::MsgType; +using protocol::Response; + + +void Protocol1Connection::LastError::set(protocol::Response* response, const char* api) { + set(response->code, response->msg, response->msgLen, api); +} + + +void Protocol1Connection::LastError::set( + adl::uint32_t code, + const void* msg, + adl::size_t msgLen, + const char* api +) { + this->code = code; + this->api = api; + + adl::ByteArray b; + b.append(msg, msgLen); + this->msg = b.toString(); +} + + +void Protocol1Connection::LastError::clear() { + code = 0; + msg.clear(); + api.clear(); +} + + +Status Protocol1Connection::sendResponse( + const adl::uint32_t code, + const adl::size_t msgLen, + const void* msg, + const adl::size_t paimonLen, + const void* paimon +) { + adl::ByteArray data; + data.resize(8 + msgLen + paimonLen); + (* (uint32_t*) &data[0]) = adl::htonl(code); + (* (uint32_t*) &data[4]) = adl::htonl(uint32_t(msgLen)); + adl::memcpy(data.data() + 8, msg, msgLen); + adl::memcpy(data.data() + 8 + msgLen, paimon, paimonLen); + return sendMsg(MsgType::Response, data); +} + + +Status Protocol1Connection::sendResponse(const uint32_t code, const adl::ByteArray& msg) { + adl::ByteArray data; + data.resize(8 + msg.size()); + + (* (uint32_t*) &data[0]) = adl::htonl(code); + (* (uint32_t*) &data[4]) = adl::htonl(uint32_t(msg.size())); + adl::memcpy(data.data() + 8, msg.data(), msg.size()); + + Status status = sendMsg(MsgType::Response, data); + return status; +} + + +Status Protocol1Connection::sendResponse(const adl::uint32_t code, const adl::TString& msg) { + return sendResponse(code, adl::ByteArray {msg.c_str()}); +} + + +Status Protocol1Connection::sendResponse(const adl::uint32_t code, const char* msg) { + return sendResponse(code, adl::ByteArray {msg}); +} + + + +Status Protocol1Connection::recvResponse(Response** response) { + Msg* rawMsg = nullptr; + Status libResult = recvMsg(&rawMsg, MsgType::Response); + + if (libResult != Status::SUCCESS) { + return libResult; + } + + + *response = (Response*) rawMsg; + + + (*response)->code = adl::ntohl((*response)->code); + (*response)->msgLen = adl::ntohl((*response)->msgLen); + + return Status::SUCCESS; +} + + +// ------ 0x1001 : Auth ------ + +Status Protocol1Connection::auth(const adl::ByteArray& key) { + adl::ByteArray challenge; + Status status = recvAuth(challenge); + if (status != Status::SUCCESS) + return status; + + crypto::rc4Inplace(challenge, key); + if ((status = sendResponse(0, challenge)) != Status::SUCCESS) { + return status; + } + + protocol::Response* response = nullptr; + status = recvResponse(&response); + if (status != Status::SUCCESS) + return status; + + if (response->code != 0) + status = Status::PROTOCOL_ERROR; + + this->freeMsg(response); + return status; +} + + +Status Protocol1Connection::auth( + const adl::HashMap& appsKeyring, + const adl::ArrayList& memoryNodesKeyring +) { + return auth(&appsKeyring, &memoryNodesKeyring); +} + + +Status Protocol1Connection::auth( + const adl::HashMap* appsKeyring, + const adl::ArrayList* memoryNodesKeyring +) { + adl::ByteArray challenge { "fyt's score is A+" }; // TODO + + Status status; + if ( (status = sendAuth(challenge)) != Status::SUCCESS ) + return status; + + Response* response = nullptr; + if ((status = recvResponse(&response)) != Status::SUCCESS) + return status; + + adl::ByteArray cipher { response->msg, response->msgLen }; + + bool verified = false; + + // check if app + + if (appsKeyring) { + for (const auto& it : *appsKeyring) { + verified = crypto::rc4Verify(it.second, challenge, cipher); + if (verified) { + appId = it.first; + nodeType = NodeType::App; + break; + } + } + } + + // check if memory node + + if (!verified && memoryNodesKeyring) { + if (crypto::rc4Verify(*memoryNodesKeyring, cipher, challenge) != -1) { + nodeType = NodeType::MemoryNode; + verified = true; + } + else { + nodeType = NodeType::Unknown; + } + } + + sendResponse(!!(nodeType == NodeType::Unknown), nullptr); + + this->freeMsg(response); + response = nullptr; + return Status::SUCCESS; +} + + + +Status Protocol1Connection::sendAuth(const adl::ByteArray& challenge) { + return sendMsg(MsgType::Auth, challenge); +} + + +Status Protocol1Connection::recvAuth(adl::ByteArray& challenge) { + protocol::Msg* msg = nullptr; + Status status = recvMsg(&msg, MsgType::Auth); + if (status != Status::SUCCESS) + return status; + + if (!challenge.resize(msg->header.length)) { + adl::defaultAllocator.free(msg); + return Status::OUT_OF_RESOURCE; + } + + adl::memcpy(challenge.data(), msg->data, challenge.size()); + this->freeMsg(msg); + return Status::SUCCESS; +} + +// ------ 0x1100 : Get Identity Keys ------ + +struct GetIdentityKeysKeyHeaderPacked { + Protocol1Connection::ReplyGetIdentityKeysParams::NodeType nodeType; + Protocol1Connection::ReplyGetIdentityKeysParams::KeyType keyType; + adl::int8_t reserved0[2] = {0}; + + adl::int64_t offset; + adl::int32_t len; + + adl::int64_t id; + adl::int8_t reserved1[16] = {0}; +} __packed; + + +Status Protocol1Connection::ReplyGetIdentityKeysParams::addKey( + NodeType nodeType, + KeyType keyType, + const adl::ByteArray& key, + adl::int64_t id +) { + + // check params + + if (nodeType != NodeType::App && nodeType != NodeType::MemoryNode) + return Status::INVALID_PARAMETERS; + + if (keyType != KeyType::RC4) // only RC4 allowed. + return Status::INVALID_PARAMETERS; + + if (key.size() > INT32_MAX) + return Status::INVALID_PARAMETERS; + + + // allocate header + + const adl::size_t headerSize = sizeof(GetIdentityKeysKeyHeaderPacked); + + if ( ! keyHeaders.resize(keyHeaders.size() + headerSize) ) { + Genode::error("Vesper Protocol ReplyGetIdentityKeys: Failed to add key. Out of resource."); + Genode::error("> Resize failed. Target size: ", keyHeaders.size() + headerSize); + return Status::OUT_OF_RESOURCE; + } + + auto head = (GetIdentityKeysKeyHeaderPacked*) (keyHeaders.data() + keyHeaders.size() - headerSize); + + adl::memset(head, 0, headerSize); + + head->nodeType = nodeType; + head->keyType = keyType; + head->offset = (adl::int64_t) keys.size(); + head->len = (adl::int32_t) key.size(); + head->id = id; + + if (keys.append(key.data(), key.size()) != 0 /* Indicates error. */) { + keyHeaders.resize(keyHeaders.size() - headerSize); + Genode::error("Vesper Protocol ReplyGetIdentityKeys: Failed to add key. Out of resource."); + return Status::OUT_OF_RESOURCE; + } + +#if VESPER_PROTOCOL_DEBUG + Genode::log("Key added to reply get identity keys params. ", key.toString().c_str()); + Genode::log("> keyHeaders.size() = ", keyHeaders.size()); +#endif + return Status::SUCCESS; +} + + +Status Protocol1Connection::sendGetIdentityKeys() { + return sendMsg(MsgType::GetIdentityKeys); +} + + +Status Protocol1Connection::replyGetIdentityKeys(const ReplyGetIdentityKeysParams& params) { + + adl::ByteArray msg; + + if (!msg.resize(8 + params.keyHeaders.size() + params.keys.size())) + return Status::OUT_OF_RESOURCE; + +#if VESPER_PROTOCOL_DEBUG + Genode::log("replyGetIdentityKeys"); + Genode::log("> params.keyHeaders.size() = ", params.keyHeaders.size()); + Genode::log("> params.keys.size() = ", params.keys.size()); +#endif + + adl::uint64_t nkeys = params.keyHeaders.size() / sizeof(GetIdentityKeysKeyHeaderPacked); + * (adl::uint64_t *) msg.data() = adl::htonq(nkeys); + + adl::memcpy(msg.data() + 8, params.keyHeaders.data(), params.keyHeaders.size()); + adl::memcpy(msg.data() + 8 + params.keyHeaders.size(), params.keys.data(), params.keys.size()); + + auto pHeader = (GetIdentityKeysKeyHeaderPacked*) (msg.data() + 8); + + while ((void*) pHeader < msg.data() + 8 + params.keyHeaders.size()) { + pHeader->len = adl::htonl(pHeader->len); + + pHeader->offset = adl::htonq( + pHeader->offset + adl::int64_t(sizeof(nkeys)) + adl::int64_t(params.keyHeaders.size()) + ); + + pHeader->id = adl::htonq(pHeader->id); + + pHeader++; + } + + return sendResponse(0, msg); +} + + +Status Protocol1Connection::appreciateGetIdentityKeys( + protocol::Response& response, + void* data, + void (*record) ( + ReplyGetIdentityKeysParams::NodeType nodeType, + ReplyGetIdentityKeysParams::KeyType keyType, + const adl::ByteArray& key, + adl::int64_t id, + void* data + ) +) { + if (response.code != 0) { + Genode::error("Vesper Protocol [appreciate GetIdentityKeys]: response.code is not 0."); + return Status::PROTOCOL_ERROR; + } + + auto pMsg = (const char*) response.msg; + auto nkeys = adl::ntohq (* (adl::uint64_t *) pMsg); + + adl::size_t msgLen = response.header.length - 8; // Response body minus error-code and msg-len. + if (msgLen < 8 /* nkeys tooks 8 bytes */ + nkeys * sizeof(GetIdentityKeysKeyHeaderPacked)) { + Genode::error("Vesper Protocol [appreciate GetIdentityKeys]: msg too short."); + return Status::PROTOCOL_ERROR; + } + + + auto pHeader = (GetIdentityKeysKeyHeaderPacked*) (pMsg + 8); + + for (adl::size_t i = 0; i < nkeys; i++) { + auto& header = pHeader[i]; + + const auto RC4Type = ReplyGetIdentityKeysParams::KeyType::RC4; + const auto AppType = ReplyGetIdentityKeysParams::NodeType::App; + const auto MemoryNodeType = ReplyGetIdentityKeysParams::NodeType::MemoryNode; + + if (header.keyType != RC4Type || (header.nodeType != MemoryNodeType && header.nodeType != AppType)) { + Genode::error("Vesper Protocol [appreciate GetIdentityKeys]: Bad Type."); + Genode::error( + "> key type: ", + adl::uint32_t(header.keyType), + ", node type: ", + adl::uint32_t(header.nodeType) + ); + return Status::PROTOCOL_ERROR; + } + + auto offset = adl::ntohq(header.offset); + auto len = adl::ntohl(header.len); + auto id = adl::ntohq(header.id); + if (offset + len > adl::int64_t(msgLen)) { + Genode::error("Vesper Protocol [appreciate GetIdentityKeys]: Out of bound."); + Genode::error("> ", offset, " + ", len, " IS NOT LARGER THAN ", msgLen); + return Status::PROTOCOL_ERROR; + } + + adl::ByteArray key; + if (key.append(pMsg + offset, (adl::size_t) len) != 0) { + Genode::error("Vesper Protocol [appreciate GetIdentityKeys]: Out of resource."); + return Status::OUT_OF_RESOURCE; + } + + record(header.nodeType, header.keyType, key, id, data); + } + + return Status::SUCCESS; +} + + +// ------ 0x2000 : Memory Node Show ID ------ + + +Status Protocol1Connection::sendMemoryNodeShowId(adl::int64_t id) { + adl::int64_t netOrderId = adl::htonq(id); + return sendMsg( + protocol::MsgType::MemoryNodeShowId, + &netOrderId, + 8 + ); +} + + +Status Protocol1Connection::decodeMemoryNodeShowId(protocol::Msg* msg, adl::int64_t* id) { + + if (msg->header.length < sizeof(adl::int64_t)) + return Status::PROTOCOL_ERROR; + + *id = adl::ntohq( *(adl::int64_t*) msg->data); + return Status::SUCCESS; +} + + +// ------ 0x2001 : Memory Node Clock In ------ + + +Status Protocol1Connection::memoryNodeClockIn(adl::int64_t* id, IP4Addr ip, adl::uint16_t port) { + Status status = sendMemoryNodeClockIn(ip.ui32, port); + if (status != Status::SUCCESS) + return Status::PROTOCOL_ERROR; + + RECV_AND_HANDLE_RESPONSE( + auto& res = response; + if (res->header.length < 16) { + Genode::error("Bad response. Bad length: ", adl::uint64_t(res->header.length)); + status = Status::PROTOCOL_ERROR; + } + else { + *id = adl::ntohq(* (adl::int64_t *) res->msg); + } + ); + + return status; +} + + +Status Protocol1Connection::sendMemoryNodeClockIn(adl::uint32_t tcp4Ip, adl::uint16_t port) { + adl::uint8_t data[12]; + *(adl::int32_t*) data = adl::htonl(4); // TCP Protocol Version 4. + *(adl::uint32_t*) (data + 4) = adl::htonl((adl::uint32_t) port); + *(adl::uint32_t*) (data + 8) = tcp4Ip; + return sendMsg(MsgType::MemoryNodeClockIn, data, sizeof(data)); +} + + +Status Protocol1Connection::decodeMemoryNodeClockIn( + protocol::Msg* msg, + adl::int32_t* tcpVer, + adl::uint16_t* port, + adl::uint8_t ip[] +) { + if (msg->header.length < 12) { + return Status::PROTOCOL_ERROR; + } + + *tcpVer = adl::ntohl( *(adl::int32_t*) (msg->data + 0) ); + *port = (adl::uint16_t) adl::ntohl( *(adl::uint32_t*) (msg->data + 4) ); + + if (*tcpVer != 4) + return Status::PROTOCOL_ERROR; + + adl::memcpy(ip, msg->data + 8, (*tcpVer == 4 ? 4 : 16)); + return Status::SUCCESS; +} + + + +// ------ 0x2004 : Locate Memory Nodes ------ + +Status Protocol1Connection::sendLocateMemoryNodes() { + return sendMsg(MsgType::LocateMemoryNodes); +} + + +Status Protocol1Connection::locateMemoryNodes(adl::ArrayList& out) { + Status status = sendLocateMemoryNodes(); + if (status != Status::SUCCESS) + return status; + + RECV_AND_HANDLE_RESPONSE( + out.clear(); + auto& r = response; + + auto ENTRY_SIZE = sizeof(LocateMemoryNodeNodeInfoEntry); + for (adl::size_t off = 0; off < r->header.length - 8; off += ENTRY_SIZE) { + auto& entryPacked = *(LocateMemoryNodeNodeInfoEntry*) (r->msg + off); + + auto tcpVer = adl::ntohl(entryPacked.tcpVersion); + if (tcpVer != 4) { + Genode::error( + "VesperProtocol [locate memory nodes]: Entry on offset ", + off, + " 's tcp version is ", + (tcpVer + 0), // member of packed struct cannot in Genode::log directly. + ". Ignored." + ); + + continue; + } + + out.append({}); + out.back().id = adl::ntohq(entryPacked.id); + out.back().port = (adl::uint16_t) adl::ntohl(entryPacked.port); + out.back().tcpVersion = tcpVer; + out.back().ip = entryPacked.inet4addr; + } + ); + + return status; +} + + +// ------ 0x3001 : Try Alloc ------ + +Status Protocol1Connection::sendTryAlloc(adl::size_t blockSize, adl::size_t nBlocks) { + adl::ByteArray data; + if (!data.resize(16)) { + return Status::OUT_OF_RESOURCE; + } + + auto a = (adl::uint64_t*) data.data(); + a[0] = adl::htonq(adl::uint64_t(blockSize)); + a[1] = adl::htonq(adl::uint64_t(nBlocks)); + return sendMsg(MsgType::TryAlloc, data); +} + + +Status Protocol1Connection::decodeTryAlloc(Msg* msg, adl::size_t* blockSize, adl::size_t* nBlocks) { + // Generated by Google Gemini 2.0 Flash. Checked by GTY. + + if (msg->header.length < 2 * sizeof(adl::uint64_t)) { + return Status::PROTOCOL_ERROR; + } + + *blockSize = adl::ntohq(*reinterpret_cast(msg->data)); + *nBlocks = adl::ntohq(*reinterpret_cast(msg->data + 8)); + + return Status::SUCCESS; +} + + +Status Protocol1Connection::replyTryAlloc(adl::ArrayList blockIds) { + adl::ArrayList data = blockIds; + + for (auto& it : data) { + it = adl::htonq(it); + } + + return sendResponse(0, data.size() * sizeof(data[0]), data.data()); +} + + +Status Protocol1Connection::tryAlloc( + adl::size_t blockSize, + adl::size_t nBlocks, + adl::ArrayList& idOut +) { + Status status = sendTryAlloc(blockSize, nBlocks); + if (status != Status::SUCCESS) + return status; + + + RECV_AND_HANDLE_RESPONSE( + auto& r = response; + idOut.clear(); + for (auto p = (adl::int64_t*) r->msg; (const char*) p < r->msg + r->header.length - 8; p++) { + idOut.append(adl::ntohq(*p)); + } + ); + + return status; +} + + + +// ------ 0x3002 : Read Block ------ + +Status Protocol1Connection::sendReadBlock(adl::int64_t blockId) { + adl::ByteArray data; + if (!data.resize(sizeof(blockId))) + return Status::OUT_OF_RESOURCE; + + *(adl::int64_t*) data.data() = adl::htonq(blockId); + return sendMsg(MsgType::ReadBlock, data); +} + + +Status Protocol1Connection::decodeReadBlock(protocol::Msg* msg, adl::int64_t* blockId) { + // Generated by Google Gemini 2.0 Flash. Checked by GTY. + + if (msg->header.length < sizeof(adl::int64_t)) { + return Status::PROTOCOL_ERROR; + } + *blockId = adl::ntohq(*reinterpret_cast(msg->data)); + return Status::SUCCESS; +} + + +Status Protocol1Connection::replyReadBlock(const void* data, adl::size_t size) { + return sendResponse(0, size, data); +} + + + +Status Protocol1Connection::readBlock(adl::int64_t blockId, void* buf, adl::size_t bufSize) { + Status status = sendReadBlock(blockId); + + if (status != Status::SUCCESS) + return status; + + RECV_AND_HANDLE_RESPONSE( + auto& r = response; + auto blockSize = r->header.length - 8; + if (blockSize != bufSize) { + Genode::error( + "Vesper Protocol [read block]: bufSize ", + bufSize, + " not match block size ", + blockSize + ); + status = Status::PROTOCOL_ERROR; + } + else { + adl::memcpy(buf, r->msg, bufSize); + } + ); + + return status; +} + + +// ------ 0x3003 : Write Block ------ + +Status Protocol1Connection::sendWriteBlock(adl::int64_t blockId, const void* data, adl::size_t size) { + adl::ByteArray msg; + + if (!msg.resize(size + 8)) { + return Status::OUT_OF_RESOURCE; + } + + *((adl::int64_t*) msg.data()) = adl::htonq(blockId); + adl::memcpy(msg.data() + 8, data, size); + + return sendMsg(MsgType::WriteBlock, msg); +} + + +Status Protocol1Connection::decodeWriteBlock(protocol::Msg* msg, adl::int64_t* id, adl::ByteArray& data) { + auto len = msg->header.length; + if (len < 8) { // Block ID + return Status::PROTOCOL_ERROR; + } + + *id = *(adl::int64_t*) msg->data; + *id = adl::ntohq(*id); + + auto blockSize = len - 8; + if (!data.resize(blockSize)) { + return Status::OUT_OF_RESOURCE; + } + + adl::memcpy(data.data(), msg->data + 8, blockSize); + + return Status::SUCCESS; +} + + + +Status Protocol1Connection::writeBlock(adl::int64_t blockId, const void* data, adl::size_t size) { + Status status = Status::SUCCESS; + + if ((status = sendWriteBlock(blockId, data, size)) != Status::SUCCESS) { + return status; + } + + RECV_AND_HANDLE_RESPONSE({}); + + return status; +} + + + + +// ------ 0x3004 : Check Avail Mem ------ + +Status Protocol1Connection::sendCheckAvailMem() { + return sendMsg(MsgType::CheckAvailMem); +} + + +Status Protocol1Connection::replyCheckAvailMem(adl::size_t availMem) { + adl::uint64_t availMemNetOrder = adl::htonq((adl::uint64_t) availMem); + return sendResponse(0, sizeof(availMem), &availMemNetOrder); +} + + +Status Protocol1Connection::checkAvailMem(adl::size_t* availMem) { + Status status = sendCheckAvailMem(); + if (status != Status::SUCCESS) { + return status; + } + + RECV_AND_HANDLE_RESPONSE( + *availMem = (adl::size_t) adl::htonq(*(adl::uint64_t*) response->msg); + ); + + return status; +} + + + + +// ------ 0x3005 : Free Block ------ + +Status Protocol1Connection::sendFreeBlock(adl::int64_t blockId) { + auto bId_netOrder = adl::htonq(blockId); + return sendMsg(MsgType::FreeBlock, &bId_netOrder, sizeof(bId_netOrder)); +} + + +Status Protocol1Connection::decodeFreeBlock(Msg* msg, adl::int64_t* blockId) { + // Generated by Google Gemini 2.0 Flash. Checked by GTY. + + if (msg->header.length < sizeof(adl::int64_t)) { + return Status::PROTOCOL_ERROR; + } + + *blockId = adl::ntohq(*reinterpret_cast(msg->data)); + + return Status::SUCCESS; +} + + +Status Protocol1Connection::freeBlock(adl::int64_t blockId) { + // Generated by Google Gemini 2.0 Flash. Checked by GTY. + + Status status = sendFreeBlock(blockId); + if (status != Status::SUCCESS) { + return status; + } + + RECV_AND_HANDLE_RESPONSE({}); + + return status; +} + + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/Protocol1Connection.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/Protocol1Connection.h new file mode 100644 index 0000000000000000000000000000000000000000..7318a0f9878fe05ee75ab74d5e9fadb78d6c171c --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/Protocol1Connection.h @@ -0,0 +1,275 @@ +/* + * Protocol V1 Connection + * + * Created on 2025.1.21 at Xiangzhou, Zhuhai, Guangdong + * + * + * gongty [at] tongji [dot] edu [dot] cn + * + */ + +#pragma once + +#include +#include +#include + +namespace monkey::net { + + +/** + * @deprecated Use latest protocol version is suggested. + */ +class Protocol1Connection : public ProtocolConnection { +public: + struct MemoryNodeInfo { + adl::int64_t id; + adl::int32_t tcpVersion; + adl::uint16_t port; + + IP4Addr ip; + }; + + enum class NodeType { + MemoryNode, + App, + Unknown, + Other + } nodeType = NodeType::Other; + + + /** + * Other side's id. + */ + union { + adl::int64_t memoryNodeId = -1; + adl::int64_t appId; + adl::int64_t nodeId; + }; + + + static const adl::int64_t VERSION = 1; + virtual adl::int64_t version() override { return VERSION; } + + + /** + * errno: only remembers last failed response received in one-click methods. + */ + struct LastError { + adl::uint32_t code = 0; + adl::TString msg; + adl::TString api; + + void set(protocol::Response* response, const char* api = nullptr); + void set(adl::uint32_t code, const void* msg, adl::size_t msgLen, const char* api = nullptr); + void clear(); + } lastError; + + + // Connection operations + + + Status sendResponse( + const adl::uint32_t code, + const adl::size_t msgLen, + const void* msg, + const adl::size_t paimonLen = 0, + const void* paimon = nullptr + ); + + Status sendResponse(const adl::uint32_t code, const adl::ByteArray& msg); + Status sendResponse(const adl::uint32_t code, const adl::TString& msg); + Status sendResponse(const adl::uint32_t code, const char* msg = nullptr); + + + + /** + * The caller is responsible for freeing `response` + * when monkey::Status is SUCCESS. + */ + Status recvResponse(protocol::Response** response); + + + + // ------ 0x1001 : Auth ------ + + Status sendAuth(const adl::ByteArray& challenge); + Status recvAuth(adl::ByteArray& challenge); + + /** + * Client-mode auth: Show identity to server. + */ + Status auth(const adl::ByteArray& key); + + /** + * Server-mode auth: Check client's identity. + * + * @return monkey::Status::SUCCESS Only means no network error occurred. + * You should also check whether `this->nodeType` is correct. + */ + Status auth( + // app id -> app key + const adl::HashMap& appsKeyring, + const adl::ArrayList& memoryNodesKeyring + ); + + + /** + * Server-mode auth: Check client's identity. + * This method works just like the one above except: You can set keyring to nullptr + * to ignore some type(s). + * + * @return monkey::Status::SUCCESS Only means no network error occurred. + * You should also check whether `this->nodeType` is correct. + */ + Status auth( + // app id -> app key + const adl::HashMap* appsKeyring, + const adl::ArrayList* memoryNodesKeyring + ); + + + // ------ 0x1100 : Get Identity Keys ------ + + struct ReplyGetIdentityKeysParams { + adl::ByteArray keyHeaders; + adl::ByteArray keys; + + enum class NodeType : adl::int8_t { + App = 0, + MemoryNode = 1 + }; + + enum class KeyType : adl::int8_t { + RC4 = 0 + }; + + monkey::Status addKey( + NodeType nodeType, + KeyType keyType, + const adl::ByteArray& key, + adl::int64_t id = 0 // ignored for Memory Node + ); + }; + + + Status sendGetIdentityKeys(); + Status replyGetIdentityKeys(const ReplyGetIdentityKeysParams&); + Status appreciateGetIdentityKeys( + protocol::Response& response, + void* data, + + /** + * Called for each key. + * + * @param data Same as `data` passed to `appreciate`. + */ + void (*record) ( + ReplyGetIdentityKeysParams::NodeType nodeType, + ReplyGetIdentityKeysParams::KeyType keyType, + const adl::ByteArray& key, + adl::int64_t id, + void* data + ) + ); + + + // ------ 0x2000 : Memory Node Show ID ------ + + Status sendMemoryNodeShowId(adl::int64_t id); + Status decodeMemoryNodeShowId(protocol::Msg* msg, adl::int64_t* id); + + + // ------ 0x2001 : Memory Node Clock In ------ + + + Status memoryNodeClockIn(adl::int64_t* id, IP4Addr ip, adl::uint16_t port); + + + /** + * For TCP4. + */ + Status sendMemoryNodeClockIn(adl::uint32_t tcp4Ip, adl::uint16_t port); + + /** + * + * + * @param ip Should be at least 16 bytes (array length at least 16). + * For TCP4, ip[0] to ip[3] is filled. + * For TCP6, ip[0] to ip[15] is filled. + * + */ + Status decodeMemoryNodeClockIn( + protocol::Msg* msg, + adl::int32_t* tcpVer, + adl::uint16_t* port, + adl::uint8_t ip[] + ); + + + // ------ 0x2004 : Locate Memory Nodes ------ + + struct LocateMemoryNodeNodeInfoEntry { + adl::int64_t id; + adl::int32_t tcpVersion; // must be 4 (in net order) + adl::uint32_t port; + + adl::uint32_t inet4addr; + adl::int8_t padding[12]; + } __packed; + + Status sendLocateMemoryNodes(); + Status locateMemoryNodes(adl::ArrayList&); + + + // ------ 0x3001 : Try Alloc ------ + + Status sendTryAlloc(adl::size_t blockSize, adl::size_t nBlocks); + Status decodeTryAlloc(protocol::Msg* msg, adl::size_t* blockSize, adl::size_t* nBlocks); + Status replyTryAlloc(adl::ArrayList blockIds); + Status tryAlloc(adl::size_t blockSize, adl::size_t nBlocks, adl::ArrayList& idOut); + + // ------ 0x3002 : Read Block ------ + + Status sendReadBlock(adl::int64_t blockId); + Status decodeReadBlock(protocol::Msg* msg, adl::int64_t* blockId); + Status replyReadBlock(const void* data, adl::size_t size); + + /** + * Read data from remote, and fill it into `bufSize`. + * + * @param bufSize If 0, api would not check buf's boundary. + * If not 0 and response's size not match, + * error reported with no change to buf. + */ + Status readBlock(adl::int64_t blockId, void* buf, adl::size_t bufSize = 0); + + + // ------ 0x3003 : Write Block ------ + + Status sendWriteBlock(adl::int64_t blockId, const void* data, adl::size_t size); + Status decodeWriteBlock(protocol::Msg* msg, adl::int64_t* id, adl::ByteArray& data); + + Status writeBlock(adl::int64_t blockId, const void* data, adl::size_t size); + + // ------ 0x3004 : Check Avail Mem ------ + + Status sendCheckAvailMem(); + Status replyCheckAvailMem(adl::size_t availMem); + Status checkAvailMem(adl::size_t* availMem); + + + // ------ 0x3005 : Free Block ------ + + Status sendFreeBlock(adl::int64_t blockId); + Status decodeFreeBlock(protocol::Msg*, adl::int64_t* blockId); + Status freeBlock(adl::int64_t blockId); + +}; + + + +MONKEY_NET_IMPL_DOCK_PROTOCOL(Protocol1Connection); + + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/Protocol2Connection.cc b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/Protocol2Connection.cc new file mode 100644 index 0000000000000000000000000000000000000000..cf3480b0e08a680d24d44636739301151e0b4cc1 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/Protocol2Connection.cc @@ -0,0 +1,251 @@ +/* + Monkey Protocol Connection Version 2 + + + Created on 2025.6.7 at Wujing, Minhang + gongty [at] alumni [dot] tongji [dot] edu [dot] cn +*/ + + +#include + +using namespace monkey; +using namespace monkey::net; +using monkey::net::protocol::Response; + + +#define RECV_AND_HANDLE_RESPONSE MONKEY_PROTOCOL_RECV_AND_HANDLE_RESPONSE + +// ------ 0x3001 : Try Alloc ------ + +Status Protocol2Connection::sendTryAlloc() { + return sendMsg(protocol::MsgType::TryAlloc); +} + + +Status Protocol2Connection::replyTryAlloc(adl::int64_t blockId, adl::int64_t dataVer, adl::int64_t readKey, adl::int64_t writeKey) { + + struct { + adl::int64_t blockId; + adl::int64_t dataVer; + adl::int64_t rdkey; + adl::int64_t wrkey; + } __packed payload; + + payload.blockId = adl::htonq(blockId); + payload.dataVer = adl::htonq(dataVer); + payload.rdkey = readKey; + payload.wrkey = writeKey; + + return sendResponse(0, sizeof(payload), &payload); +} + + +Status Protocol2Connection::tryAlloc(adl::int64_t* blockId, adl::int64_t* dataVer, adl::int64_t* readKey, adl::int64_t* writeKey) { + + Status status = sendTryAlloc(); + if (status != Status::SUCCESS) + return status; + + + RECV_AND_HANDLE_RESPONSE( + auto& r = response; + + struct Payload { + adl::int64_t blockId; + adl::int64_t dataVer; + adl::int64_t rdkey; + adl::int64_t wrkey; + } __packed; + + if (r->header.length < sizeof(Payload)) { + Genode::error("TryAlloc: Failed to decode response."); + status = Status::PROTOCOL_ERROR; + break; + } + + Payload* pPayload = (Payload*) r->msg; + Payload& payload = *pPayload; + if (blockId) + *blockId = adl::ntohq(payload.blockId); + if (dataVer) + *dataVer = adl::ntohq(payload.dataVer); + if (readKey) + *readKey = payload.rdkey; + if (writeKey) + *writeKey = payload.wrkey; + ); + + return status; +} + + + +// ------ 0x3002 : Read Block ------ + + + + +Status Protocol2Connection::replyReadBlock(adl::int64_t dataVer, const void* data) { + auto header = makeHeader((adl::uint32_t) protocol::MsgType::Response, 4096 + 8 + sizeof(dataVer)); + + adl::int64_t dataVerNetOrder = adl::htonq(dataVer); + auto acc = send(&header, sizeof(net::protocol::Header)); + + adl::int32_t zero = 0; + + // code and msgLen all set to 0. + acc += send(&zero, sizeof(zero)); + acc += send(&zero, sizeof(zero)); + + acc += send(&dataVerNetOrder, sizeof(dataVerNetOrder)); + acc += send(data, 4096); + + return (acc == sizeof(header) + 4096 + 8 + sizeof(dataVer)) ? Status::SUCCESS : Status::NETWORK_ERROR; +} + + +Status Protocol2Connection::readBlock(adl::int64_t blockId, void* buf, adl::int64_t* dataVer) { + Status status = sendReadBlock(blockId); + + if (status != Status::SUCCESS) + return status; + + RECV_AND_HANDLE_RESPONSE( + auto& r = response; + auto blockSize = r->header.length - 8 - sizeof(*dataVer); + if (blockSize != 4096) { + Genode::error( + "Vesper Protocol [read block]: bufSize ", + 4096, + " not match block size ", + blockSize + ); + status = Status::PROTOCOL_ERROR; + } + else { + if (dataVer) + *dataVer = adl::htonq(* (adl::int64_t*) r->msg); + adl::memcpy(buf, r->msg + sizeof(dataVer), blockSize /* 4096 */); + } + ); + + return status; +} + + + +// ------ 0x3003 : Write Block ------ + + + +Status Protocol2Connection::writeBlock(adl::int64_t blockId, const void* data, adl::int64_t* dataVer) { + Status status = sendWriteBlock(blockId, data); + + if (status != Status::SUCCESS) { + return status; + } + + RECV_AND_HANDLE_RESPONSE( + auto& r = response; + if (r->msgLen != 8) { + Genode::error("Protocol Error: ", __FUNCTION__); + status = Status::PROTOCOL_ERROR; + break; + } + + if (dataVer) { + *dataVer = adl::ntohq(* (adl::int64_t*) r->msg); + } + ); + + return status; +} + + + +// ------ 0x3006 : Ref Block ------ + +Status Protocol2Connection::decodeRefBlock(protocol::Msg* msg, adl::int64_t* accessKey) { + + if (msg->header.length < sizeof(adl::int64_t)) { + return Status::PROTOCOL_ERROR; + } + + *accessKey = *(adl::int64_t*) msg->data; + return Status::SUCCESS; +} + + +Status Protocol2Connection::refBlock(adl::int64_t accessKey, adl::int64_t* blockId) { + Status status = sendMsg(protocol::MsgType::RefBlock, &accessKey, sizeof(accessKey)); + + + RECV_AND_HANDLE_RESPONSE( + auto& r = response; + // assert response is valid. so skip msglen check. + + if (blockId) + *blockId = adl::ntohq(* (adl::int64_t *) r->msg); + ); + + return status; +} + + + +// ------ 0x3007 : Unref Block ------ + + +Status Protocol2Connection::decodeUnrefBlock(protocol::Msg* msg, adl::int64_t* blockId) { + if (msg->header.length < sizeof(adl::int64_t)) { + return Status::PROTOCOL_ERROR; + } + + *blockId = adl::ntohq(*(adl::int64_t*) msg->data); + return Status::SUCCESS; +} + + +Status Protocol2Connection::unrefBlock(adl::int64_t blockId) { + adl::int64_t blockIdNetOrder = adl::htonq(blockId); + Status status = sendMsg(protocol::MsgType::UnrefBlock, &blockIdNetOrder, sizeof(blockIdNetOrder)); + if (status != Status::SUCCESS) { + return status; + } + + RECV_AND_HANDLE_RESPONSE({}); + + return status; +} + + + +// ------ 0x3008 : Get Block Data Version ------ + +Status Protocol2Connection::decodeGetBlockDataVersion(protocol::Msg* msg, adl::int64_t* id) { + + if (msg->header.length < sizeof(adl::int64_t)) { + return Status::PROTOCOL_ERROR; + } + + *id = adl::ntohq(*reinterpret_cast(msg->data)); + return Status::SUCCESS; +} + + +Status Protocol2Connection::getBlockDataVersion(adl::int64_t blockId, adl::int64_t* dataVer) { + adl::int64_t blockIdNetOrder = adl::htonq(blockId); + Status status = sendMsg(protocol::MsgType::GetBlockDataVersion, &blockIdNetOrder, sizeof(blockIdNetOrder)); + if (status != Status::SUCCESS) { + return status; + } + + RECV_AND_HANDLE_RESPONSE( + if (dataVer) + *dataVer = adl::ntohq(*(adl::int64_t*) response->msg); + ); + + return status; +} + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/Protocol2Connection.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/Protocol2Connection.h new file mode 100644 index 0000000000000000000000000000000000000000..20bf6c22272d3129197ae557c3a44401699b21ef --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/Protocol2Connection.h @@ -0,0 +1,89 @@ +/* + Monkey Protocol Connection Version 2 + + + Created on 2025.6.7 at Wujing, Minhang + gongty [at] alumni [dot] tongji [dot] edu [dot] cn +*/ + +#pragma once + +#include +#include + + +namespace monkey::net { + +class Protocol2Connection : public Protocol1Connection { +public: + + static const adl::int64_t VERSION = 2; + virtual adl::int64_t version() override { return VERSION; } + + + // ------ 0x3001 : Try Alloc ------ + + Status sendTryAlloc(); + Status decodeTryAlloc(protocol::Msg* msg, adl::size_t* blockSize, adl::size_t* nBlocks) = delete; + Status replyTryAlloc(adl::int64_t blockId, adl::int64_t dataVer, adl::int64_t readKey, adl::int64_t writeKey); + Status tryAlloc( + adl::int64_t* blockId, + adl::int64_t* dataVer = nullptr, + adl::int64_t* readKey = nullptr, + adl::int64_t* writeKey = nullptr); + + + // ------ 0x3002 : Read Block ------ + + inline Status sendReadBlock(adl::int64_t blockId) { + return Protocol1Connection::sendReadBlock(blockId); + } + inline Status decodeReadBlock(protocol::Msg* msg, adl::int64_t* blockId) { + return Protocol1Connection::decodeReadBlock(msg, blockId); + } + Status replyReadBlock(adl::int64_t dataVer, const void* data); + + /** + * Read data from remote, and fill it into `bufSize`. + * + * You should ensure `buf` size is enough (not less than 4KB). + */ + Status readBlock(adl::int64_t blockId, void* buf, adl::int64_t* dataVer = nullptr); + + + // ------ 0x3003 : Write Block ------ + + inline Status sendWriteBlock(adl::int64_t blockId, const void* data) { + return Protocol1Connection::sendWriteBlock(blockId, data, 4096); + } + inline Status decodeWriteBlock(protocol::Msg* msg, adl::int64_t* id, adl::ByteArray& data) { + return Protocol1Connection::decodeWriteBlock(msg, id, data); + } + + Status writeBlock(adl::int64_t blockId, const void* data, adl::int64_t* dataVer = nullptr); + + + // ------ 0x3006 : Ref Block ------ + + Status decodeRefBlock(protocol::Msg* msg, adl::int64_t* accessKey); + Status refBlock(adl::int64_t accessKey, adl::int64_t* blockId); + + // ------ 0x3007 : Unref Block ------ + + + Status decodeUnrefBlock(protocol::Msg* msg, adl::int64_t* blockId); + Status unrefBlock(adl::int64_t blockId); + + // ------ 0x3008 : Get Block Data Version ------ + + Status decodeGetBlockDataVersion(protocol::Msg* msg, adl::int64_t* id); + Status getBlockDataVersion(adl::int64_t blockId, adl::int64_t* dataVer); + +}; + + + +MONKEY_NET_IMPL_DOCK_PROTOCOL(Protocol2Connection); + +} // namespace monkey::net + diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/ProtocolConnection.cc b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/ProtocolConnection.cc new file mode 100644 index 0000000000000000000000000000000000000000..a775f8d1434e21fd1518516c9d63ed9a23f2f223 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/ProtocolConnection.cc @@ -0,0 +1,333 @@ +/* + * Protocol Connection Base + * + * Created on 2025.1.21 at Xiangzhou, Zhuhai, Guangdong + * + * + * gongty [at] tongji [dot] edu [dot] cn + * + */ + + +#include +#include + + +#if VESPER_PROTOCOL_DEBUG +#include +#endif + +namespace monkey::net { + +using protocol::MsgType; +using protocol::Msg; +using protocol::Header; + + + +Status ProtocolConnection::sendMsg(MsgType type, const void* data, adl::uint64_t dataLen) { + Header header = makeHeader(adl::uint32_t(type), dataLen); + + +#if VESPER_PROTOCOL_DEBUG + { + adl::TString hex; + adl::size_t pos = 0; + debug::hexView( + 1, + [&] () { + if (pos < sizeof(Header)) { + return (int) ((char*) &header)[pos++]; + } + else if (pos - sizeof(Header) < dataLen && /* TODO */ pos - sizeof(Header) < 0x40) { + return (int) ((char*) data)[pos++ - sizeof(Header)]; + } + else { + return EOF; + } + }, + [&] (int ch) { + hex += char(ch); + } + ); + + Genode::log("VESPER_PROTOCOL_DEBUG : --- Send Msg -->"); + auto typeMachineOrder = adl::ntohl(header.type); + Genode::log( + "> header.type : ", + Genode::Hex(typeMachineOrder), + " ", protocol::msgTypeToString(MsgType(typeMachineOrder)) + ); + Genode::log("> header.length: ", adl::size_t(adl::ntohq(header.length))); + Genode::log(hex.c_str()); + } +#endif + + const adl::uint32_t headerSize = sizeof(header); + adl::uint64_t msgSize = headerSize + dataLen; + + adl::int64_t dataWrote = send(&header, headerSize); + if (data && dataLen) + dataWrote += send(data, dataLen); + + // Returns whether all msg sent. + return dataWrote >= 0 && adl::uint64_t(dataWrote) == msgSize ? Status::SUCCESS : Status::NETWORK_ERROR; +} + + +Status ProtocolConnection::sendMsg(MsgType type, const adl::ByteArray& data) { + return sendMsg(type, data.data(), data.size()); +} + + + + +Status ProtocolConnection::recvHeader(Header* header) { + adl::uint64_t received = 0; + while(received < sizeof(Header)){ + adl::int64_t num_bytes = recv( ((char*) header) + received, sizeof(Header) - received); + if(num_bytes == -1) { + Genode::error("[Receive_header] Read failed."); + return Status::NETWORK_ERROR; + } + received += uint64_t(num_bytes); + } + + + // Check magic. + + if ( !magicMatch(header->magic) ) { + Genode::error("[Receive Header] Header mismatch!"); + return Status::PROTOCOL_ERROR; + } + + + // Data from network is in network's byte-order. Convert it to native one. + + header->type = adl::ntohl(header->type); + header->length = adl::ntohq(header->length); + + return Status::SUCCESS; +} + + +Status ProtocolConnection::recvMsg(Msg** msg, MsgType type) { + Status libResult; + + Header header; + if ((libResult = recvHeader(&header)) != Status::SUCCESS) { + return libResult; + } + + + adl::uint64_t msgSize = (adl::uint64_t) header.length + sizeof(Header); + auto protocolMsg = adl::defaultAllocator.alloc(1, true, msgSize); + + if (protocolMsg == nullptr) { + // out of resource. + return Status::OUT_OF_RESOURCE; + } + + adl::memcpy(protocolMsg, &header, sizeof(Header)); + + adl::uint64_t received = 0; + while(received < header.length){ + adl::int64_t num_bytes = recv(((char*) protocolMsg) + sizeof(Header) + received, header.length - received); + if(num_bytes == -1){ + Genode::error("[Recv_msg] Read failed."); + adl::defaultAllocator.free(protocolMsg); + return Status::NETWORK_ERROR; + } + received += adl::uint64_t(num_bytes); + } + + + + if (type != MsgType::None && protocolMsg->header.type != adl::uint32_t(type)) { + adl::defaultAllocator.free(protocolMsg); + return Status::PROTOCOL_ERROR; + } + + + *msg = protocolMsg; + + +#if VESPER_PROTOCOL_DEBUG + { + adl::TString hex; + adl::size_t pos = 0; + debug::hexView( + 1, + [&] () { + if (pos < sizeof(Header) + header.length && /* TODO */ pos - sizeof(Header) < 0x40) { + return (int) ((char*) protocolMsg)[pos++]; + } + else { + return EOF; + } + }, + [&] (int ch) { + hex += char(ch); + } + ); + + Genode::log("VESPER_PROTOCOL_DEBUG : <-- Recv Msg ---"); + Genode::log( + "> header.type : ", + Genode::Hex(header.type), " ", + protocol::msgTypeToString(MsgType(header.type)) + ); + Genode::log("> header.length: ", adl::size_t(header.length)); + Genode::log(hex.c_str()); + Genode::log("Note: Header field is adjusted to host's byte order."); + } +#endif + + + return Status::SUCCESS; +} + + +// ------ 0x1000 : Hello ------ + +static Status serverModeHello( + ProtocolConnection& client, + const adl::ArrayList& protocolVersions, + adl::int64_t* finalVersion +) { + Genode::log("Vesper Protocol [Hello]: Running server-mode Hello."); + adl::ArrayList clientVers; + Status status; + if ((status = client.recvHello(clientVers)) != Status::SUCCESS) { + Genode::error("Vesper Protocol [Hello]: Failed on phase 1."); + return status; + } + + adl::ArrayList versionsSupported; + + for (auto& it : clientVers) { + if (protocolVersions.contains(it)) { + versionsSupported.append(it); + } + } + + if (versionsSupported.isEmpty()) { + Genode::warning("Vesper Protocol [Hello]: Versions supported by client is empty."); + client.sendHello(adl::ArrayList{}); + return status = Status::PROTOCOL_ERROR; + } + + + if ((status = client.sendHello(versionsSupported)) != Status::SUCCESS) { + Genode::error("Vesper Protocol [Hello]: Failed on phase 2."); + return status; + } + + + if ((status = client.recvHello(clientVers)) != Status::SUCCESS) { + Genode::error("Vesper Protocol [Hello]: Failed on phase 3."); + return status; + } + + if (clientVers.size() == 1 && versionsSupported.contains(clientVers[0])) { + *finalVersion = clientVers[0]; + return Status::SUCCESS; + } + + Genode::error("Vesper Protocol [Hello]: No agreement reached with client."); + return Status::PROTOCOL_ERROR; +} + + +static Status clientModeHello( + ProtocolConnection& client, + const adl::ArrayList& protocolVersions, + adl::int64_t* finalVersion +) { + if (protocolVersions.isEmpty()) { + Genode::error("Vesper Protocol [Hello]: Protocol version list is empty."); + return Status::INVALID_PARAMETERS; + } + + Genode::log("Vesper Protocol [Hello]: Running client-mode Hello."); + + Status status; + if ((status = client.sendHello(protocolVersions)) != Status::SUCCESS) + return status; + + adl::ArrayList vers; + if ((status = client.recvHello(vers)) != Status::SUCCESS) { + Genode::error("Vesper Protocol [Hello]: Failed on phase 2."); + return status; + } + + adl::int64_t maxVer = -1; + for (auto& it : vers) { + if (it > maxVer && protocolVersions.contains(it)) { + maxVer = it; + } + } + + if (maxVer == -1) + return Status::PROTOCOL_ERROR; + + vers.clear(); + vers.append(maxVer); + if ((status = client.sendHello(vers)) != Status::SUCCESS) + return status; + + *finalVersion = maxVer; + return Status::SUCCESS; +} + + +Status ProtocolConnection::hello( + const adl::ArrayList& protocolVersions, + HelloMode mode, + adl::int64_t* finalVersion +) { + auto handler = (mode == HelloMode::SERVER ? serverModeHello : clientModeHello); + return handler(*this, protocolVersions, finalVersion); +} + + +Status ProtocolConnection::hello(adl::int64_t version, HelloMode mode) { + adl::ArrayList arr; + arr.append(version); + adl::int64_t finalVer; + return hello(arr, mode, &finalVer); +} + + +Status ProtocolConnection::sendHello(const adl::ArrayList& protocolVersions) { + auto data = protocolVersions; + for (auto& it : data) + it = adl::htonq(it); + + return sendMsg(MsgType::Hello, data.data(), data.size() * sizeof(adl::int64_t)); +} + + + +Status ProtocolConnection::recvHello(adl::ArrayList& out) { + Msg* msg; + Status status = recvMsg(&msg, MsgType::Hello); + if (status != Status::SUCCESS) + return status; + + out.clear(); + + for (size_t i = 0; i < msg->header.length / sizeof(int64_t); i++) { + void* p = msg->data + i * sizeof(int64_t); + adl::int64_t value = * (int64_t *) p; + out.append( adl::ntohq(value) ); + } + + this->freeMsg(msg); + + return status; +} + + + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/ProtocolConnection.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/ProtocolConnection.h new file mode 100644 index 0000000000000000000000000000000000000000..c889e56e1b5410eaee6e38f2f24d4aeb4836c921 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/ProtocolConnection.h @@ -0,0 +1,222 @@ +/* + * Protocol Connection Base + * + * Created on 2025.1.21 at Xiangzhou, Zhuhai, Guangdong + * + * + * gongty [at] tongji [dot] edu [dot] cn + * + */ + +#pragma once + +#include + +#include + +#include +#include +#include + +#include + +#include + + + +/** + * This macro is designed for simple "ping-pong" like requests. + * + * This macro should be used after sending request to server. + * + * The macro do follows: + * 1. recv response (return if error). + * 2. if response code is not 0, record and set `status` to error type. + * 3. if response code is 0, run code in `onSuccess`. `response` is used for raw msg. + * 4. free up response message. + */ +#define MONKEY_PROTOCOL_RECV_AND_HANDLE_RESPONSE(onSuccess) \ + do { \ + Response* response = nullptr; \ + if ((status = recvResponse(&response)) != Status::SUCCESS) { \ + return status; \ + } \ + if (response->code != 0) { \ + MONKEY_LOG_ERROR( \ + "VesperProtocol: Response code is ", \ + (response->code + 0), \ + " in api ", \ + __FUNCTION__ \ + ); \ + lastError.set(response, __FUNCTION__); \ + MONKEY_LOG_ERROR( \ + "> Error message is: ", \ + (lastError.msg.c_str() ? lastError.msg.c_str() : "") \ + ); \ + status = Status::PROTOCOL_ERROR; \ + } \ + else { \ + onSuccess \ + } \ + if (response) { \ + this->freeMsg(response); \ + response = nullptr; \ + } \ + } while (0) + + +namespace monkey::net { + +/** + * Agent for a Vesper Protocol based socket connection. + * + * Method types: + * - send [TYPE] : Send a [TYPE] msg to other. + * - decode [TYPE] : Decode a [TYPE] msg received. [TYPE] should be checked. + * - reply [TYPE] : Send a response msg to reply a received [TYPE] msg. + * - appreciate [TYPE] : Understand the reply of [TYPE] request. `appreciate` often appreciates `response`. + * You should ensure msg passed to method is really the response of [TYPE] request. + * - (do) [TYPE] : Do the entire [TYPE] procedure as client (or server if specific method called). + * + * + * + * Client Server + * ============================== + * + * (send) \ + * ----------> . | + * . | + * (decode) | + * . \ + * . > "one click" + * (reply) / + * . <----------- | + * . | + * . | + * (appreciate) / + * + * + * This class only maintains basic info for connection, so it can be copied freely. + * Do not store data like `request counter` in it. + */ +class ProtocolConnection : public Socket4 { +public: + + // Static methods + + inline static const char* magic() { return protocol::MAGIC; } + inline static adl::size_t magicLen() { return protocol::MAGIC_LEN; } + + inline static bool magicMatch(const adl::uint8_t magic[]) { + return * (const adl::uint32_t*) ProtocolConnection::magic() == * (const adl::uint32_t*) magic; + } + + + // Basic info methods + + /** + * This marks current protocol version supported by this object, + * not the one protocol is using. + * + * Each connection should starts with a version 0 connection, then upgrade to + * certain version after "Hello". + * + * See `Protocol.md` for details. + */ + virtual adl::int64_t version() { + return 0; // Override this for each version. + } + + + // Connection operations + + + inline virtual void makeHeader( + protocol::Header* header, + adl::uint32_t type, + adl::uint64_t length, + bool netOrder = true, + bool copyMagic = true + ) { + if (copyMagic) { + adl::memcpy(header->magic, magic(), magicLen()); + } + + header->type = netOrder ? adl::htonl(type) : type; + header->length = netOrder ? adl::htonq(length) : length; + } + + + inline virtual protocol::Header makeHeader(adl::uint32_t type, adl::uint64_t length, bool netOrder = true) { + protocol::Header header; + makeHeader(&header, type, length, netOrder, false); + return header; // We believe NRV would optimize this. + } + + + virtual Status sendMsg( + protocol::MsgType type, + const void* data = nullptr, + adl::uint64_t dataLen = 0 + ); + + + virtual Status sendMsg( + protocol::MsgType type, + const adl::ByteArray& data + ); + + + + virtual Status recvHeader(protocol::Header*); + + + /** + * The caller is responsible for freeing `msg` by calling freeMsg. + * when monkey::Status is SUCCESS. + * + * @param type Ensure type. Set to MsgType::None to disable this check. + */ + virtual Status recvMsg(protocol::Msg** msg, protocol::MsgType type = protocol::MsgType::None); + virtual inline void freeMsg(protocol::Msg* msg) { + if (msg) { + adl::defaultAllocator.free(msg); + } + } + + virtual inline void freeMsg(protocol::Response* msg) { + this->freeMsg((protocol::Msg*) msg); + } + + + + // ------ 0x1000 : Hello ------ + + enum class HelloMode : bool { + SERVER, CLIENT + }; + + virtual Status hello( + const adl::ArrayList& protocolVersions, + HelloMode, + adl::int64_t* finalVersion + ) final; + + /** + * Use a specified version of protocol. If failed, no more negotiation needed. + * + * @param version The version you want to use. + * @param serverMode Whether works in server mode. + * + * @return Status If SUCCESS, negotiation success. + */ + virtual Status hello(adl::int64_t version, HelloMode) final; + + virtual Status sendHello(const adl::ArrayList& protocolVersions) final; + virtual Status recvHello(adl::ArrayList& out) final; + + +}; + + +} \ No newline at end of file diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/ProtocolConnectionDock.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/ProtocolConnectionDock.h new file mode 100644 index 0000000000000000000000000000000000000000..6b8dac47998f9890697fb532f020ecae528c3ea1 --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/ProtocolConnectionDock.h @@ -0,0 +1,31 @@ +/* + Protocol Connection Dock Support + + Created on 2025.6.7 at Wujing, Minhang + + gongty [at] alumni [dot] tongji [dot] edu [dot] cn + +*/ + +/* + TODO (For server mode): + 1. bind + 2. listen + 3. socket +*/ + + +#pragma once + +#include + +#include + + +// Place this in public access field. +#define MONKEY_NET_PROTOCOL_USE_DOCK() + + + +#define MONKEY_NET_IMPL_DOCK_PROTOCOL(baseclass) + \ No newline at end of file diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/defines.cc b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/defines.cc new file mode 100644 index 0000000000000000000000000000000000000000..923131eee56a5a3ea523a4899df91a087e1b54fc --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/defines.cc @@ -0,0 +1,91 @@ +/* + * Basic type defines + * + * Created on 2025.1.21 at Xiangzhou, Zhuhai, Guangdong + * + * + * gongty [at] tongji [dot] edu [dot] cn + * + */ + +#include + +namespace monkey::net::protocol { + +const char* MAGIC = "mkOS"; + + +const char* msgTypeToString(MsgType type) { + switch (type) { + case MsgType::None: + return "None"; + + + case MsgType::Response: + return "Response"; + + + case MsgType::Hello: + return "Hello"; + + case MsgType::Auth: + return "Auth"; + + + case MsgType::GetIdentityKeys: + return "GetIdentityKeys"; + + + case MsgType::MemoryNodeShowId: + return "MemoryNodeShowId"; + + case MsgType::MemoryNodeClockIn: + return "MemoryNodeClockIn"; + + case MsgType::MemoryNodeClockOut: + return "MemoryNodeClockOut"; + + case MsgType::MemoryNodeHandover: + return "MemoryNodeHandover"; + + case MsgType::LocateMemoryNodes: + return "LocateMemoryNodes"; + + + case MsgType::TryAlloc: + return "TryAlloc"; + + case MsgType::ReadBlock: + return "ReadBlock"; + + case MsgType::WriteBlock: + return "WriteBlock"; + + case MsgType::CheckAvailMem: + return "CheckAvailMem"; + + case MsgType::FreeBlock: + return "FreeBlock"; + + + case MsgType::RefBlock: + return "RefBlock"; + + case MsgType::UnrefBlock: + return "UnrefBlock"; + + case MsgType::GetBlockDataVersion: + return "GetBlockDataVersion"; + + + case MsgType::PingPong: + return "PingPong"; + + + default: + return "UNDEFINED"; + }; +} + + +} diff --git a/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/defines.h b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/defines.h new file mode 100644 index 0000000000000000000000000000000000000000..ef0cad557bf646d5debc78fa51bd1c7763221ccd --- /dev/null +++ b/projects/sel4test/apps/monkey-mnemosyne/src/monkey/net/protocol/defines.h @@ -0,0 +1,99 @@ +/* + * Basic type defines + * + * Created on 2025.1.21 at Xiangzhou, Zhuhai, Guangdong + * + * + * gongty [at] tongji [dot] edu [dot] cn + * + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include + + +#define __packed __attribute__((__packed__)) + + + +namespace monkey::net::protocol { + + +extern const char* MAGIC; +const adl::size_t MAGIC_LEN = 4; + + +enum class MsgType : adl::uint32_t { + None = 0x0000, + + Response = 0xA001, + + Hello = 0x1000, + Auth = 0x1001, + + GetIdentityKeys = 0x1100, + + MemoryNodeShowId = 0x2000, + MemoryNodeClockIn = 0x2001, + MemoryNodeClockOut = 0x2002, + MemoryNodeHandover = 0x2003, + LocateMemoryNodes = 0x2004, + + TryAlloc = 0x3001, + ReadBlock = 0x3002, + WriteBlock = 0x3003, + CheckAvailMem = 0x3004, + FreeBlock = 0x3005, + RefBlock = 0x3006, + UnrefBlock = 0x3007, + GetBlockDataVersion = 0x3008, + + PingPong = 0x4001 +}; + + +const char* msgTypeToString(MsgType); + + +struct Header { + union { + adl::uint8_t magic[4]; + + // You can treat it as 32-bit integer. + adl::uint32_t magicI32 = [] () {return * (adl::uint32_t*) MAGIC;} (); + } __packed; + + adl::uint32_t type; + adl::uint64_t length; // Tell byte-order whenever use. +} __packed; + + +/** + * Common message. + */ +struct Msg { + Header header; + adl::uint8_t data[0]; +} __packed; + + +struct Response { + Header header; + adl::uint32_t code; + adl::uint32_t msgLen; + + const char msg[0]; // Not null-terminated for error. +} __packed; + + +} diff --git a/projects/sel4test/apps/sel4test-driver/CMakeLists.txt b/projects/sel4test/apps/sel4test-driver/CMakeLists.txt index bb4a6a1ae725fed6fd511ff3bc72936a38dd882d..1523d094f46ea6b9bcafe39c5d7e08b4d71489f7 100755 --- a/projects/sel4test/apps/sel4test-driver/CMakeLists.txt +++ b/projects/sel4test/apps/sel4test-driver/CMakeLists.txt @@ -82,7 +82,7 @@ add_subdirectory(../sel4test-tests sel4test-tests) include(cpio) MakeCPIO(archive.o "$") -add_executable(sel4test-driver EXCLUDE_FROM_ALL ${static} archive.o) +add_executable(sel4test-driver EXCLUDE_FROM_ALL ${static} src/malloc_stub.c src/wpool_stub.c src/immfs.c archive.o) target_include_directories(sel4test-driver PRIVATE "include") target_link_libraries( sel4test-driver @@ -99,6 +99,7 @@ target_link_libraries( sel4platsupport sel4muslcsys sel4testsupport + sel4bench PRIVATE sel4test-driver_Config ) target_compile_options(sel4test-driver PRIVATE -Werror -g) diff --git a/projects/sel4test/apps/sel4test-driver/src/immfs.c b/projects/sel4test/apps/sel4test-driver/src/immfs.c new file mode 100644 index 0000000000000000000000000000000000000000..de74edae70266c21f17f399997f8741e90651c1c --- /dev/null +++ b/projects/sel4test/apps/sel4test-driver/src/immfs.c @@ -0,0 +1,498 @@ +/* immfs: minimal in-memory filesystem with basic path operations. */ + +#include +#include +#include +#include +#include + +#include "test.h" + +/* xmalloc symbols are provided by malloc_stub.c; forward declare to avoid pulling init.h */ +void xmalloc_init(driver_env_t env); +void *xmalloc(size_t size); +void xfree(void *ptr); + +#define IMMFS_NAME_MAX 32 +#define IMMFS_MAX_COMPONENTS 32 + +typedef enum { + IMMFS_NODE_DIR, + IMMFS_NODE_FILE, +} immfs_node_type_t; + +typedef struct immfs_node { + char name[IMMFS_NAME_MAX]; + immfs_node_type_t type; + struct immfs_node *parent; + struct immfs_node *sibling; + struct immfs_node *child; + uint8_t *data; + size_t size; + size_t capacity; +} immfs_node_t; + +static int ensure_capacity(immfs_node_t *file, size_t needed); + +static immfs_node_t root = { + .name = "/", + .type = IMMFS_NODE_DIR, + .parent = NULL, + .sibling = NULL, + .child = NULL, + .data = NULL, + .size = 0, + .capacity = 0, +}; +static int fs_ready; + +static immfs_node_t *alloc_node(const char *name, immfs_node_type_t type) +{ + immfs_node_t *n = (immfs_node_t *)xmalloc(sizeof(*n)); + if (!n) { + return NULL; + } + memset(n, 0, sizeof(*n)); + n->type = type; + strncpy(n->name, name, IMMFS_NAME_MAX - 1); + n->name[IMMFS_NAME_MAX - 1] = '\0'; + return n; +} + +static void free_subtree(immfs_node_t *node) +{ + while (node) { + immfs_node_t *next = node->sibling; + if (node->child) { + free_subtree(node->child); + } + if (node->data) { + xfree(node->data); + } + xfree(node); + node = next; + } +} + +static void reset_root(void) +{ + if (root.child) { + free_subtree(root.child); + } + root.child = NULL; + root.size = 0; + root.capacity = 0; + root.data = NULL; +} + +static immfs_node_t *find_child(immfs_node_t *dir, const char *name) +{ + for (immfs_node_t *c = dir->child; c; c = c->sibling) { + if (strncmp(c->name, name, IMMFS_NAME_MAX) == 0) { + return c; + } + } + return NULL; +} + +static int split_path(const char *path, char parts[][IMMFS_NAME_MAX]) +{ + if (!path) { + return -EINVAL; + } + int count = 0; + const char *p = path; + while (*p == '/') { + p++; + } + while (*p) { + if (count >= IMMFS_MAX_COMPONENTS) { + return -ENAMETOOLONG; + } + const char *start = p; + while (*p && *p != '/') { + p++; + } + size_t len = (size_t)(p - start); + if (len == 0 || len >= IMMFS_NAME_MAX) { + return -ENAMETOOLONG; + } + memcpy(parts[count], start, len); + parts[count][len] = '\0'; + count++; + while (*p == '/') { + p++; + } + } + return count; +} + +static immfs_node_t *walk_dirs(char parts[][IMMFS_NAME_MAX], int count, int upto, int create_missing) +{ + immfs_node_t *node = &root; + for (int i = 0; i < upto; i++) { + immfs_node_t *child = find_child(node, parts[i]); + if (!child) { + if (!create_missing) { + return NULL; + } + child = alloc_node(parts[i], IMMFS_NODE_DIR); + if (!child) { + return NULL; + } + child->parent = node; + child->sibling = node->child; + node->child = child; + } + if (child->type != IMMFS_NODE_DIR) { + return NULL; + } + node = child; + } + return node; +} + +/* Move a page buffer (allocated via xmalloc) into a new file path and free the buffer. */ +int immfs_swap(void *page_buf, const char *dst_path) +{ + if (!fs_ready) { + return -ENODEV; + } + if (!page_buf) { + return -EINVAL; + } + char dparts[IMMFS_MAX_COMPONENTS][IMMFS_NAME_MAX]; + int dcount = split_path(dst_path, dparts); + if (dcount <= 0) { + return -EINVAL; + } + + immfs_node_t *dparent = walk_dirs(dparts, dcount, dcount - 1, 0); + if (!dparent) { + return -ENOENT; + } + immfs_node_t *existing = find_child(dparent, dparts[dcount - 1]); + if (existing) { + return existing->type == IMMFS_NODE_DIR ? -EISDIR : -EEXIST; + } + + immfs_node_t *dst = alloc_node(dparts[dcount - 1], IMMFS_NODE_FILE); + if (!dst) { + return -ENOMEM; + } + dst->parent = dparent; + dst->sibling = dparent->child; + dparent->child = dst; + + /* attach provided page buffer */ + size_t page_size = 1u << seL4_PageBits; + dst->data = (uint8_t *)page_buf; + dst->capacity = page_size; + dst->size = page_size; + + /* page_buf is now owned by the file; nothing else to free */ + return 0; +} + +int immfs_init(driver_env_t env) +{ + xmalloc_init(env); + reset_root(); + fs_ready = 1; + return 0; +} + +int immfs_mkdir(const char *path) +{ + if (!fs_ready) { + return -ENODEV; + } + char parts[IMMFS_MAX_COMPONENTS][IMMFS_NAME_MAX]; + int count = split_path(path, parts); + if (count < 0) { + return count; + } + if (count == 0) { + return 0; + } + immfs_node_t *parent = walk_dirs(parts, count, count - 1, 1); + if (!parent) { + return -ENOENT; + } + immfs_node_t *existing = find_child(parent, parts[count - 1]); + if (existing) { + return existing->type == IMMFS_NODE_DIR ? 0 : -EEXIST; + } + immfs_node_t *dir = alloc_node(parts[count - 1], IMMFS_NODE_DIR); + if (!dir) { + return -ENOMEM; + } + dir->parent = parent; + dir->sibling = parent->child; + parent->child = dir; + return 0; +} + +int immfs_create(const char *path) +{ + if (!fs_ready) { + return -ENODEV; + } + char parts[IMMFS_MAX_COMPONENTS][IMMFS_NAME_MAX]; + int count = split_path(path, parts); + if (count <= 0) { + return count < 0 ? count : -EINVAL; + } + immfs_node_t *parent = walk_dirs(parts, count, count - 1, 0); + if (!parent) { + return -ENOENT; + } + immfs_node_t *existing = find_child(parent, parts[count - 1]); + if (existing) { + return existing->type == IMMFS_NODE_FILE ? -EEXIST : -EISDIR; + } + immfs_node_t *file = alloc_node(parts[count - 1], IMMFS_NODE_FILE); + if (!file) { + return -ENOMEM; + } + file->parent = parent; + file->sibling = parent->child; + parent->child = file; + return 0; +} + +static int ensure_capacity(immfs_node_t *file, size_t needed) +{ + if (file->capacity >= needed) { + return 0; + } + size_t newcap = file->capacity ? file->capacity : 128; + while (newcap < needed) { + if (newcap > (SIZE_MAX / 2)) { + newcap = needed; + break; + } + newcap *= 2; + } + uint8_t *buf = (uint8_t *)xmalloc(newcap); + if (!buf) { + return -ENOMEM; + } + if (file->data && file->size) { + memcpy(buf, file->data, file->size); + } + if (file->data) { + xfree(file->data); + } + file->data = buf; + file->capacity = newcap; + return 0; +} + +ssize_t immfs_write(const char *path, off_t offset, const void *buf, size_t len) +{ + if (!fs_ready) { + return -ENODEV; + } + if (offset < 0) { + return -EINVAL; + } + if (!buf && len > 0) { + return -EINVAL; + } + char parts[IMMFS_MAX_COMPONENTS][IMMFS_NAME_MAX]; + int count = split_path(path, parts); + if (count <= 0) { + return count < 0 ? count : -EINVAL; + } + immfs_node_t *parent = walk_dirs(parts, count, count - 1, 0); + if (!parent) { + return -ENOENT; + } + immfs_node_t *file = find_child(parent, parts[count - 1]); + if (!file) { + return -ENOENT; + } + if (file->type != IMMFS_NODE_FILE) { + return -EISDIR; + } + size_t end = (size_t)offset + len; + if ((off_t)end < offset) { + return -EOVERFLOW; + } + int err = ensure_capacity(file, end); + if (err) { + return err; + } + if ((size_t)offset > file->size) { + memset(file->data + file->size, 0, (size_t)offset - file->size); + } + if (len > 0) { + memcpy(file->data + offset, buf, len); + } + if (end > file->size) { + file->size = end; + } + return (ssize_t)len; +} + +ssize_t immfs_read(const char *path, off_t offset, void *buf, size_t len) +{ + if (!fs_ready) { + return -ENODEV; + } + if (offset < 0) { + return -EINVAL; + } + if (!buf && len > 0) { + return -EINVAL; + } + char parts[IMMFS_MAX_COMPONENTS][IMMFS_NAME_MAX]; + int count = split_path(path, parts); + if (count <= 0) { + return count < 0 ? count : -EINVAL; + } + immfs_node_t *parent = walk_dirs(parts, count, count - 1, 0); + if (!parent) { + return -ENOENT; + } + immfs_node_t *file = find_child(parent, parts[count - 1]); + if (!file) { + return -ENOENT; + } + if (file->type != IMMFS_NODE_FILE) { + return -EISDIR; + } + if ((size_t)offset >= file->size) { + return 0; + } + size_t available = file->size - (size_t)offset; + size_t to_copy = available < len ? available : len; + if (to_copy > 0) { + memcpy(buf, file->data + offset, to_copy); + } + return (ssize_t)to_copy; +} + +int immfs_unlink(const char *path) +{ + if (!fs_ready) { + return -ENODEV; + } + char parts[IMMFS_MAX_COMPONENTS][IMMFS_NAME_MAX]; + int count = split_path(path, parts); + if (count <= 0) { + return count < 0 ? count : -EINVAL; + } + immfs_node_t *parent = walk_dirs(parts, count, count - 1, 0); + if (!parent) { + return -ENOENT; + } + immfs_node_t *prev = NULL; + immfs_node_t *cur = parent->child; + while (cur && strncmp(cur->name, parts[count - 1], IMMFS_NAME_MAX) != 0) { + prev = cur; + cur = cur->sibling; + } + if (!cur) { + return -ENOENT; + } + if (cur->type == IMMFS_NODE_DIR && cur->child) { + return -ENOTEMPTY; + } + if (prev) { + prev->sibling = cur->sibling; + } else { + parent->child = cur->sibling; + } + if (cur->child) { + free_subtree(cur->child); + } + if (cur->data) { + xfree(cur->data); + } + xfree(cur); + return 0; +} + +int test_immfs(driver_env_t env) +{ + printf("[immfs] starting test suite\n"); + int err = immfs_init(env); + test_assert(err == 0); + + printf("[immfs] create dirs /etc and /var/log\n"); + + err = immfs_mkdir("/etc"); + test_assert(err == 0); + err = immfs_mkdir("/var/log"); + test_assert(err == 0); + + printf("[immfs] create /etc/config and write payload\n"); + err = immfs_create("/etc/config"); + test_assert(err == 0); + + const char *payload = "root=config"; + ssize_t written = immfs_write("/etc/config", 0, payload, strlen(payload)); + test_assert(written == (ssize_t)strlen(payload)); + + char buf[64] = {0}; + ssize_t read = immfs_read("/etc/config", 0, buf, sizeof(buf) - 1); + test_assert(read == written); + test_assert(strncmp(buf, payload, (size_t)written) == 0); + + printf("[immfs] append suffix to /etc/config\n"); + const char *suffix = ";mode=debug"; + written = immfs_write("/etc/config", read, suffix, strlen(suffix)); + test_assert(written == (ssize_t)strlen(suffix)); + memset(buf, 0, sizeof(buf)); + read = immfs_read("/etc/config", 0, buf, sizeof(buf) - 1); + test_assert(read == (ssize_t)(strlen(payload) + strlen(suffix))); + + wait_for_uart_keypress(); + printf("[immfs] swapping memory into /etc/swap.bak\n"); + size_t page_size = 1u << seL4_PageBits; + uint8_t *page = (uint8_t *)xmalloc(page_size); + test_assert(page != NULL); + printf("[immfs] prepared page buffer at %p\n", page); + /* seed page with config contents and a pattern */ + memset(page, 0xCC, page_size); + memcpy(page, payload, strlen(payload)); + memcpy(page + strlen(payload), suffix, strlen(suffix)); + err = immfs_swap(page, "/etc/swap.bak"); + test_assert(err == 0); + printf("[immfs] swapped page buffer into /etc/swap.bak\n"); + uint8_t *page_read = (uint8_t *)xmalloc(page_size); + test_assert(page_read != NULL); + read = immfs_read("/etc/swap.bak", 0, page_read, page_size); + test_assert(read == (ssize_t)page_size); + test_assert(memcmp(page_read, page, page_size) == 0); + printf("[immfs] verified /etc/swap.bak contents\n"); + xfree(page_read); + + // printf("[immfs] write and read /var/log/boot.log\n"); + err = immfs_create("/var/log/boot.log"); + test_assert(err == 0); + const char *log1 = "boot start\n"; + const char *log2 = "boot ok\n"; + test_assert(immfs_write("/var/log/boot.log", 0, log1, strlen(log1)) == (ssize_t)strlen(log1)); + test_assert(immfs_write("/var/log/boot.log", strlen(log1), log2, strlen(log2)) == (ssize_t)strlen(log2)); + char logbuf[64] = {0}; + read = immfs_read("/var/log/boot.log", 0, logbuf, sizeof(logbuf) - 1); + test_assert(read == (ssize_t)(strlen(log1) + strlen(log2))); + + // printf("[immfs] unlink files and directories\n"); + + err = immfs_unlink("/etc/config"); + test_assert(err == 0); + test_assert(immfs_read("/etc/config", 0, buf, sizeof(buf)) == -ENOENT); + + err = immfs_unlink("/var"); + test_assert(err == -ENOTEMPTY); + test_assert(immfs_unlink("/var/log/boot.log") == 0); + test_assert(immfs_unlink("/var/log") == 0); + test_assert(immfs_unlink("/var") == 0); + + printf("[Lib: immfs] file system test completed successfully\n"); + return sel4test_get_result(); +} diff --git a/projects/sel4test/apps/sel4test-driver/src/init.h b/projects/sel4test/apps/sel4test-driver/src/init.h new file mode 100644 index 0000000000000000000000000000000000000000..257b03cdec8024659c5595a08d80e3bd08ae0c61 --- /dev/null +++ b/projects/sel4test/apps/sel4test-driver/src/init.h @@ -0,0 +1,781 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#include "test.h" +#include "timer.h" + +#include + +/* ammount of untyped memory to reserve for the driver (8mb) */ +#define DRIVER_UNTYPED_MEMORY (1 << 25) // 32MB for Phytium Pi +/* Number of untypeds to try and use to allocate the driver memory. + * if we cannot get 8mb with 16 untypeds then something is probably wrong */ +#define DRIVER_NUM_UNTYPEDS 16 + +/* dimensions of virtual memory for the allocator to use */ +#define ALLOCATOR_VIRTUAL_POOL_SIZE ((1 << seL4_PageBits) * 50) + +/* static memory for the allocator to bootstrap with */ +#define ALLOCATOR_STATIC_POOL_SIZE ((1 << seL4_PageBits) * 40) // 40 pages for Phytium Pi +static char allocator_mem_pool[ALLOCATOR_STATIC_POOL_SIZE]; + +/* static memory for virtual memory bootstrapping */ +static sel4utils_alloc_data_t data; + +/* environment encapsulating allocation interfaces etc */ +static struct driver_env env; +/* list of untypeds to give out to test processes */ +static vka_object_t untypeds[CONFIG_MAX_NUM_BOOTINFO_UNTYPED_CAPS]; +/* list of sizes (in bits) corresponding to untyped */ +static uint8_t untyped_size_bits_list[CONFIG_MAX_NUM_BOOTINFO_UNTYPED_CAPS]; + +extern char _cpio_archive[]; +extern char _cpio_archive_end[]; + +static elf_t tests_elf; + +/* initialise our runtime environment */ +static void init_env(driver_env_t env) +{ + allocman_t *allocman; + reservation_t virtual_reservation; + int error; + + /* create an allocator */ + printf("Creating allocator with pool size: %d bytes\n", ALLOCATOR_STATIC_POOL_SIZE); + allocman = bootstrap_use_current_simple(&env->simple, ALLOCATOR_STATIC_POOL_SIZE, allocator_mem_pool); + if (allocman == NULL) { + ZF_LOGF("Failed to create allocman"); + } + printf("Allocator created successfully at %p\n", allocman); + + /* create a vka (interface for interacting with the underlying allocator) */ + printf("Creating VKA interface\n"); + allocman_make_vka(&env->vka, allocman); + printf("VKA interface created successfully\n"); + + /* create a vspace (virtual memory management interface). We pass + * boot info not because it will use capabilities from it, but so + * it knows the address and will add it as a reserved region */ + printf("Getting BootInfo for VSpace initialization\n"); + seL4_BootInfo *bootinfo = platsupport_get_bootinfo(); + if (bootinfo == NULL) { + ZF_LOGF("BootInfo is NULL - critical failure"); + } + printf("BootInfo at %p, userImageFrames: %lu-%lu, userImagePaging: %lu-%lu\n", + bootinfo, bootinfo->userImageFrames.start, bootinfo->userImageFrames.end, + bootinfo->userImagePaging.start, bootinfo->userImagePaging.end); + printf("BootInfo untypedList: %lu-%lu, initThreadCNodeSizeBits: %lu, initThreadDomain: %lu\n", + bootinfo->untyped.start, bootinfo->untyped.end, + bootinfo->initThreadCNodeSizeBits, bootinfo->initThreadDomain); + + printf("Getting page directory from simple interface\n"); + seL4_CPtr pd_cap = simple_get_pd(&env->simple); + if (pd_cap == seL4_CapNull) { + ZF_LOGF("Failed to get page directory capability from simple interface"); + } + printf("Page directory capability: %lu\n", pd_cap); + + printf("Bootstrapping VSpace with BootInfo\n"); + error = sel4utils_bootstrap_vspace_with_bootinfo_leaky(&env->vspace, + &data, pd_cap, + &env->vka, bootinfo); + if (error) { + printf("Failed to bootstrap vspace, error: %d\n", error); + ZF_LOGF("Failed to bootstrap vspace, error: %d", error); + } + printf("VSpace bootstrapped successfully\n"); + + /* fill the allocator with virtual memory */ + printf("Reserving virtual memory pool of size: %d bytes\n", ALLOCATOR_VIRTUAL_POOL_SIZE); + void *vaddr; + virtual_reservation = vspace_reserve_range(&env->vspace, + ALLOCATOR_VIRTUAL_POOL_SIZE, seL4_AllRights, 1, &vaddr); + if (virtual_reservation.res == 0) { + printf("Failed to provide virtual memory for allocator, vaddr: %p\n", vaddr); + ZF_LOGF("Failed to provide virtual memory for allocator, vaddr: %p", vaddr); + } + printf("Virtual memory reserved successfully at %p\n", vaddr); + + printf("Configuring virtual pool for allocator\n"); + bootstrap_configure_virtual_pool(allocman, vaddr, + ALLOCATOR_VIRTUAL_POOL_SIZE, pd_cap); + printf("Virtual pool configured successfully\n"); + + printf("Initializing IO operations\n"); + + /* WORKAROUND: Skip FDT processing in hypervisor environment to avoid MMIO faults + * Root cause: FDT parsing in simple_get_extended_bootinfo causes invalid memory access + * that triggers MMIO faults at addresses like 0x47f8xx in hypervisor environment */ + printf("WORKAROUND: Manually initializing IO ops without FDT for hypervisor compatibility\n"); + memset(&env->ops, 0, sizeof(env->ops)); + + /* Initialize malloc ops */ + error = sel4platsupport_new_malloc_ops(&env->ops.malloc_ops); + ZF_LOGF_IF(error, "Failed to initialise malloc ops, error: %d", error); + + /* Initialize io mapper */ + error = sel4platsupport_new_io_mapper(&env->vspace, &env->vka, &env->ops.io_mapper); + ZF_LOGF_IF(error, "Failed to initialise io mapper, error: %d", error); + + /* Skip FDT initialization - set to NULL for hypervisor compatibility */ + env->ops.io_fdt.cookie = NULL; + env->ops.io_fdt.get_fn = NULL; + + /* Initialize IRQ ops */ + error = sel4platsupport_new_irq_ops(&env->ops.irq_ops, &env->vka, &env->simple, + DEFAULT_IRQ_INTERFACE_CONFIG, &env->ops.malloc_ops); + ZF_LOGF_IF(error, "Failed to initialise IRQ ops, error: %d", error); + + printf("IO operations initialized successfully (without FDT)\n"); + + printf("=== init_env completed successfully ===\n"); +} + +/* Free a list of objects */ +static void free_objects(vka_object_t *objects, unsigned int num) +{ + for (unsigned int i = 0; i < num; i++) { + vka_free_object(&env.vka, &objects[i]); + } +} + +/* Allocate untypeds till either a certain number of bytes is allocated + * or a certain number of untyped objects */ +static unsigned int allocate_untypeds(vka_object_t *untypeds, size_t bytes, unsigned int max_untypeds) +{ + unsigned int num_untypeds = 0; + size_t allocated = 0; + + ZF_LOGI("=== Starting allocate_untypeds ==="); + ZF_LOGI("Target bytes: %zu, max_untypeds: %u", bytes, max_untypeds); + + /* try to allocate as many of each possible untyped size as possible */ + for (uint8_t size_bits = 28; size_bits > PAGE_BITS_4K; size_bits--) { + int allocations_this_size = 0; + /* keep allocating until we run out, or if allocating would + * cause us to allocate too much memory*/ + while (num_untypeds < max_untypeds && + allocated + BIT(size_bits) <= bytes && + vka_alloc_untyped(&env.vka, size_bits, &untypeds[num_untypeds]) == 0) { + allocated += BIT(size_bits); + allocations_this_size++; + num_untypeds++; + } + if (allocations_this_size > 0) { + ZF_LOGI("Allocated %d untypeds of size %u bits (%zu bytes each)", + allocations_this_size, size_bits, BIT(size_bits)); + } + } + + ZF_LOGI("Total allocated: %u untypeds, %zu bytes", num_untypeds, allocated); + ZF_LOGI("=== allocate_untypeds completed ==="); + return num_untypeds; +} + +/* extract a large number of untypeds from the allocator */ +static unsigned int populate_untypeds(vka_object_t *untypeds) +{ + ZF_LOGI("=== Starting populate_untypeds ==="); + + /* First reserve some memory for the driver */ + ZF_LOGI("Reserving %zu bytes (%u MB) for driver with max %u untypeds", + DRIVER_UNTYPED_MEMORY, DRIVER_UNTYPED_MEMORY >> 20, DRIVER_NUM_UNTYPEDS); + vka_object_t reserve[DRIVER_NUM_UNTYPEDS]; + unsigned int reserve_num = allocate_untypeds(reserve, DRIVER_UNTYPED_MEMORY, DRIVER_NUM_UNTYPEDS); + ZF_LOGI("Reserved %u untypeds for driver", reserve_num); + + /* Now allocate everything else for the tests */ + ZF_LOGI("Allocating untypeds for tests (max %zu bytes, max %zu objects)", + (size_t)(1 << 26), ARRAY_SIZE(untyped_size_bits_list)); + unsigned int num_untypeds = allocate_untypeds(untypeds, (1 << 26), ARRAY_SIZE(untyped_size_bits_list)); + ZF_LOGI("Allocated %u untypeds for tests", num_untypeds); + + /* Fill out the size_bits list */ + for (unsigned int i = 0; i < num_untypeds; i++) { + untyped_size_bits_list[i] = untypeds[i].size_bits; + } + + /* Return reserve memory */ + ZF_LOGI("Freeing %u reserved objects", reserve_num); + free_objects(reserve, reserve_num); + + /* Return number of untypeds for tests */ + if (num_untypeds == 0) { + ZF_LOGF("No untypeds for tests!"); + } + + ZF_LOGI("=== populate_untypeds completed: %u untypeds available ===", num_untypeds); + return num_untypeds; +} + +static void init_timer(void) +{ + if (config_set(CONFIG_HAVE_TIMER)) { + int error; + + /* setup the timers and have our wrapper around simple capture the IRQ caps */ + printf("Initializing ltimer_default_init...\n"); + error = ltimer_default_init(&env.ltimer, env.ops, NULL, NULL); + ZF_LOGF_IF(error, "Failed to setup the timers"); + printf("ltimer_default_init completed\n"); + + /* Check timer IRQ configuration */ + if (env.ltimer.get_num_irqs != NULL) { + size_t num_irqs = env.ltimer.get_num_irqs(env.ltimer.data); + printf("Timer IRQs configured: %zu\n", num_irqs); + } else { + printf("WARNING: Timer get_num_irqs function is NULL - no IRQ support\n"); + } + + error = vka_alloc_notification(&env.vka, &env.timer_notify_test); + ZF_LOGF_IF(error, "Failed to allocate notification object for tests"); + + /* Allocate the timer notification if not already done (for platforms without IRQs) */ + if (env.timer_notification.cptr == seL4_CapNull) { + error = vka_alloc_notification(&env.vka, &env.timer_notification); + ZF_LOGF_IF(error, "Failed to allocate timer notification object"); + } + + error = seL4_TCB_BindNotification(simple_get_tcb(&env.simple), env.timer_notification.cptr); + ZF_LOGF_IF(error, "Failed to bind timer notification to sel4test-driver"); + + /* set up the timer manager */ + tm_init(&env.tm, &env.ltimer, &env.ops, 1); + printf("Timer initialization completed\n"); + } +} + +void sel4test_start_suite(const char *name) +{ + if (config_set(CONFIG_PRINT_XML)) { + printf("\n"); + } else { + printf("Starting test suite %s\n", name); + } +} + +void sel4test_start_test(const char *name, int n) +{ + if (config_set(CONFIG_PRINT_XML)) { + printf("\t\n", "sel4test", name); + } else { + printf("Starting test %d: %s\n", n, name); + } + sel4test_reset(); + sel4test_start_printf_buffer(); +} + +void sel4test_end_test(test_result_t result) +{ + sel4test_end_printf_buffer(); + test_check(result == SUCCESS); + + if (config_set(CONFIG_PRINT_XML)) { + printf("\t\n"); + } + + if (config_set(CONFIG_HAVE_TIMER)) { + timer_reset(&env); + } +} + +void sel4test_end_suite(int num_tests, int num_tests_passed, int skipped_tests) +{ + if (config_set(CONFIG_PRINT_XML)) { + printf("\n"); + } else { + if (num_tests_passed != num_tests) { + printf("Test suite failed. %d/%d tests passed.\n", num_tests_passed, num_tests); + } else { + printf("Test suite passed. %d tests passed. %d tests disabled.\n", num_tests, skipped_tests); + } + } +} + +void sel4test_stop_tests(test_result_t result, int tests_done, int tests_failed, int num_tests, int skipped_tests) +{ + /* if its a special abort case, output why we are aborting */ + switch (result) { + case ABORT: + printf("Halting on fatal assertion...\n"); + break; + case FAILURE: + assert(config_set(CONFIG_TESTPRINTER_HALT_ON_TEST_FAILURE)); + printf("Halting on first test failure\n"); + break; + default: + /* nothing to output if its successful */ + break; + } + + /* last test - test all tests ran */ + sel4test_start_test("Test all tests ran", num_tests + 1); + /* Account for skipped tests: tests_done should equal num_tests (enabled tests) */ + printf("Debug: tests_done=%d, num_tests=%d, skipped_tests=%d\n", tests_done, num_tests, skipped_tests); + test_eq(tests_done, num_tests); + if (sel4test_get_result() != SUCCESS) { + tests_failed++; + } + tests_done++; + num_tests++; + sel4test_end_test(sel4test_get_result()); + + sel4test_end_suite(tests_done, tests_done - tests_failed, skipped_tests); + + if (tests_failed > 0) { + printf("*** FAILURES DETECTED ***\n"); + } else if (tests_done < num_tests) { + printf("*** ALL tests not run ***\n"); + } else { + printf("All is well in the universe\n"); + } + printf("\n"); +} + +static int collate_tests(testcase_t *tests_in, int n, testcase_t *tests_out[], int out_index, + regex_t *reg, int *skipped_tests) +{ + for (int i = 0; i < n; i++) { + /* make sure the string is null terminated */ + tests_in[i].name[TEST_NAME_MAX - 1] = '\0'; + if (regexec(reg, tests_in[i].name, 0, NULL, 0) == 0) { + if (tests_in[i].enabled) { + tests_out[out_index] = &tests_in[i]; + out_index++; + } else { + (*skipped_tests)++; + } + } + } + + return out_index; +} + +void sel4test_run_tests(struct driver_env *e) +{ + /* Iterate through test types. */ + int max_test_types = (int)(__stop__test_type - __start__test_type); + struct test_type *test_types[max_test_types]; + int num_test_types = 0; + for (struct test_type *i = __start__test_type; i < __stop__test_type; i++) { + test_types[num_test_types] = i; + num_test_types++; + } + + /* Ensure we iterate through test types in order of ID. */ + qsort(test_types, num_test_types, sizeof(struct test_type *), test_type_comparator); + + /* Count how many tests actually exist and allocate space for them */ + int driver_tests = (int)(__stop__test_case - __start__test_case); + uint64_t tc_size = 0; + testcase_t *sel4test_tests = (testcase_t *) sel4utils_elf_get_section(&tests_elf, "_test_case", &tc_size); + if (sel4test_tests == NULL) { + ZF_LOGF(TESTS_APP": Failed to find section: _test_case"); + } + int tc_tests = tc_size / sizeof(testcase_t); + int all_tests = driver_tests + tc_tests; + testcase_t *tests[all_tests]; + + /* Extract and filter the tests based on the regex */ + regex_t reg; + int error = regcomp(®, CONFIG_TESTPRINTER_REGEX, REG_EXTENDED | REG_NOSUB); + ZF_LOGF_IF(error, "Error compiling regex \"%s\"", CONFIG_TESTPRINTER_REGEX); + + int skipped_tests = 0; + /* get all the tests in the test case section in the driver */ + int num_tests = collate_tests(__start__test_case, driver_tests, tests, 0, ®, &skipped_tests); + /* get all the tests in the sel4test_tests app */ + num_tests = collate_tests(sel4test_tests, tc_tests, tests, num_tests, ®, &skipped_tests); + + /* finished with regex */ + regfree(®); + + /* Sort the tests to remove any non determinism in test ordering */ + qsort(tests, num_tests, sizeof(testcase_t *), test_comparator); + + /* Now that they are sorted we can easily ensure there are no duplicate tests. + * this just ensures some sanity as if there are duplicates, they could have some + * arbitrary ordering, which might result in difficulty reproducing test failures */ + for (int i = 1; i < num_tests; i++) { + ZF_LOGF_IF(strcmp(tests[i]->name, tests[i - 1]->name) == 0, "tests have no strict order! %s %s", + tests[i]->name, tests[i - 1]->name); + } + + /* Check that we don't miss any tests because of an undeclared test type */ + int tests_done = 0; + int tests_failed = 0; + + sel4test_start_suite("sel4test"); + /* First: test that there are tests to run */ + sel4test_start_test("Test that there are tests", tests_done); + test_gt(num_tests, 0); + sel4test_end_test(sel4test_get_result()); + tests_done++; + + /* Iterate through test types so that we run them in order of test type, then name. + * Test types are ordered by ID in test.h. */ + for (int tt = 0; tt < num_test_types; tt++) { + /* set up */ + if (test_types[tt]->set_up_test_type != NULL) { + test_types[tt]->set_up_test_type((uintptr_t)e); + } + + for (int i = 0; i < num_tests; i++) { + if (tests[i]->test_type == test_types[tt]->id) { + sel4test_start_test(tests[i]->name, tests_done); + if (test_types[tt]->set_up != NULL) { + test_types[tt]->set_up((uintptr_t)e); + } + + test_result_t result = test_types[tt]->run_test(tests[i], (uintptr_t)e); + + if (test_types[tt]->tear_down != NULL) { + test_types[tt]->tear_down((uintptr_t)e); + } + sel4test_end_test(result); + + if (result != SUCCESS) { + tests_failed++; + if (config_set(CONFIG_TESTPRINTER_HALT_ON_TEST_FAILURE) || result == ABORT) { + sel4test_stop_tests(result, tests_done + 1, tests_failed, num_tests + 1, skipped_tests); + return; + } + } + tests_done++; + } + } + + /* tear down */ + if (test_types[tt]->tear_down_test_type != NULL) { + test_types[tt]->tear_down_test_type((uintptr_t)e); + } + } + + /* and we're done */ + sel4test_stop_tests(SUCCESS, tests_done, tests_failed, num_tests + 1, skipped_tests); +} + +void *main_continued(void *arg UNUSED) +{ + + /* elf region data */ + int num_elf_regions; + sel4utils_elf_region_t elf_regions[MAX_REGIONS]; + + printf("=== main_continued started ===\n"); + printf("env.vspace pointer: %p\n", &env.vspace); + printf("env.vka pointer: %p\n", &env.vka); + printf("env.vspace content check:\n"); + printf(" env.vspace.data: %p\n", env.vspace.data); + printf(" env.vspace.new_pages: %p\n", env.vspace.new_pages); + printf(" env.vspace.reserve_range_at: %p\n", env.vspace.reserve_range_at); + printf("Validating vspace structure...\n"); + if (env.vspace.data == NULL) { + printf("ERROR: env.vspace.data is NULL!\n"); + } + if (env.vspace.new_pages == NULL) { + printf("ERROR: env.vspace.new_pages function pointer is NULL!\n"); + } + if (env.vspace.reserve_range_at == NULL) { + printf("ERROR: env.vspace.reserve_range_at function pointer is NULL!\n"); + } + + unsigned long elf_size; + unsigned long cpio_len = _cpio_archive_end - _cpio_archive; + const void *elf_file = cpio_get_file(_cpio_archive, cpio_len, TESTS_APP, &elf_size); + ZF_LOGF_IF(elf_file == NULL, "Error: failed to lookup ELF file"); + int status = elf_newFile(elf_file, elf_size, &tests_elf); + ZF_LOGF_IF(status, "Error: invalid ELF file"); + + /* Print welcome banner. */ + printf("\n"); + printf("seL4 Test\n"); + printf("=========\n"); + printf("\n"); + + int error; + + /* allocate a piece of device untyped memory for the frame tests, + * note that spike doesn't have any device untypes so the tests that require device untypes are turned off */ + if (!config_set(CONFIG_PLAT_SPIKE)) { + bool allocated = false; + int untyped_count = simple_get_untyped_count(&env.simple); + for (int i = 0; i < untyped_count; i++) { + bool device = false; + uintptr_t ut_paddr = 0; + size_t ut_size_bits = 0; + seL4_CPtr ut_cptr = simple_get_nth_untyped(&env.simple, i, &ut_size_bits, &ut_paddr, &device); + if (device) { + error = vka_alloc_frame_at(&env.vka, seL4_PageBits, ut_paddr, &env.device_obj); + if (!error) { + allocated = true; + /* we've allocated a single device frame and that's all we need */ + break; + } + } + } + ZF_LOGF_IF(allocated == false, "Failed to allocate a device frame for the frame tests"); + } + + /* allocate lots of untyped memory for tests to use */ + printf("About to populate untypeds...\n"); + env.num_untypeds = populate_untypeds(untypeds); + env.untypeds = untypeds; + printf("Untypeds populated: %u\n", env.num_untypeds); + + /* create a frame that will act as the init data, we can then map that + * in to target processes */ + printf("About to allocate init data frame, vspace at: %p\n", &env.vspace); + printf("Before vspace_new_pages call:\n"); + printf(" env.vspace.data: %p\n", env.vspace.data); + printf(" env.vspace.new_pages: %p\n", env.vspace.new_pages); + printf(" Calling vspace_new_pages(&env.vspace=%p, seL4_AllRights, 1, PAGE_BITS_4K)\n", &env.vspace); + + if (&env.vspace == NULL) { + printf("ERROR: env.vspace is NULL!\n"); + ZF_LOGF("env.vspace is NULL!"); + } + if (env.vspace.data == NULL) { + printf("ERROR: env.vspace.data is NULL before vspace_new_pages!\n"); + ZF_LOGF("env.vspace.data is NULL before vspace_new_pages!"); + } + + env.init = (test_init_data_t *) vspace_new_pages(&env.vspace, seL4_AllRights, 1, PAGE_BITS_4K); + assert(env.init != NULL); + printf("Init data frame allocated at: %p\n", env.init); + + /* copy the untyped size bits list across to the init frame */ + memcpy(env.init->untyped_size_bits_list, untyped_size_bits_list, sizeof(uint8_t) * env.num_untypeds); + + /* parse elf region data about the test image to pass to the tests app */ + // printf("About to parse ELF regions...\n"); + // num_elf_regions = sel4utils_elf_num_regions(&tests_elf); + // assert(num_elf_regions <= MAX_REGIONS); + // printf("TEST 0001\n"); + // sel4utils_elf_reserve(NULL, &tests_elf, elf_regions); + // printf("TEST 0002\n"); + + // /* copy the region list for the process to clone itself */ + // memcpy(env.init->elf_regions, elf_regions, sizeof(sel4utils_elf_region_t) * num_elf_regions); + // env.init->num_elf_regions = num_elf_regions; + // printf("env num_elf_regions: %d\n", num_elf_regions); + + /* setup init data that won't change test-to-test */ + env.init->priority = seL4_MaxPrio - 1; + if (plat_init) { + plat_init(&env); + } + + /* Allocate a reply object for the RT kernel. */ + if (config_set(CONFIG_KERNEL_MCS)) { + error = vka_alloc_reply(&env.vka, &env.reply); + ZF_LOGF_IF(error, "Failed to allocate reply"); + } + + /* now run the tests */ + sel4test_run_tests(&env); + + return NULL; +} + +/* Note that the following globals are place here because it is not expected that + * this function be refactored out of sel4test-driver in its current form. */ +/* Number of objects to track allocation of. Currently all serial devices are + * initialised with a single Frame object. Future devices may need more than 1. + */ +#define NUM_ALLOC_AT_TO_TRACK 1 +/* Static global to store the original vka_utspace_alloc_at function. It + * isn't expected for this to dynamically change after initialisation.*/ +static vka_utspace_alloc_at_fn vka_utspace_alloc_at_base; +/* State that serial_utspace_alloc_at_fn uses to determine whether to cache + * allocations. It is intended that this flag gets set before the serial device + * is initialised and then unset afterwards. */ +static bool serial_utspace_record = false; + +typedef struct uspace_alloc_at_args { + uintptr_t paddr; + seL4_Word type; + seL4_Word size_bits; + cspacepath_t dest; +} uspace_alloc_at_args_t; +/* This instance of vka_utspace_alloc_at_fn will keep a record of allocations up + * to NUM_ALLOC_AT_TO_TRACK while serial_utspace_record is set. When serial_utspace_record + * is unset, any allocations matching recorded allocations will instead copy the cap + * that was originally allocated. These subsequent allocations cannot be freed using + * vka_utspace_free and instead the caps would have to be manually deleted. + * Freeing these objects via vka_utspace_free would require also wrapping that function.*/ +static int serial_utspace_alloc_at_fn(void *data, const cspacepath_t *dest, seL4_Word type, seL4_Word size_bits, + uintptr_t paddr, seL4_Word *cookie) +{ + static uspace_alloc_at_args_t args_prev[NUM_ALLOC_AT_TO_TRACK] = {}; + static size_t num_alloc = 0; + + ZF_LOGF_IF(!vka_utspace_alloc_at_base, "vka_utspace_alloc_at_base not initialised."); + if (!serial_utspace_record) { + for (int i = 0; i < num_alloc; i++) { + if (paddr == args_prev[i].paddr && + type == args_prev[i].type && + size_bits == args_prev[i].size_bits) { + return vka_cnode_copy(dest, &args_prev[i].dest, seL4_AllRights); + } + } + return vka_utspace_alloc_at_base(data, dest, type, size_bits, paddr, cookie); + } else { + ZF_LOGF_IF(num_alloc >= NUM_ALLOC_AT_TO_TRACK, "Trying to allocate too many utspace objects"); + int ret = vka_utspace_alloc_at_base(data, dest, type, size_bits, paddr, cookie); + if (ret) { + return ret; + } + uspace_alloc_at_args_t a = {.paddr = paddr, .type = type, .size_bits = size_bits, .dest = *dest}; + args_prev[num_alloc] = a; + num_alloc++; + return ret; + + } +} + +static ps_irq_register_fn_t irq_register_fn_copy; + +static irq_id_t sel4test_timer_irq_register(UNUSED void *cookie, ps_irq_t irq, irq_callback_fn_t callback, + void *callback_data) +{ + static int num_timer_irqs = 0; + + int error; + + ZF_LOGF_IF(!callback, "Passed in a NULL callback"); + + ZF_LOGF_IF(num_timer_irqs >= MAX_TIMER_IRQS, "Trying to register too many timer IRQs"); + + /* Allocate the IRQ */ + error = sel4platsupport_copy_irq_cap(&env.vka, &env.simple, &irq, + &env.timer_irqs[num_timer_irqs].handler_path); + ZF_LOGF_IF(error, "Failed to allocate IRQ handler"); + + /* Allocate the root notifitcation if we haven't already done so */ + if (env.timer_notification.cptr == seL4_CapNull) { + error = vka_alloc_notification(&env.vka, &env.timer_notification); + ZF_LOGF_IF(error, "Failed to allocate notification object"); + } + + /* Mint a notification for the IRQ handler to pair with */ + error = vka_cspace_alloc_path(&env.vka, &env.badged_timer_notifications[num_timer_irqs]); + ZF_LOGF_IF(error, "Failed to allocate path for the badged notification"); + cspacepath_t root_notification_path = {0}; + vka_cspace_make_path(&env.vka, env.timer_notification.cptr, &root_notification_path); + error = vka_cnode_mint(&env.badged_timer_notifications[num_timer_irqs], &root_notification_path, + seL4_AllRights, BIT(num_timer_irqs)); + ZF_LOGF_IF(error, "Failed to mint notification for timer"); + + /* Pair the notification and the handler */ + error = seL4_IRQHandler_SetNotification(env.timer_irqs[num_timer_irqs].handler_path.capPtr, + env.badged_timer_notifications[num_timer_irqs].capPtr); + ZF_LOGF_IF(error, "Failed to pair the notification and handler together"); + + /* Ack the handler so interrupts can come in */ + error = seL4_IRQHandler_Ack(env.timer_irqs[num_timer_irqs].handler_path.capPtr); + ZF_LOGF_IF(error, "Failed to ack the IRQ handler"); + + /* Fill out information about the callbacks */ + env.timer_cbs[num_timer_irqs].callback = callback; + env.timer_cbs[num_timer_irqs].callback_data = callback_data; + + return num_timer_irqs++; +} + +/* When the root task exists, it should simply suspend itself */ +static void sel4test_exit(int code) +{ + seL4_TCB_Suspend(seL4_CapInitThreadTCB); +} + + +/* xmalloc_init provided by malloc_stub.c */ +void xmalloc_init(driver_env_t env); +/* test allocator API */ +void *xmalloc(size_t size); +void xfree(void *ptr); +/* minikv API */ +int minikv_init(driver_env_t env); +int minikv_set(const char *key, void *value); +void *minikv_get(const char *key); +int minikv_delete(const char *key); +void minikv_destroy(void); + +/* immfs: simple in-memory filesystem */ +int immfs_init(driver_env_t env); +int immfs_mkdir(const char *path); +int immfs_create(const char *path); +ssize_t immfs_write(const char *path, off_t offset, const void *buf, size_t len); +ssize_t immfs_read(const char *path, off_t offset, void *buf, size_t len); +int immfs_unlink(const char *path); + + +// Testsuite +int test_malloc_free(driver_env_t env); +int test_minikv(driver_env_t env); +int test_wpool(driver_env_t env); +int test_wpool_remote(driver_env_t env, void *data_vaddr); +int test_immfs(driver_env_t env); +void sel4test_driver_init(void) +{ + printf("=== sel4test_driver_init started ===\n"); + + /* initialize the environment */ + init_env(&env); + + /* initialize timer */ + init_timer(); + + /* wrap the vka_utspace_alloc_at function to track serial device allocations */ + vka_utspace_alloc_at_base = env.vka.utspace_alloc_at; + env.vka.utspace_alloc_at = serial_utspace_alloc_at_fn; + + /* initialize xmalloc */ + xmalloc_init(&env); + + /* initialize minikv */ + // int error = minikv_init(&env); + // ZF_LOGF_IF(error, "Failed to initialize minikv"); + + printf("=== sel4test_driver_init completed ===\n"); +} diff --git a/projects/sel4test/apps/sel4test-driver/src/main.c b/projects/sel4test/apps/sel4test-driver/src/main.c index d6718517a08fc3642bc58f4f355dfac860a3b403..b6eee6557c32c994b1d06c77c4e187aa3c98054c 100755 --- a/projects/sel4test/apps/sel4test-driver/src/main.c +++ b/projects/sel4test/apps/sel4test-driver/src/main.c @@ -14,723 +14,72 @@ #include #include #include +#include #include - -#include -#include - -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include - -#include +#include +#include "init.h" #include "test.h" -#include "timer.h" - -#include - -/* ammount of untyped memory to reserve for the driver (8mb) */ -#define DRIVER_UNTYPED_MEMORY (1 << 25) // 32MB for Phytium Pi -/* Number of untypeds to try and use to allocate the driver memory. - * if we cannot get 8mb with 16 untypeds then something is probably wrong */ -#define DRIVER_NUM_UNTYPEDS 16 - -/* dimensions of virtual memory for the allocator to use */ -#define ALLOCATOR_VIRTUAL_POOL_SIZE ((1 << seL4_PageBits) * 50) - -/* static memory for the allocator to bootstrap with */ -#define ALLOCATOR_STATIC_POOL_SIZE ((1 << seL4_PageBits) * 40) // 40 pages for Phytium Pi -static char allocator_mem_pool[ALLOCATOR_STATIC_POOL_SIZE]; - -/* static memory for virtual memory bootstrapping */ -static sel4utils_alloc_data_t data; - -/* environment encapsulating allocation interfaces etc */ -static struct driver_env env; -/* list of untypeds to give out to test processes */ -static vka_object_t untypeds[CONFIG_MAX_NUM_BOOTINFO_UNTYPED_CAPS]; -/* list of sizes (in bits) corresponding to untyped */ -static uint8_t untyped_size_bits_list[CONFIG_MAX_NUM_BOOTINFO_UNTYPED_CAPS]; - -extern char _cpio_archive[]; -extern char _cpio_archive_end[]; - -static elf_t tests_elf; - -/* initialise our runtime environment */ -static void init_env(driver_env_t env) -{ - allocman_t *allocman; - reservation_t virtual_reservation; - int error; - - printf("=== Starting init_env for Phytium Pi platform ==="); - - /* create an allocator */ - printf("Creating allocator with pool size: %d bytes\n", ALLOCATOR_STATIC_POOL_SIZE); - allocman = bootstrap_use_current_simple(&env->simple, ALLOCATOR_STATIC_POOL_SIZE, allocator_mem_pool); - if (allocman == NULL) { - ZF_LOGF("Failed to create allocman"); - } - printf("Allocator created successfully at %p\n", allocman); - - /* create a vka (interface for interacting with the underlying allocator) */ - printf("Creating VKA interface\n"); - allocman_make_vka(&env->vka, allocman); - printf("VKA interface created successfully\n"); - - /* create a vspace (virtual memory management interface). We pass - * boot info not because it will use capabilities from it, but so - * it knows the address and will add it as a reserved region */ - printf("Getting BootInfo for VSpace initialization\n"); - seL4_BootInfo *bootinfo = platsupport_get_bootinfo(); - if (bootinfo == NULL) { - ZF_LOGF("BootInfo is NULL - critical failure for Phytium Pi platform"); - } - printf("BootInfo at %p, userImageFrames: %lu-%lu, userImagePaging: %lu-%lu\n", - bootinfo, bootinfo->userImageFrames.start, bootinfo->userImageFrames.end, - bootinfo->userImagePaging.start, bootinfo->userImagePaging.end); - printf("BootInfo untypedList: %lu-%lu, initThreadCNodeSizeBits: %lu, initThreadDomain: %lu\n", - bootinfo->untyped.start, bootinfo->untyped.end, - bootinfo->initThreadCNodeSizeBits, bootinfo->initThreadDomain); - - printf("Getting page directory from simple interface\n"); - seL4_CPtr pd_cap = simple_get_pd(&env->simple); - if (pd_cap == seL4_CapNull) { - ZF_LOGF("Failed to get page directory capability from simple interface"); - } - printf("Page directory capability: %lu\n", pd_cap); - - printf("Bootstrapping VSpace with BootInfo\n"); - error = sel4utils_bootstrap_vspace_with_bootinfo_leaky(&env->vspace, - &data, pd_cap, - &env->vka, bootinfo); - if (error) { - printf("Failed to bootstrap vspace, error: %d\n", error); - ZF_LOGF("Failed to bootstrap vspace, error: %d", error); - } - printf("VSpace bootstrapped successfully\n"); - - /* fill the allocator with virtual memory */ - printf("Reserving virtual memory pool of size: %d bytes\n", ALLOCATOR_VIRTUAL_POOL_SIZE); - void *vaddr; - virtual_reservation = vspace_reserve_range(&env->vspace, - ALLOCATOR_VIRTUAL_POOL_SIZE, seL4_AllRights, 1, &vaddr); - if (virtual_reservation.res == 0) { - printf("Failed to provide virtual memory for allocator, vaddr: %p\n", vaddr); - ZF_LOGF("Failed to provide virtual memory for allocator, vaddr: %p", vaddr); - } - printf("Virtual memory reserved successfully at %p\n", vaddr); - - printf("Configuring virtual pool for allocator\n"); - bootstrap_configure_virtual_pool(allocman, vaddr, - ALLOCATOR_VIRTUAL_POOL_SIZE, pd_cap); - printf("Virtual pool configured successfully\n"); - - printf("Initializing IO operations\n"); - - /* WORKAROUND: Skip FDT processing in hypervisor environment to avoid MMIO faults - * Root cause: FDT parsing in simple_get_extended_bootinfo causes invalid memory access - * that triggers MMIO faults at addresses like 0x47f8xx in hypervisor environment */ - printf("WORKAROUND: Manually initializing IO ops without FDT for hypervisor compatibility\n"); - memset(&env->ops, 0, sizeof(env->ops)); - - /* Initialize malloc ops */ - error = sel4platsupport_new_malloc_ops(&env->ops.malloc_ops); - ZF_LOGF_IF(error, "Failed to initialise malloc ops, error: %d", error); - - /* Initialize io mapper */ - error = sel4platsupport_new_io_mapper(&env->vspace, &env->vka, &env->ops.io_mapper); - ZF_LOGF_IF(error, "Failed to initialise io mapper, error: %d", error); - - /* Skip FDT initialization - set to NULL for hypervisor compatibility */ - env->ops.io_fdt.cookie = NULL; - env->ops.io_fdt.get_fn = NULL; - - /* Initialize IRQ ops */ - error = sel4platsupport_new_irq_ops(&env->ops.irq_ops, &env->vka, &env->simple, - DEFAULT_IRQ_INTERFACE_CONFIG, &env->ops.malloc_ops); - ZF_LOGF_IF(error, "Failed to initialise IRQ ops, error: %d", error); - - printf("IO operations initialized successfully (without FDT)\n"); - - printf("=== init_env completed successfully ===\n"); -} - -/* Free a list of objects */ -static void free_objects(vka_object_t *objects, unsigned int num) -{ - for (unsigned int i = 0; i < num; i++) { - vka_free_object(&env.vka, &objects[i]); - } -} - -/* Allocate untypeds till either a certain number of bytes is allocated - * or a certain number of untyped objects */ -static unsigned int allocate_untypeds(vka_object_t *untypeds, size_t bytes, unsigned int max_untypeds) -{ - unsigned int num_untypeds = 0; - size_t allocated = 0; - - ZF_LOGI("=== Starting allocate_untypeds ==="); - ZF_LOGI("Target bytes: %zu, max_untypeds: %u", bytes, max_untypeds); - - /* try to allocate as many of each possible untyped size as possible */ - for (uint8_t size_bits = 28; size_bits > PAGE_BITS_4K; size_bits--) { - int allocations_this_size = 0; - /* keep allocating until we run out, or if allocating would - * cause us to allocate too much memory*/ - while (num_untypeds < max_untypeds && - allocated + BIT(size_bits) <= bytes && - vka_alloc_untyped(&env.vka, size_bits, &untypeds[num_untypeds]) == 0) { - allocated += BIT(size_bits); - allocations_this_size++; - num_untypeds++; - } - if (allocations_this_size > 0) { - ZF_LOGI("Allocated %d untypeds of size %u bits (%zu bytes each)", - allocations_this_size, size_bits, BIT(size_bits)); - } - } - - ZF_LOGI("Total allocated: %u untypeds, %zu bytes", num_untypeds, allocated); - ZF_LOGI("=== allocate_untypeds completed ==="); - return num_untypeds; -} -/* extract a large number of untypeds from the allocator */ -static unsigned int populate_untypeds(vka_object_t *untypeds) -{ - ZF_LOGI("=== Starting populate_untypeds ==="); - - /* First reserve some memory for the driver */ - ZF_LOGI("Reserving %zu bytes (%u MB) for driver with max %u untypeds", - DRIVER_UNTYPED_MEMORY, DRIVER_UNTYPED_MEMORY >> 20, DRIVER_NUM_UNTYPEDS); - vka_object_t reserve[DRIVER_NUM_UNTYPEDS]; - unsigned int reserve_num = allocate_untypeds(reserve, DRIVER_UNTYPED_MEMORY, DRIVER_NUM_UNTYPEDS); - ZF_LOGI("Reserved %u untypeds for driver", reserve_num); - - /* Now allocate everything else for the tests */ - ZF_LOGI("Allocating untypeds for tests (max %zu bytes, max %zu objects)", - (size_t)(1 << 26), ARRAY_SIZE(untyped_size_bits_list)); - unsigned int num_untypeds = allocate_untypeds(untypeds, (1 << 26), ARRAY_SIZE(untyped_size_bits_list)); - ZF_LOGI("Allocated %u untypeds for tests", num_untypeds); - - /* Fill out the size_bits list */ - for (unsigned int i = 0; i < num_untypeds; i++) { - untyped_size_bits_list[i] = untypeds[i].size_bits; - } - - /* Return reserve memory */ - ZF_LOGI("Freeing %u reserved objects", reserve_num); - free_objects(reserve, reserve_num); - /* Return number of untypeds for tests */ - if (num_untypeds == 0) { - ZF_LOGF("No untypeds for tests!"); - } - - ZF_LOGI("=== populate_untypeds completed: %u untypeds available ===", num_untypeds); - return num_untypeds; -} -static void init_timer(void) +void printRawString(char* str) { - if (config_set(CONFIG_HAVE_TIMER)) { - int error; - - /* setup the timers and have our wrapper around simple capture the IRQ caps */ - printf("Initializing ltimer_default_init...\n"); - error = ltimer_default_init(&env.ltimer, env.ops, NULL, NULL); - ZF_LOGF_IF(error, "Failed to setup the timers"); - printf("ltimer_default_init completed\n"); - - /* Check timer IRQ configuration */ - if (env.ltimer.get_num_irqs != NULL) { - size_t num_irqs = env.ltimer.get_num_irqs(env.ltimer.data); - printf("Timer IRQs configured: %zu\n", num_irqs); - } else { - printf("WARNING: Timer get_num_irqs function is NULL - no IRQ support\n"); - } - - error = vka_alloc_notification(&env.vka, &env.timer_notify_test); - ZF_LOGF_IF(error, "Failed to allocate notification object for tests"); - - /* Allocate the timer notification if not already done (for platforms without IRQs) */ - if (env.timer_notification.cptr == seL4_CapNull) { - error = vka_alloc_notification(&env.vka, &env.timer_notification); - ZF_LOGF_IF(error, "Failed to allocate timer notification object"); + size_t max_len = 1024;/* 为安全起见,可以限制最大读长度,避免读到非法内存 */ + size_t i = 0; + putchar('"'); + while (i < max_len) { + char c = str[i]; + if (c == '\0') + { + break; } - - error = seL4_TCB_BindNotification(simple_get_tcb(&env.simple), env.timer_notification.cptr); - ZF_LOGF_IF(error, "Failed to bind timer notification to sel4test-driver"); - - /* set up the timer manager */ - tm_init(&env.tm, &env.ltimer, &env.ops, 1); - printf("Timer initialization completed\n"); - } -} - -void sel4test_start_suite(const char *name) -{ - if (config_set(CONFIG_PRINT_XML)) { - printf("\n"); - } else { - printf("Starting test suite %s\n", name); - } -} - -void sel4test_start_test(const char *name, int n) -{ - if (config_set(CONFIG_PRINT_XML)) { - printf("\t\n", "sel4test", name); - } else { - printf("Starting test %d: %s\n", n, name); + putchar(c); + i++; } - sel4test_reset(); - sel4test_start_printf_buffer(); + putchar('"'); + putchar('\n'); } -void sel4test_end_test(test_result_t result) -{ - sel4test_end_printf_buffer(); - test_check(result == SUCCESS); - - if (config_set(CONFIG_PRINT_XML)) { - printf("\t\n"); - } +static ps_chardevice_t uart_console; +static bool uart_console_ready = false; - if (config_set(CONFIG_HAVE_TIMER)) { - timer_reset(&env); - } -} - -void sel4test_end_suite(int num_tests, int num_tests_passed, int skipped_tests) +static ps_chardevice_t *get_uart_console(void) { - if (config_set(CONFIG_PRINT_XML)) { - printf("\n"); - } else { - if (num_tests_passed != num_tests) { - printf("Test suite failed. %d/%d tests passed.\n", num_tests_passed, num_tests); - } else { - printf("Test suite passed. %d tests passed. %d tests disabled.\n", num_tests, skipped_tests); + if (!uart_console_ready) { + ps_chardevice_t *dev = ps_cdev_init(PS_SERIAL_DEFAULT, &env.ops, &uart_console); + if (dev == NULL) { + ZF_LOGE("Failed to initialise UART console device for input"); + return NULL; } + uart_console_ready = true; } + return &uart_console; } -void sel4test_stop_tests(test_result_t result, int tests_done, int tests_failed, int num_tests, int skipped_tests) +void wait_for_uart_keypress(void) { - /* if its a special abort case, output why we are aborting */ - switch (result) { - case ABORT: - printf("Halting on fatal assertion...\n"); - break; - case FAILURE: - assert(config_set(CONFIG_TESTPRINTER_HALT_ON_TEST_FAILURE)); - printf("Halting on first test failure\n"); - break; - default: - /* nothing to output if its successful */ - break; + ps_chardevice_t *dev = get_uart_console(); + if (dev == NULL) { + printf("UART input unavailable; continuing without wait.\n"); + return; } - /* last test - test all tests ran */ - sel4test_start_test("Test all tests ran", num_tests + 1); - /* Account for skipped tests: tests_done should equal num_tests (enabled tests) */ - printf("Debug: tests_done=%d, num_tests=%d, skipped_tests=%d\n", tests_done, num_tests, skipped_tests); - test_eq(tests_done, num_tests); - if (sel4test_get_result() != SUCCESS) { - tests_failed++; - } - tests_done++; - num_tests++; - sel4test_end_test(sel4test_get_result()); - - sel4test_end_suite(tests_done, tests_done - tests_failed, skipped_tests); - - if (tests_failed > 0) { - printf("*** FAILURES DETECTED ***\n"); - } else if (tests_done < num_tests) { - printf("*** ALL tests not run ***\n"); - } else { - printf("All is well in the universe\n"); - } - printf("\n\n"); -} - -static int collate_tests(testcase_t *tests_in, int n, testcase_t *tests_out[], int out_index, - regex_t *reg, int *skipped_tests) -{ - for (int i = 0; i < n; i++) { - /* make sure the string is null terminated */ - tests_in[i].name[TEST_NAME_MAX - 1] = '\0'; - if (regexec(reg, tests_in[i].name, 0, NULL, 0) == 0) { - if (tests_in[i].enabled) { - tests_out[out_index] = &tests_in[i]; - out_index++; - } else { - (*skipped_tests)++; - } - } - } - - return out_index; -} - -void sel4test_run_tests(struct driver_env *e) -{ - /* Iterate through test types. */ - int max_test_types = (int)(__stop__test_type - __start__test_type); - struct test_type *test_types[max_test_types]; - int num_test_types = 0; - for (struct test_type *i = __start__test_type; i < __stop__test_type; i++) { - test_types[num_test_types] = i; - num_test_types++; - } - - /* Ensure we iterate through test types in order of ID. */ - qsort(test_types, num_test_types, sizeof(struct test_type *), test_type_comparator); - - /* Count how many tests actually exist and allocate space for them */ - int driver_tests = (int)(__stop__test_case - __start__test_case); - uint64_t tc_size = 0; - testcase_t *sel4test_tests = (testcase_t *) sel4utils_elf_get_section(&tests_elf, "_test_case", &tc_size); - if (sel4test_tests == NULL) { - ZF_LOGF(TESTS_APP": Failed to find section: _test_case"); - } - int tc_tests = tc_size / sizeof(testcase_t); - int all_tests = driver_tests + tc_tests; - testcase_t *tests[all_tests]; - - /* Extract and filter the tests based on the regex */ - regex_t reg; - int error = regcomp(®, CONFIG_TESTPRINTER_REGEX, REG_EXTENDED | REG_NOSUB); - ZF_LOGF_IF(error, "Error compiling regex \"%s\"", CONFIG_TESTPRINTER_REGEX); - - int skipped_tests = 0; - /* get all the tests in the test case section in the driver */ - int num_tests = collate_tests(__start__test_case, driver_tests, tests, 0, ®, &skipped_tests); - /* get all the tests in the sel4test_tests app */ - num_tests = collate_tests(sel4test_tests, tc_tests, tests, num_tests, ®, &skipped_tests); - - /* finished with regex */ - regfree(®); - - /* Sort the tests to remove any non determinism in test ordering */ - qsort(tests, num_tests, sizeof(testcase_t *), test_comparator); - - /* Now that they are sorted we can easily ensure there are no duplicate tests. - * this just ensures some sanity as if there are duplicates, they could have some - * arbitrary ordering, which might result in difficulty reproducing test failures */ - for (int i = 1; i < num_tests; i++) { - ZF_LOGF_IF(strcmp(tests[i]->name, tests[i - 1]->name) == 0, "tests have no strict order! %s %s", - tests[i]->name, tests[i - 1]->name); - } - - /* Check that we don't miss any tests because of an undeclared test type */ - int tests_done = 0; - int tests_failed = 0; - - sel4test_start_suite("sel4test"); - /* First: test that there are tests to run */ - sel4test_start_test("Test that there are tests", tests_done); - test_gt(num_tests, 0); - sel4test_end_test(sel4test_get_result()); - tests_done++; - - /* Iterate through test types so that we run them in order of test type, then name. - * Test types are ordered by ID in test.h. */ - for (int tt = 0; tt < num_test_types; tt++) { - /* set up */ - if (test_types[tt]->set_up_test_type != NULL) { - test_types[tt]->set_up_test_type((uintptr_t)e); - } - - for (int i = 0; i < num_tests; i++) { - if (tests[i]->test_type == test_types[tt]->id) { - sel4test_start_test(tests[i]->name, tests_done); - if (test_types[tt]->set_up != NULL) { - test_types[tt]->set_up((uintptr_t)e); - } - - test_result_t result = test_types[tt]->run_test(tests[i], (uintptr_t)e); - - if (test_types[tt]->tear_down != NULL) { - test_types[tt]->tear_down((uintptr_t)e); - } - sel4test_end_test(result); - - if (result != SUCCESS) { - tests_failed++; - if (config_set(CONFIG_TESTPRINTER_HALT_ON_TEST_FAILURE) || result == ABORT) { - sel4test_stop_tests(result, tests_done + 1, tests_failed, num_tests + 1, skipped_tests); - return; - } - } - tests_done++; - } - } - - /* tear down */ - if (test_types[tt]->tear_down_test_type != NULL) { - test_types[tt]->tear_down_test_type((uintptr_t)e); - } - } - - /* and we're done */ - sel4test_stop_tests(SUCCESS, tests_done, tests_failed, num_tests + 1, skipped_tests); -} - -void *main_continued(void *arg UNUSED) -{ - - /* elf region data */ - int num_elf_regions; - sel4utils_elf_region_t elf_regions[MAX_REGIONS]; - - printf("=== main_continued started ===\n"); - printf("env.vspace pointer: %p\n", &env.vspace); - printf("env.vka pointer: %p\n", &env.vka); - printf("env.vspace content check:\n"); - printf(" env.vspace.data: %p\n", env.vspace.data); - printf(" env.vspace.new_pages: %p\n", env.vspace.new_pages); - printf(" env.vspace.reserve_range_at: %p\n", env.vspace.reserve_range_at); - printf("Validating vspace structure...\n"); - if (env.vspace.data == NULL) { - printf("ERROR: env.vspace.data is NULL!\n"); - } - if (env.vspace.new_pages == NULL) { - printf("ERROR: env.vspace.new_pages function pointer is NULL!\n"); - } - if (env.vspace.reserve_range_at == NULL) { - printf("ERROR: env.vspace.reserve_range_at function pointer is NULL!\n"); - } - - unsigned long elf_size; - unsigned long cpio_len = _cpio_archive_end - _cpio_archive; - const void *elf_file = cpio_get_file(_cpio_archive, cpio_len, TESTS_APP, &elf_size); - ZF_LOGF_IF(elf_file == NULL, "Error: failed to lookup ELF file"); - int status = elf_newFile(elf_file, elf_size, &tests_elf); - ZF_LOGF_IF(status, "Error: invalid ELF file"); - - /* Print welcome banner. */ - printf("\n"); - printf("seL4 Test\n"); - printf("=========\n"); - printf("\n"); - - int error; - - /* allocate a piece of device untyped memory for the frame tests, - * note that spike doesn't have any device untypes so the tests that require device untypes are turned off */ - if (!config_set(CONFIG_PLAT_SPIKE)) { - bool allocated = false; - int untyped_count = simple_get_untyped_count(&env.simple); - for (int i = 0; i < untyped_count; i++) { - bool device = false; - uintptr_t ut_paddr = 0; - size_t ut_size_bits = 0; - seL4_CPtr ut_cptr = simple_get_nth_untyped(&env.simple, i, &ut_size_bits, &ut_paddr, &device); - if (device) { - error = vka_alloc_frame_at(&env.vka, seL4_PageBits, ut_paddr, &env.device_obj); - if (!error) { - allocated = true; - /* we've allocated a single device frame and that's all we need */ - break; - } - } - } - ZF_LOGF_IF(allocated == false, "Failed to allocate a device frame for the frame tests"); - } - - /* allocate lots of untyped memory for tests to use */ - printf("About to populate untypeds...\n"); - env.num_untypeds = populate_untypeds(untypeds); - env.untypeds = untypeds; - printf("Untypeds populated: %u\n", env.num_untypeds); - - /* create a frame that will act as the init data, we can then map that - * in to target processes */ - printf("About to allocate init data frame, vspace at: %p\n", &env.vspace); - printf("Before vspace_new_pages call:\n"); - printf(" env.vspace.data: %p\n", env.vspace.data); - printf(" env.vspace.new_pages: %p\n", env.vspace.new_pages); - printf(" Calling vspace_new_pages(&env.vspace=%p, seL4_AllRights, 1, PAGE_BITS_4K)\n", &env.vspace); - - if (&env.vspace == NULL) { - printf("ERROR: env.vspace is NULL!\n"); - ZF_LOGF("env.vspace is NULL!"); - } - if (env.vspace.data == NULL) { - printf("ERROR: env.vspace.data is NULL before vspace_new_pages!\n"); - ZF_LOGF("env.vspace.data is NULL before vspace_new_pages!"); - } - - env.init = (test_init_data_t *) vspace_new_pages(&env.vspace, seL4_AllRights, 1, PAGE_BITS_4K); - assert(env.init != NULL); - printf("Init data frame allocated at: %p\n", env.init); - - /* copy the untyped size bits list across to the init frame */ - memcpy(env.init->untyped_size_bits_list, untyped_size_bits_list, sizeof(uint8_t) * env.num_untypeds); - - /* parse elf region data about the test image to pass to the tests app */ - num_elf_regions = sel4utils_elf_num_regions(&tests_elf); - assert(num_elf_regions <= MAX_REGIONS); - sel4utils_elf_reserve(NULL, &tests_elf, elf_regions); - - /* copy the region list for the process to clone itself */ - memcpy(env.init->elf_regions, elf_regions, sizeof(sel4utils_elf_region_t) * num_elf_regions); - env.init->num_elf_regions = num_elf_regions; - - /* setup init data that won't change test-to-test */ - env.init->priority = seL4_MaxPrio - 1; - if (plat_init) { - plat_init(&env); - } + printf("Press any key on the serial console to continue...\n"); + fflush(stdout); - /* Allocate a reply object for the RT kernel. */ - if (config_set(CONFIG_KERNEL_MCS)) { - error = vka_alloc_reply(&env.vka, &env.reply); - ZF_LOGF_IF(error, "Failed to allocate reply"); + int ch = EOF; + while ((ch = ps_cdev_getchar(dev)) == EOF) { + /* busy-wait until input is available */ } - /* now run the tests */ - sel4test_run_tests(&env); - - return NULL; -} - -/* Note that the following globals are place here because it is not expected that - * this function be refactored out of sel4test-driver in its current form. */ -/* Number of objects to track allocation of. Currently all serial devices are - * initialised with a single Frame object. Future devices may need more than 1. - */ -#define NUM_ALLOC_AT_TO_TRACK 1 -/* Static global to store the original vka_utspace_alloc_at function. It - * isn't expected for this to dynamically change after initialisation.*/ -static vka_utspace_alloc_at_fn vka_utspace_alloc_at_base; -/* State that serial_utspace_alloc_at_fn uses to determine whether to cache - * allocations. It is intended that this flag gets set before the serial device - * is initialised and then unset afterwards. */ -static bool serial_utspace_record = false; - -typedef struct uspace_alloc_at_args { - uintptr_t paddr; - seL4_Word type; - seL4_Word size_bits; - cspacepath_t dest; -} uspace_alloc_at_args_t; -/* This instance of vka_utspace_alloc_at_fn will keep a record of allocations up - * to NUM_ALLOC_AT_TO_TRACK while serial_utspace_record is set. When serial_utspace_record - * is unset, any allocations matching recorded allocations will instead copy the cap - * that was originally allocated. These subsequent allocations cannot be freed using - * vka_utspace_free and instead the caps would have to be manually deleted. - * Freeing these objects via vka_utspace_free would require also wrapping that function.*/ -static int serial_utspace_alloc_at_fn(void *data, const cspacepath_t *dest, seL4_Word type, seL4_Word size_bits, - uintptr_t paddr, seL4_Word *cookie) -{ - static uspace_alloc_at_args_t args_prev[NUM_ALLOC_AT_TO_TRACK] = {}; - static size_t num_alloc = 0; - - ZF_LOGF_IF(!vka_utspace_alloc_at_base, "vka_utspace_alloc_at_base not initialised."); - if (!serial_utspace_record) { - for (int i = 0; i < num_alloc; i++) { - if (paddr == args_prev[i].paddr && - type == args_prev[i].type && - size_bits == args_prev[i].size_bits) { - return vka_cnode_copy(dest, &args_prev[i].dest, seL4_AllRights); - } - } - return vka_utspace_alloc_at_base(data, dest, type, size_bits, paddr, cookie); + if (ch >= 32 && ch < 127) { + printf("Received '%c' (0x%x)\n", ch, ch); } else { - ZF_LOGF_IF(num_alloc >= NUM_ALLOC_AT_TO_TRACK, "Trying to allocate too many utspace objects"); - int ret = vka_utspace_alloc_at_base(data, dest, type, size_bits, paddr, cookie); - if (ret) { - return ret; - } - uspace_alloc_at_args_t a = {.paddr = paddr, .type = type, .size_bits = size_bits, .dest = *dest}; - args_prev[num_alloc] = a; - num_alloc++; - return ret; - + // printf("Received byte 0x%x\n", ch & 0xff); } } -static ps_irq_register_fn_t irq_register_fn_copy; - -static irq_id_t sel4test_timer_irq_register(UNUSED void *cookie, ps_irq_t irq, irq_callback_fn_t callback, - void *callback_data) -{ - static int num_timer_irqs = 0; - - int error; - - ZF_LOGF_IF(!callback, "Passed in a NULL callback"); - - ZF_LOGF_IF(num_timer_irqs >= MAX_TIMER_IRQS, "Trying to register too many timer IRQs"); - - /* Allocate the IRQ */ - error = sel4platsupport_copy_irq_cap(&env.vka, &env.simple, &irq, - &env.timer_irqs[num_timer_irqs].handler_path); - ZF_LOGF_IF(error, "Failed to allocate IRQ handler"); - - /* Allocate the root notifitcation if we haven't already done so */ - if (env.timer_notification.cptr == seL4_CapNull) { - error = vka_alloc_notification(&env.vka, &env.timer_notification); - ZF_LOGF_IF(error, "Failed to allocate notification object"); - } - - /* Mint a notification for the IRQ handler to pair with */ - error = vka_cspace_alloc_path(&env.vka, &env.badged_timer_notifications[num_timer_irqs]); - ZF_LOGF_IF(error, "Failed to allocate path for the badged notification"); - cspacepath_t root_notification_path = {0}; - vka_cspace_make_path(&env.vka, env.timer_notification.cptr, &root_notification_path); - error = vka_cnode_mint(&env.badged_timer_notifications[num_timer_irqs], &root_notification_path, - seL4_AllRights, BIT(num_timer_irqs)); - ZF_LOGF_IF(error, "Failed to mint notification for timer"); - - /* Pair the notification and the handler */ - error = seL4_IRQHandler_SetNotification(env.timer_irqs[num_timer_irqs].handler_path.capPtr, - env.badged_timer_notifications[num_timer_irqs].capPtr); - ZF_LOGF_IF(error, "Failed to pair the notification and handler together"); - - /* Ack the handler so interrupts can come in */ - error = seL4_IRQHandler_Ack(env.timer_irqs[num_timer_irqs].handler_path.capPtr); - ZF_LOGF_IF(error, "Failed to ack the IRQ handler"); - - /* Fill out information about the callbacks */ - env.timer_cbs[num_timer_irqs].callback = callback; - env.timer_cbs[num_timer_irqs].callback_data = callback_data; - - return num_timer_irqs++; -} - -/* When the root task exists, it should simply suspend itself */ -static void sel4test_exit(int code) -{ - seL4_TCB_Suspend(seL4_CapInitThreadTCB); -} - int main(void) { /* Set exit handler */ @@ -739,7 +88,6 @@ int main(void) int error; seL4_BootInfo *info = platsupport_get_bootinfo(); - ZF_LOGI("=== seL4 Test Driver Starting on Phytium Pi ==="); ZF_LOGI("BootInfo pointer: %p", info); if (info) { ZF_LOGI("Initial BootInfo - nodeID: %u, numNodes: %u, numIOPTLevels: %u", @@ -815,7 +163,7 @@ int main(void) /* switch to a bigger, safer stack with a guard page * before starting the tests */ - printf("Switching to a safer, bigger stack... "); + printf("Switching to a safer, bigger stack...\n"); fflush(stdout); void *res; @@ -826,5 +174,56 @@ int main(void) test_assert_fatal(res == 0); ZF_LOGI("=== seL4 Test Driver completed ==="); + wait_for_uart_keypress(); + + printf("----------------ROOTSERVER--------------------\n"); + seL4_Word* Ptr2ShmemCommBuff= (seL4_Word*)seL4_GetIPCBuffer(); + unsigned long long *vaddrs=(unsigned long long *)*Ptr2ShmemCommBuff; + + // 打印三个共享内存区域的虚拟地址 + printf("Shmem Comm VAddrs:\n"); + printf(" ROOT_Q VADDR: 0x%llx\n", vaddrs[0]); // [0] = ROOT_Q + printf(" SEL4_Q VADDR: 0x%llx\n", vaddrs[1]); // [1] = SEL4_Q + printf(" DATA VADDR: 0x%llx\n", vaddrs[2]); // [2] = DATA + printf("--------------------------------------------\n"); + // === 新增:边界访问测试(只测合法范围,避免 crash)=== + volatile char *root_q = (char*)vaddrs[0]; + volatile char *sel4_q = (char*)vaddrs[1]; + volatile char *data = (char*)vaddrs[2]; + + // 测试 ROOT_Q: 4KB → offset 0 和 4095 + root_q[0] = 'R'; + root_q[4095] = 'r'; + printf("ROOT_Q boundary test: OK\n"); + + // 测试 SEL4_Q: 4KB → offset 0 和 4095 + sel4_q[0] = 'S'; + sel4_q[4095] = 's'; + printf("SEL4_Q boundary test: OK\n"); + + // 测试 DATA: 4MB → offset 0 和 0x3FFFFF (4MB - 1) + data[0] = 'D'; + data[0x3FFFFF] = 'd'; // 4MB - 1 = 0x400000 - 1 = 0x3FFFFF + printf("DATA boundary test: OK\n"); + // === 边界测试结束 === + printRawString((char*)(vaddrs[1])); + + wait_for_uart_keypress(); + + + printf("----------------MKUSERLIBS--------------------\n"); + test_malloc_free(&env); + wait_for_uart_keypress(); + test_immfs(&env); + + /* Test worker pool */ + wait_for_uart_keypress(); + test_wpool(&env); + + /* Test remote worker pool load info */ + wait_for_uart_keypress(); + test_wpool_remote(&env, (void*)vaddrs[2]); + + return 0; } diff --git a/projects/sel4test/apps/sel4test-driver/src/malloc_stub.c b/projects/sel4test/apps/sel4test-driver/src/malloc_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..b4a2a35c547d00da1ef29d37e4685c3288c11ae8 --- /dev/null +++ b/projects/sel4test/apps/sel4test-driver/src/malloc_stub.c @@ -0,0 +1,387 @@ +/* Mixed allocator: small allocations served from local static slab pools + * (array-based), large allocations served by mapping pages using the vspace + * (via vspace_new_pages). This implementation keeps slab storage in static + * arrays (no dynamic memory) and records page-backed allocations so they + * can be unmapped on free. */ + +#include +#include +#include +#include +#include + +#include "test.h" /* driver_env_t */ +#include +#include + + +#define MAX_SMALL_ALLOC 256 +#define SLOTS_PER_CLASS 64 + +/* slab classes */ +static const size_t slab_sizes[] = {16, 32, 64, 128, 256}; +#define NSLAB (sizeof(slab_sizes) / sizeof(slab_sizes[0])) + +/* static slab storage: NSLAB classes x SLOTS_PER_CLASS slots x MAX_SMALL_ALLOC bytes */ +static uint8_t slab_storage[NSLAB][SLOTS_PER_CLASS][MAX_SMALL_ALLOC]; +/* per-class free stacks (indices) */ +static int16_t slab_free_stack_top[NSLAB]; +static int16_t slab_free_stack[NSLAB][SLOTS_PER_CLASS]; + +/* page-backed allocation records */ +#define MAX_PAGE_ALLOCS 128 +typedef struct page_alloc_rec { + void *vaddr; + size_t num_pages; + size_t page_bits; +} page_alloc_rec_t; +static page_alloc_rec_t page_allocs[MAX_PAGE_ALLOCS]; +static int page_allocs_count = 0; + +/* allocator env */ +static driver_env_t alloc_env = NULL; +static int alloc_initted = 0; + +/* Initialize allocator (must be called from test) */ +void xmalloc_init(driver_env_t env) +{ + if (alloc_initted) return; + /* allow slab-only mode when env == NULL (no page-backed allocations) */ + alloc_env = env; + + /* initialise slab free stacks */ + for (int c = 0; c < (int)NSLAB; c++) { + slab_free_stack_top[c] = 0; + for (int i = 0; i < SLOTS_PER_CLASS; i++) { + slab_free_stack[c][i] = SLOTS_PER_CLASS - 1 - i; /* fill with indices */ + } + } + + page_allocs_count = 0; + alloc_initted = 1; +} + +/* helper: pick slab class for size, or -1 if too large */ +static int slab_class_for_size(size_t size) +{ + for (int i = 0; i < (int)NSLAB; i++) { + if (size <= slab_sizes[i]) return i; + } + return -1; +} + +/* allocate pages via vspace_new_pages; record for free */ +static void *alloc_pages(size_t num_pages, int hugepage) +{ + /* page allocation requires a valid env; return NULL if not available */ + if (!alloc_initted || !alloc_env) return NULL; + if (num_pages == 0) return NULL; + size_t page_bits = hugepage ? seL4_LargePageBits : seL4_PageBits; + void *vaddr = vspace_new_pages(&alloc_env->vspace, seL4_AllRights, num_pages, page_bits); + if (!vaddr) return NULL; + if (page_allocs_count < MAX_PAGE_ALLOCS) { + page_allocs[page_allocs_count].vaddr = vaddr; + page_allocs[page_allocs_count].num_pages = num_pages; + page_allocs[page_allocs_count].page_bits = page_bits; + page_allocs_count++; + } + return vaddr; +} + +/* free page-backed region (unmap + let vspace free the frames) */ +static void free_pages(void *ptr) +{ + if (!alloc_initted || !alloc_env || ptr == NULL) return; + for (int i = 0; i < page_allocs_count; i++) { + if (page_allocs[i].vaddr == ptr) { + vspace_unmap_pages(&alloc_env->vspace, ptr, page_allocs[i].num_pages, page_allocs[i].page_bits, VSPACE_FREE); + /* compact array */ + page_allocs[i] = page_allocs[page_allocs_count - 1]; + page_allocs_count--; + return; + } + } +} + +/* xmalloc: slab for small sizes, pages for large sizes */ +void *xmalloc(size_t size) +{ + if (size == 0) return NULL; + if (!alloc_initted) return NULL; + + if (size <= MAX_SMALL_ALLOC) { + int cls = slab_class_for_size(size); + if (cls >= 0) { + /* have a slab class */ + if (slab_free_stack_top[cls] < SLOTS_PER_CLASS) { + int idx = slab_free_stack[cls][slab_free_stack_top[cls]++]; + return (void *)&slab_storage[cls][idx][0]; + } + /* no free slot in this class; fallthrough to page alloc */ + } + } + + /* page-based allocation */ + int use_huge_pages = size >= (1 << seL4_LargePageBits); + size_t page_size = use_huge_pages ? (1 << seL4_LargePageBits) : (1 << seL4_PageBits); + size_t num_pages = (size + page_size - 1) / page_size; + void *v = alloc_pages(num_pages, use_huge_pages); + return v; +} + +/* xfree: return to slab pool if pointer is within slab arrays, otherwise free pages */ +void xfree(void *ptr) +{ + if (!ptr || !alloc_initted) return; + + /* check slab pools */ + for (int c = 0; c < (int)NSLAB; c++) { + uint8_t *base = (uint8_t *)&slab_storage[c][0][0]; + size_t class_block = SLOTS_PER_CLASS * MAX_SMALL_ALLOC; + if ((uint8_t *)ptr >= base && (uint8_t *)ptr < base + class_block) { + size_t offset = (uint8_t *)ptr - base; + int idx = offset / MAX_SMALL_ALLOC; + /* push back */ + if (slab_free_stack_top[c] > 0) { + slab_free_stack[c][--slab_free_stack_top[c]] = idx; + } else { + /* stack is full, shouldn't happen */ + } + return; + } + } + + /* otherwise must be page-backed allocation; free if we recorded it */ + free_pages(ptr); +} + +/* Is a usable time source available? */ +static inline int time_source_available(void) +{ +#ifdef CONFIG_EXPORT_PMU_USER + return 1; /* PMU is user-accessible */ +#else + return alloc_env && alloc_env->ltimer.get_time != NULL; +#endif +} + +/* Timestamp helper: prefer PMU cycles when exported to user, otherwise ltimer */ +static inline uint64_t get_cycles(void) +{ +#ifdef CONFIG_EXPORT_PMU_USER + static int bench_started = 0; + if (!bench_started) { + sel4bench_init(); + bench_started = 1; + } + return (uint64_t)sel4bench_get_cycle_count(); +#else + uint64_t t = 0; + if (alloc_env && alloc_env->ltimer.get_time && alloc_env->ltimer.get_time(alloc_env->ltimer.data, &t) == 0) { + return t; /* typically nanoseconds */ + } + /* Should not be reached if time_source_available() was checked */ + static uint64_t fallback = 0; + printf("[xmalloc] Warning: get_cycles fallback used\n"); + return ++fallback; +#endif +} + +int test_allocation_performance(driver_env_t env) +{ + printf("[xmalloc] starting allocation performance test\n"); + + if (!time_source_available()) { + printf("[xmalloc] timing source unavailable (no PMU export, no ltimer); skipping perf timings\n"); + return 0; + } + + const size_t total_alloc_size = 32 * 1024 * 1024; /* 32MB */ + const int num_small_pages = total_alloc_size / (4 * 1024); + const int num_huge_pages = total_alloc_size / (2 * 1024 * 1024); + const int batch_size = 128; /* Process in batches to avoid resource exhaustion */ + void *ptrs[batch_size]; + + /* Time 4KB page allocations */ + printf("[xmalloc] getting cycles count before starting...\n"); + uint64_t start_4k = get_cycles(); + for (int i = 0; i < num_small_pages; i += batch_size) { + int current_batch_size = (i + batch_size <= num_small_pages) ? batch_size : num_small_pages - i; + for (int j = 0; j < current_batch_size; j++) { + ptrs[j] = alloc_pages(1, 0); /* 1 page, not hugepage */ + test_assert(ptrs[j] != NULL); + } + for (int j = 0; j < current_batch_size; j++) { + free_pages(ptrs[j]); + } + } + uint64_t end_4k = get_cycles(); + printf("[xmalloc] 4KB page allocation time: %" PRIu64 " cycles\n", end_4k - start_4k); + + /* Time 2MB page allocations */ + uint64_t start_2m = get_cycles(); + for (int i = 0; i < num_huge_pages; i++) { + ptrs[i] = alloc_pages(1, 1); /* 1 page, hugepage */ + test_assert(ptrs[i] != NULL); + } + uint64_t end_2m = get_cycles(); + for (int i = 0; i < num_huge_pages; i++) { + free_pages(ptrs[i]); + } + printf("[xmalloc] 2MB page allocation time: %" PRIu64 " cycles\n", end_2m - start_2m); + + printf("[xmalloc] allocation performance test done\n"); + return 0; +} + +int test_slab_performance(driver_env_t env) +{ + /* ensure allocator is ready even if called standalone */ + if (!alloc_initted) { + xmalloc_init(env); + } + + printf("[xmalloc] starting slab performance test\n"); + + if (!time_source_available()) { + printf("[xmalloc] timing source unavailable (no PMU export, no ltimer); skipping slab perf timings\n"); + return 0; + } + + const uint64_t cycles_per_second = 1000ULL * 1000ULL * 1000ULL; /* assume 1GHz for throughput estimate */ + const size_t sizes[] = {16, 32, 64, 128, 256}; + const int iterations = 5000000; + const int nsizes = (int)(sizeof(sizes) / sizeof(sizes[0])); + + uint64_t ave_ops_per_sec = 0; + + for (int s = 0; s < nsizes; s++) { + size_t size = sizes[s]; + uint64_t start = get_cycles(); + for (int i = 0; i < iterations; i++) { + void *p = xmalloc(size); + test_assert(p != NULL); + xfree(p); + } + uint64_t end = get_cycles(); + uint64_t delta = end - start; + uint64_t ops_per_sec = delta ? ((uint64_t)iterations * cycles_per_second) / delta : 0; + uint64_t bytes_per_sec = delta ? ((uint64_t)iterations * size * cycles_per_second) / delta : 0; + + printf("[xmalloc] slab size %zu: %" PRIu64 " cycles, %" PRIu64 " ops/s\n", + size, delta, ops_per_sec); + ave_ops_per_sec += ops_per_sec; + } + ave_ops_per_sec /= nsizes; + printf("[xmalloc] average slab ops/s: %" PRIu64 "\n", ave_ops_per_sec); + + printf("[xmalloc] slab performance test done\n"); + return 0; +} + +int test_huge_page_allocation(driver_env_t env) +{ + printf("[xmalloc] testing huge page allocation\n"); + size_t huge_page_size = 1 << seL4_LargePageBits; + void *huge = xmalloc(huge_page_size); + test_assert(huge != NULL); + printf("[xmalloc] allocated huge block at %p\n", huge); + memset(huge, 0xAB, huge_page_size); + for (int i = 0; i < huge_page_size; i++) { + test_assert(((unsigned char *)huge)[i] == 0xAB); + } + xfree(huge); + printf("[xmalloc] freed huge block\n"); + printf("[xmalloc] huge page allocation test done\n"); + return 0; +} + +extern int vka_debug_print_paddr; + +int test_malloc_free(driver_env_t env) +{ + /* initialise allocator heap backed by vspace pages */ + xmalloc_init(env); + printf("[xmalloc] initializing allocator\n"); + wait_for_uart_keypress(); + + /* 1) Exercise slab allocations: allocate many small blocks until slabs + * are exhausted and some allocations fall back to page-backed. */ + printf("[xmalloc] starting slab allocation test\n"); + const int NUM_SMALL = 70; /* should exceed per-class slab slots (default 64) */ + void *small_ptrs[NUM_SMALL]; + for (int i = 0; i < NUM_SMALL; i++) { + small_ptrs[i] = xmalloc(16); + test_assert(small_ptrs[i] != NULL); + memset(small_ptrs[i], 0xA5, 16); + for (int j = 0; j < 16; j++) { + test_assert(((unsigned char *)small_ptrs[i])[j] == 0xA5); + } + } + printf("[xmalloc] slab allocation test done\n"); + + /* free a couple of small slots and reallocate to test reuse */ + printf("[xmalloc] testing reuse\n"); + xfree(small_ptrs[10]); + xfree(small_ptrs[20]); + printf("[xmalloc] freed small block 20\n"); + void *r1 = xmalloc(16); + test_assert(r1 != NULL); + void *r2 = xmalloc(16); + test_assert(r2 != NULL); + printf("[xmalloc] reallocated block 2 at %p\n", r2); + + /* free them */ + xfree(r1); + xfree(r2); + printf("[xmalloc] reuse test done\n"); + wait_for_uart_keypress(); + + /* 2) Allocate a large, page-backed allocation and test read/write */ + printf("[xmalloc] testing large allocation\n"); + void *big = xmalloc(4096); + test_assert(big != NULL); + printf("[xmalloc] allocated large block at %p\n", big); + memset(big, 0x5A, 4096); + for (int i = 0; i < 4096; i++) { + test_assert(((unsigned char *)big)[i] == 0x5A); + } + xfree(big); + printf("[xmalloc] freed large block\n"); + printf("[xmalloc] large allocation test done\n"); + wait_for_uart_keypress(); + + /* 3) test physical address printing */ + printf("[xmalloc] testing physical address management\n"); + vka_debug_print_paddr = 1; + void *ppage = xmalloc(4096); + vka_debug_print_paddr = 0; + printf("[xmalloc] allocated page-backed block at %p\n", ppage); + test_assert(ppage != NULL); + xfree(ppage); + printf("[xmalloc] physical address management test done\n"); + wait_for_uart_keypress(); + + + /* 4) Slab-only performance measurements (small sizes under 512B) */ + test_slab_performance(env); + wait_for_uart_keypress(); + + /* 5) Test huge page allocation */ + test_huge_page_allocation(env); + + /* 6) Test allocation performance */ + test_allocation_performance(env); + + /* 7) Clean up remaining small allocations */ + printf("[xmalloc] cleaning up remaining allocations\n"); + for (int i = 0; i < NUM_SMALL; i++) { + if (small_ptrs[i]) { + xfree(small_ptrs[i]); + } + } + printf("[xmalloc] slab cleanup done\n"); + + printf("[Lib: xmalloc] malloc/free test completed successfully\n"); +} + diff --git a/projects/sel4test/apps/sel4test-driver/src/test.h b/projects/sel4test/apps/sel4test-driver/src/test.h index a2e5d9a4ac092cb06923b5624f7693704644eaf5..40865ceb96759c7471af1d4df51f8991144c4fec 100755 --- a/projects/sel4test/apps/sel4test-driver/src/test.h +++ b/projects/sel4test/apps/sel4test-driver/src/test.h @@ -11,6 +11,8 @@ #include #include +#include + #include #include #include @@ -91,6 +93,9 @@ typedef struct driver_env *driver_env_t; void plat_init(driver_env_t env) WEAK; +/* Expose UART keypress wait helper to other modules */ +void wait_for_uart_keypress(void); + #ifdef CONFIG_TK1_SMMU seL4_SlotRegion arch_copy_iospace_caps_to_process(sel4utils_process_t *process, driver_env_t env); #endif diff --git a/projects/sel4test/apps/sel4test-driver/src/wpool_stub.c b/projects/sel4test/apps/sel4test-driver/src/wpool_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..e218df35be8005b15600cc7982c0f4b7978a4b2c --- /dev/null +++ b/projects/sel4test/apps/sel4test-driver/src/wpool_stub.c @@ -0,0 +1,258 @@ +/* + * Simple worker pool with user-space load balancing. Workers block on + * per-thread endpoints and a dispatcher assigns each incoming job to the + * least-loaded worker. If a caller keeps targeting the same thread (e.g. + * two back-to-back jobs aimed at thread 1), the dispatcher will rebalance + * and hand the second job to an idle worker instead. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test.h" /* driver_env_t */ +#include +#include +#include + +struct worker_ctx { + seL4_CPtr job_ep; + seL4_CPtr completion_ep; + seL4_Word worker_id; +}; + +/* Worker thread entry: arg0=worker_ctx* */ +static int worker_entry(seL4_Word ctx_word, seL4_Word unused1, seL4_Word unused2, seL4_Word unused3) +{ + struct worker_ctx *ctx = (struct worker_ctx *)ctx_word; + seL4_CPtr job_ep = ctx->job_ep; + seL4_CPtr completion_ep = ctx->completion_ep; + seL4_Word worker_id = ctx->worker_id; + + while (1) { + seL4_Word badge = 0; + seL4_MessageInfo_t tag = seL4_Recv(job_ep, &badge); + int len = (int)seL4_MessageInfo_get_length(tag); + if (len == 0) { + /* graceful exit: park thread to avoid returning */ + for (;;) { seL4_Yield(); } + } + seL4_Word job = seL4_GetMR(0); + seL4_Word result = job + 1; + printf("[wpool] worker %ld processed job %ld, result %ld\n", + (long)worker_id, (long)job, (long)result); + + seL4_SetMR(0, result); + seL4_SetMR(1, worker_id); + seL4_SetMR(2, job); + seL4_Send(completion_ep, seL4_MessageInfo_new(0, 0, 0, 3)); + } + return 0; +} + +int test_wpool(driver_env_t env) +{ + const int N_WORKERS = 4; + /* Simulate two jobs intentionally aimed at worker 0 to show balancing */ + const struct { + int job_id; + int preferred_worker; + } jobs[] = { + {0, 0}, + {1, 0}, /* this would overload worker 0 without balancing */ + {2, 1}, + {3, 2}, + {4, 3}, + {5, 0}, + }; + const int N_JOBS = (int)(sizeof(jobs) / sizeof(jobs[0])); + + /* allocate completion endpoint shared by all workers */ + seL4_CPtr completion_ep = vka_alloc_endpoint_leaky(&env->vka); + test_assert(completion_ep != 0); + printf("[wpool] allocated completion endpoint %d\n", (int)completion_ep); + + /* allocate per-worker job endpoints */ + seL4_CPtr worker_eps[N_WORKERS]; + for (int i = 0; i < N_WORKERS; i++) { + worker_eps[i] = vka_alloc_endpoint_leaky(&env->vka); + test_assert(worker_eps[i] != 0); + printf("[wpool] worker %d job endpoint %d\n", i, (int)worker_eps[i]); + } + + /* create workers using sel4utils */ + sel4utils_thread_t threads[N_WORKERS]; + struct worker_ctx ctxs[N_WORKERS]; + + for (int i = 0; i < N_WORKERS; i++) { + ctxs[i].job_ep = worker_eps[i]; + ctxs[i].completion_ep = completion_ep; + ctxs[i].worker_id = (seL4_Word)i; + seL4_CPtr cnode = simple_get_cnode(&env->simple); + seL4_Word data = api_make_guard_skip_word(seL4_WordBits - simple_get_cnode_size_bits(&env->simple)); + sel4utils_thread_config_t cfg = thread_config_default(&env->simple, cnode, data, 0, seL4_MaxPrio - 1); + int err = sel4utils_configure_thread_config(&env->vka, &env->vspace, &env->vspace, cfg, &threads[i]); + test_assert(err == 0); + err = sel4utils_start_thread(&threads[i], (sel4utils_thread_entry_fn)worker_entry, + (void *)&ctxs[i], NULL, 1); + test_assert(err == 0); + } + printf("[wpool] started %d worker threads\n", N_WORKERS); + wait_for_uart_keypress(); + + /* dispatch jobs with a simple least-loaded balancer */ + int in_flight[N_WORKERS]; + memset(in_flight, 0, sizeof(in_flight)); + + int dispatched = 0; + int completed = 0; + while (completed < N_JOBS) { + /* pick the least loaded worker */ + int chosen = 0; + int min_load = in_flight[0]; + for (int w = 1; w < N_WORKERS; w++) { + if (in_flight[w] < min_load) { + min_load = in_flight[w]; + chosen = w; + } + } + + /* seL4_Send is synchronous, so only dispatch to an idle worker. If + * everyone is busy (min_load > 0) or no jobs remain, block waiting + * for a completion to free a worker and to unblock any workers + * stuck sending to the completion endpoint. */ + bool can_dispatch = dispatched < N_JOBS && min_load == 0; + + if (can_dispatch) { + int preferred = jobs[dispatched].preferred_worker; + if (preferred != chosen) { + printf("[wpool] rebalanced job %d from preferred worker %d to worker %d (load %d vs %d)\n", + jobs[dispatched].job_id, preferred, chosen, in_flight[preferred], in_flight[chosen]); + } else { + printf("[wpool] assigning job %d to preferred worker %d (load %d)\n", + jobs[dispatched].job_id, chosen, in_flight[chosen]); + } + + seL4_SetMR(0, jobs[dispatched].job_id); + seL4_Send(worker_eps[chosen], seL4_MessageInfo_new(0, 0, 0, 1)); + in_flight[chosen]++; + dispatched++; + /* loop to allow more dispatching until we need to wait for completion */ + continue; + } + + /* wait for a completion before dispatching more */ + seL4_Word badge = 0; + seL4_MessageInfo_t tag = seL4_Recv(completion_ep, &badge); + test_assert(seL4_MessageInfo_get_length(tag) == 3); + seL4_Word result = seL4_GetMR(0); + seL4_Word worker = seL4_GetMR(1); + seL4_Word job = seL4_GetMR(2); + printf("[wpool] received result %ld for job %ld from worker %ld\n", (long)result, (long)job, (long)worker); + test_assert(result == (seL4_Word)(job + 1)); + in_flight[worker]--; + completed++; + } + + /* shutdown */ + for (int i = 0; i < N_WORKERS; i++) { + seL4_Send(worker_eps[i], seL4_MessageInfo_new(0, 0, 0, 0)); + } + + /* join and destroy threads */ + /* cleanup threads */ + for (int i = 0; i < N_WORKERS; i++) { + sel4utils_clean_up_thread(&env->vka, &env->vspace, &threads[i]); + } + + printf("[Lib: wpool] worker pool test completed successfully\n"); + return sel4test_get_result(); +} + +/* Remote load balancing test: + * Each compute node writes its current CPU load (0-100) to a shared + * memory region. + */ +int test_wpool_remote(driver_env_t env, void *data_vaddr) +{ + uint64_t seed = 0; +#ifdef CONFIG_EXPORT_PMU_USER + sel4bench_init(); + seed = sel4bench_get_cycle_count(); +#else + if (env->ltimer.get_time) { + if (env->ltimer.get_time(env->ltimer.data, &seed) != 0) { + seed = 0; + } + } +#endif + if (seed == 0) { + printf("[wpool] Warning: using fixed seed for random generation\n"); + seed = 123456789; + } + srand((unsigned int)seed); + + const size_t OFFSET_2MB = 2 * 1024 * 1024; + volatile uint64_t *load_info = (volatile uint64_t *)((char *)data_vaddr + OFFSET_2MB); + const int NUM_NODES = 3; + const int CPU_LOW = 29; + const int CPU_HIGH = 72; + + /* Generate random load info within a fixed range [0, 100] */ + for (int i = 0; i < NUM_NODES; i++) { + load_info[i] = (uint64_t)(rand() % (CPU_HIGH - CPU_LOW + 1)) + CPU_LOW; + } + + printf("[wpool] Remote CPU load info at %p (offset 0x%lx):\n", load_info, (long)OFFSET_2MB); + for (int i = 0; i < NUM_NODES; i++) { + printf(" Compute Node %d load: %llu\n", i, (unsigned long long)load_info[i]); + } + printf("[wpool] Concatenated load info: "); + for (int i = 0; i < NUM_NODES; i++) { + printf("%llu ", (unsigned long long)load_info[i]); + } + printf("\n"); + wait_for_uart_keypress(); + + /* Select the least loaded node */ + int least_loaded_node = -1; + uint64_t min_load = CPU_HIGH + 1; + for (int i = 0; i < NUM_NODES; i++) { + if (load_info[i] < min_load) { + min_load = load_info[i]; + least_loaded_node = i; + } + } + + if (least_loaded_node != -1) { + printf("[wpool] Selected least loaded node: %d with load %llu\n", + least_loaded_node, (unsigned long long)min_load); + + /* Push a remote call to the least loaded node */ + const size_t OFFSET_3MB = 3 * 1024 * 1024; + struct remote_call { + int target_node; + int job_param; + }; + volatile struct remote_call *call_info = + (volatile struct remote_call *)((char *)data_vaddr + OFFSET_3MB); + + call_info->target_node = least_loaded_node; + call_info->job_param = rand() % 1000; /* simulated job parameter */ + + printf("[wpool] Pushing remote call to node %d with job param %d at address %p\n", + call_info->target_node, call_info->job_param, call_info); + } else { + printf("[wpool] Could not determine least loaded node.\n"); + } + + printf("[Lib: wpool] remote worker pool load info test completed successfully\n"); + + return 0; +} + diff --git a/projects/sel4test/apps/sel4test-tests/CMakeLists.txt b/projects/sel4test/apps/sel4test-tests/CMakeLists.txt index cb3b67727624f55c98d4e998ec5f11d89eb873f9..b3fc66ad37236ac499e8ca6d09a8625582f437f2 100755 --- a/projects/sel4test/apps/sel4test-tests/CMakeLists.txt +++ b/projects/sel4test/apps/sel4test-tests/CMakeLists.txt @@ -27,6 +27,11 @@ file( src/arch/${KernelArch}/tests/*.S ) +# Ensure malloc stub is included even if added after initial configure +list(APPEND deps src/malloc_stub.c) +# Ensure minikv is included even if added after initial configure +list(APPEND deps src/minikv.c) + add_executable(sel4test-tests EXCLUDE_FROM_ALL ${deps}) # special handling for "arm_hyp", it's really "aarch32" set(_inc_folder_KernelSel4Arch "${KernelSel4Arch}") diff --git a/projects/sel4test/apps/sel4test-tests/src/main.c b/projects/sel4test/apps/sel4test-tests/src/main.c index a6e3cb2ddccde122348a5f44bbe3f49f148a14b3..776f6ebb7fc25e2c6e3e373a9d9d8049534af930 100755 --- a/projects/sel4test/apps/sel4test-tests/src/main.c +++ b/projects/sel4test/apps/sel4test-tests/src/main.c @@ -231,14 +231,14 @@ int main(int argc, char **argv) sel4test_reset(); test_result_t result = SUCCESS; if (test) { - printf("Running test %s (%s)\n", test->name, test->description); + printf("[seL4-Libs] Running test %s (%s)\n", test->name, test->description); result = test->function((uintptr_t)&env); } else { result = FAILURE; ZF_LOGF("Cannot find test %s", init_data->name); } - printf("Test %s %s\n", init_data->name, result == SUCCESS ? "passed" : "failed"); + printf("[seL4-Libs] Test %s %s\n", init_data->name, result == SUCCESS ? "passed" : "failed"); /* send our result back */ seL4_MessageInfo_t info = seL4_MessageInfo_new(seL4_Fault_NullFault, 0, 0, 1); seL4_SetMR(0, result); diff --git a/projects/sel4test/apps/sel4test-tests/src/malloc_stub.c b/projects/sel4test/apps/sel4test-tests/src/malloc_stub.c new file mode 100644 index 0000000000000000000000000000000000000000..eacc4319472effc50214ad72376bd3350192dae4 --- /dev/null +++ b/projects/sel4test/apps/sel4test-tests/src/malloc_stub.c @@ -0,0 +1,152 @@ +/* +/* Mixed allocator: small allocations served from local static slab pools + * (array-based), large allocations served by mapping pages using the vspace + * (via vspace_new_pages). This implementation keeps slab storage in static + * arrays (no dynamic memory) and records page-backed allocations so they + * can be unmapped on free. */ + +#include +#include +#include +#include + +#include "helpers.h" /* env_t */ +#include + +#define MAX_SMALL_ALLOC 256 +#define SLOTS_PER_CLASS 64 + +/* slab classes */ +static const size_t slab_sizes[] = {16, 32, 64, 128, 256}; +#define NSLAB (sizeof(slab_sizes) / sizeof(slab_sizes[0])) + +/* static slab storage: NSLAB classes x SLOTS_PER_CLASS slots x MAX_SMALL_ALLOC bytes */ +static uint8_t slab_storage[NSLAB][SLOTS_PER_CLASS][MAX_SMALL_ALLOC]; +/* per-class free stacks (indices) */ +static int16_t slab_free_stack_top[NSLAB]; +static int16_t slab_free_stack[NSLAB][SLOTS_PER_CLASS]; + +/* page-backed allocation records */ +#define MAX_PAGE_ALLOCS 128 +typedef struct page_alloc_rec { + void *vaddr; + size_t num_pages; +} page_alloc_rec_t; +static page_alloc_rec_t page_allocs[MAX_PAGE_ALLOCS]; +static int page_allocs_count = 0; + +/* allocator env */ +static env_t alloc_env = NULL; +static int alloc_initted = 0; + +/* Initialize allocator (must be called from test) */ +void xmalloc_init(env_t env) +{ + if (alloc_initted) return; + /* allow slab-only mode when env == NULL (no page-backed allocations) */ + alloc_env = env; + + /* initialise slab free stacks */ + for (int c = 0; c < (int)NSLAB; c++) { + slab_free_stack_top[c] = 0; + for (int i = 0; i < SLOTS_PER_CLASS; i++) { + slab_free_stack[c][i] = SLOTS_PER_CLASS - 1 - i; /* fill with indices */ + } + } + + page_allocs_count = 0; + alloc_initted = 1; +} + +/* helper: pick slab class for size, or -1 if too large */ +static int slab_class_for_size(size_t size) +{ + for (int i = 0; i < (int)NSLAB; i++) { + if (size <= slab_sizes[i]) return i; + } + return -1; +} + +/* allocate pages via vspace_new_pages; record for free */ +static void *alloc_pages(size_t num_pages) +{ + /* page allocation requires a valid env; return NULL if not available */ + if (!alloc_initted || !alloc_env) return NULL; + if (num_pages == 0) return NULL; + void *vaddr = vspace_new_pages(&alloc_env->vspace, seL4_AllRights, num_pages, seL4_PageBits); + if (!vaddr) return NULL; + if (page_allocs_count < MAX_PAGE_ALLOCS) { + page_allocs[page_allocs_count].vaddr = vaddr; + page_allocs[page_allocs_count].num_pages = num_pages; + page_allocs_count++; + } + return vaddr; +} + +/* free page-backed region (unmap + let vspace free the frames) */ +static void free_pages(void *ptr) +{ + if (!alloc_initted || !alloc_env || ptr == NULL) return; + for (int i = 0; i < page_allocs_count; i++) { + if (page_allocs[i].vaddr == ptr) { + vspace_unmap_pages(&alloc_env->vspace, ptr, page_allocs[i].num_pages, seL4_PageBits, VSPACE_FREE); + /* compact array */ + page_allocs[i] = page_allocs[page_allocs_count - 1]; + page_allocs_count--; + return; + } + } +} + +/* xmalloc: slab for small sizes, pages for large sizes */ +void *xmalloc(size_t size) +{ + if (size == 0) return NULL; + if (!alloc_initted) return NULL; + + if (size <= MAX_SMALL_ALLOC) { + int cls = slab_class_for_size(size); + if (cls >= 0) { + /* have a slab class */ + if (slab_free_stack_top[cls] < SLOTS_PER_CLASS) { + int idx = slab_free_stack[cls][slab_free_stack_top[cls]++]; + return (void *)&slab_storage[cls][idx][0]; + } + /* no free slot in this class; fallthrough to page alloc */ + } + } + + /* page-based allocation */ + size_t page_size = (size_t)1 << seL4_PageBits; + size_t num_pages = (size + page_size - 1) / page_size; + void *v = alloc_pages(num_pages); + return v; +} + +/* xfree: return to slab pool if pointer is within slab arrays, otherwise free pages */ +void xfree(void *ptr) +{ + if (!ptr || !alloc_initted) return; + + /* check slab pools */ + for (int c = 0; c < (int)NSLAB; c++) { + uint8_t *base = (uint8_t *)&slab_storage[c][0][0]; + size_t class_block = SLOTS_PER_CLASS * MAX_SMALL_ALLOC; + if ((uint8_t *)ptr >= base && (uint8_t *)ptr < base + class_block) { + size_t offset = (uint8_t *)ptr - base; + int idx = offset / MAX_SMALL_ALLOC; + /* push back */ + if (slab_free_stack_top[c] > 0) { + slab_free_stack[c][--slab_free_stack_top[c]] = idx; + } else { + /* stack is full, shouldn't happen */ + } + return; + } + } + + /* otherwise must be page-backed allocation; free if we recorded it */ + free_pages(ptr); +} + + diff --git a/projects/sel4test/apps/sel4test-tests/src/minikv.c b/projects/sel4test/apps/sel4test-tests/src/minikv.c new file mode 100644 index 0000000000000000000000000000000000000000..d748e4aa710dcdc12a640daf1de6b0a2188be523 --- /dev/null +++ b/projects/sel4test/apps/sel4test-tests/src/minikv.c @@ -0,0 +1,120 @@ +/* Minimal key-value store implementation using chained hash table. + * Uses xmalloc/xfree for allocation; call minikv_init(env) before use + * so the underlying allocator (xmalloc) can be initialised. + */ +#include "minikv.h" +#include +#include + +/* forward declarations of test allocator functions (implemented in malloc_stub.c) */ +void xmalloc_init(env_t env); +void *xmalloc(size_t size); +void xfree(void *ptr); + +/* simple djb2 string hash */ +static unsigned long hash_str(const char *str) +{ + unsigned long hash = 5381; + int c; + while ((c = *str++)) + hash = ((hash << 5) + hash) + (unsigned char)c; + return hash; +} + +/* table parameters */ +#define MINIKV_BUCKETS 1024 + +typedef struct kv_node { + char *key; + void *value; + struct kv_node *next; +} kv_node_t; + +static kv_node_t *buckets[MINIKV_BUCKETS]; +static int initted = 0; + +int minikv_init(env_t env) +{ + if (initted) return 0; + if (env) { + xmalloc_init(env); + } + for (int i = 0; i < MINIKV_BUCKETS; i++) buckets[i] = NULL; + initted = 1; + return 0; +} + +int minikv_set(const char *key, void *value) +{ + if (!initted || !key) return -1; + unsigned long h = hash_str(key) % MINIKV_BUCKETS; + kv_node_t *n = buckets[h]; + while (n) { + if (strcmp(n->key, key) == 0) { + n->value = value; /* replace */ + return 0; + } + n = n->next; + } + /* not found -> insert */ + kv_node_t *node = (kv_node_t *)xmalloc(sizeof(kv_node_t)); + if (!node) return -1; + size_t klen = strlen(key) + 1; + node->key = (char *)xmalloc(klen); + if (!node->key) { + xfree(node); + return -1; + } + memcpy(node->key, key, klen); + node->value = value; + node->next = buckets[h]; + buckets[h] = node; + return 0; +} + +void *minikv_get(const char *key) +{ + if (!initted || !key) return NULL; + unsigned long h = hash_str(key) % MINIKV_BUCKETS; + kv_node_t *n = buckets[h]; + while (n) { + if (strcmp(n->key, key) == 0) return n->value; + n = n->next; + } + return NULL; +} + +int minikv_delete(const char *key) +{ + if (!initted || !key) return -1; + unsigned long h = hash_str(key) % MINIKV_BUCKETS; + kv_node_t *n = buckets[h]; + kv_node_t *prev = NULL; + while (n) { + if (strcmp(n->key, key) == 0) { + if (prev) prev->next = n->next; else buckets[h] = n->next; + xfree(n->key); + xfree(n); + return 0; + } + prev = n; + n = n->next; + } + return -1; +} + +void minikv_destroy(void) +{ + if (!initted) return; + for (int i = 0; i < MINIKV_BUCKETS; i++) { + kv_node_t *n = buckets[i]; + while (n) { + kv_node_t *next = n->next; + xfree(n->key); + xfree(n); + n = next; + } + buckets[i] = NULL; + } + initted = 0; +} diff --git a/projects/sel4test/apps/sel4test-tests/src/minikv.h b/projects/sel4test/apps/sel4test-tests/src/minikv.h new file mode 100644 index 0000000000000000000000000000000000000000..fe629cc06cd553917a78218e78aa284bf8c70c13 --- /dev/null +++ b/projects/sel4test/apps/sel4test-tests/src/minikv.h @@ -0,0 +1,15 @@ +/* Minimal hashtable-based key-value store (minikv) + * Keys: null-terminated strings (copied) + * Values: void* (opaque pointer stored as-is) + */ +#pragma once + +#include +#include +#include "helpers.h" + +int minikv_init(env_t env); +int minikv_set(const char *key, void *value); +void *minikv_get(const char *key); +int minikv_delete(const char *key); +void minikv_destroy(void); diff --git a/projects/sel4test/apps/sel4test-tests/src/tests/scheduler.c b/projects/sel4test/apps/sel4test-tests/src/tests/scheduler.c index a4f05eb581ca3dc08eebf7384142c2e492e2fc04..4d445c84c1b91b7820cfee73d5fe2e65cc45687d 100755 --- a/projects/sel4test/apps/sel4test-tests/src/tests/scheduler.c +++ b/projects/sel4test/apps/sel4test-tests/src/tests/scheduler.c @@ -312,7 +312,7 @@ static int test_all_priorities(struct env *env) return sel4test_get_result(); } -DEFINE_TEST(SCHED0004, "Test threads at all priorities", test_all_priorities, true) +DEFINE_TESTQ(SCHED0004, "Test threads at all priorities", test_all_priorities, true) #define SCHED0005_HIGHEST_PRIO (seL4_MaxPrio - 2) /* @@ -424,7 +424,7 @@ static int test_set_priority(struct env *env) cleanup_helper(env, &thread2); return sel4test_get_result(); } -DEFINE_TEST(SCHED0005, "Test set priority", test_set_priority, true) +DEFINE_TESTQ(SCHED0005, "Test set priority", test_set_priority, true) #endif /* @@ -1067,7 +1067,7 @@ int test_one_periodic_thread(env_t env) return sel4test_get_result(); } -DEFINE_TEST(SCHED0012, "Test one periodic thread", test_one_periodic_thread, +DEFINE_TESTQ(SCHED0012, "Test one periodic thread", test_one_periodic_thread, config_set(CONFIG_KERNEL_MCS) &&config_set(CONFIG_HAVE_TIMER)) int diff --git a/projects/sel4test/apps/sel4test-tests/src/tests/vspace.c b/projects/sel4test/apps/sel4test-tests/src/tests/vspace.c index f69a63115fda5d121ddd84bf1b5adf171b4745ce..f95b6a885d8aed650c7b4273384a369e1a216ec0 100755 --- a/projects/sel4test/apps/sel4test-tests/src/tests/vspace.c +++ b/projects/sel4test/apps/sel4test-tests/src/tests/vspace.c @@ -15,6 +15,18 @@ #include "../helpers.h" +/* xmalloc_init provided by malloc_stub.c */ +void xmalloc_init(env_t env); +/* test allocator API */ +void *xmalloc(size_t size); +void xfree(void *ptr); +/* minikv API */ +int minikv_init(env_t env); +int minikv_set(const char *key, void *value); +void *minikv_get(const char *key); +int minikv_delete(const char *key); +void minikv_destroy(void); + static int remote_function(void) { return 42; @@ -118,6 +130,13 @@ test_unmap_after_delete(env_t env) error = seL4_ARM_Page_Map(frame, vspace, map_addr, seL4_AllRights, seL4_ARM_Default_VMAttributes); test_error_eq(error, seL4_NoError); + // test set and get on map_addr address + uint32_t *p = (uint32_t*)0x10000000; + *p = 0xCAFEBABE; + printf("Value = %u\n", *p); + assert(*p == 0xCAFEBABE); + + /* delete the page directory */ vka_cspace_make_path(&env->vka, vspace, &path); seL4_CNode_Delete(path.root, path.capPtr, path.capDepth); @@ -127,7 +146,7 @@ test_unmap_after_delete(env_t env) return sel4test_get_result(); } -DEFINE_TEST(VSPACE0001, "Test unmapping a page after deleting the PD", test_unmap_after_delete, true) +DEFINE_TESTQ(VSPACE0001, "Test unmapping a page after deleting the PD", test_unmap_after_delete, true) #endif /* CONFIG_ARCH_AARCHxx */ static int @@ -265,6 +284,116 @@ static int test_create_asid_pools_and_touch(env_t env) } DEFINE_TEST(VSPACE0006, "Test touching all available ASID pools", test_create_asid_pools_and_touch, true) +#if 1 +static int +test_malloc_free(env_t env) +{ + /* initialise allocator heap backed by vspace pages */ + xmalloc_init(env); + + /* 1) Exercise slab allocations: allocate many small blocks until slabs + * are exhausted and some allocations fall back to page-backed. */ + const int NUM_SMALL = 70; /* should exceed per-class slab slots (default 64) */ + void *small_ptrs[NUM_SMALL]; + for (int i = 0; i < NUM_SMALL; i++) { + small_ptrs[i] = xmalloc(16); + test_assert(small_ptrs[i] != NULL); + memset(small_ptrs[i], 0xA5, 16); + for (int j = 0; j < 16; j++) { + test_assert(((unsigned char *)small_ptrs[i])[j] == 0xA5); + } + } + + /* free a couple of small slots and reallocate to test reuse */ + xfree(small_ptrs[10]); + xfree(small_ptrs[20]); + void *r1 = xmalloc(16); + test_assert(r1 != NULL); + void *r2 = xmalloc(16); + test_assert(r2 != NULL); + /* free them */ + xfree(r1); + xfree(r2); + + /* 2) Allocate a large, page-backed allocation and test read/write */ + void *big = xmalloc(4096); + test_assert(big != NULL); + memset(big, 0x5A, 4096); + for (int i = 0; i < 4096; i++) { + test_assert(((unsigned char *)big)[i] == 0x5A); + } + xfree(big); + + /* 3) Clean up remaining small allocations */ + for (int i = 0; i < NUM_SMALL; i++) { + if (small_ptrs[i]) { + xfree(small_ptrs[i]); + } + } + + return sel4test_get_result(); +} +DEFINE_TESTQ(XMALLOC0001, "Test basic free-list allocator: xmalloc", test_malloc_free, true) +#endif + +static int +test_minikv(env_t env) +{ + /* Ensure allocator is initialised for minikv */ + xmalloc_init(env); + + int rc = minikv_init(env); + test_eq(rc, 0); + + /* allocate some values via the test allocator */ + int *v1 = xmalloc(sizeof(int)); + int *v2 = xmalloc(sizeof(int)); + int *v3 = xmalloc(sizeof(int)); + test_assert(v1 && v2 && v3); + *v1 = 11; + *v2 = 22; + *v3 = 33; + + test_eq(minikv_set("one", v1), 0); + test_eq(minikv_set("two", v2), 0); + test_eq(minikv_set("three", v3), 0); + + int *r = minikv_get("one"); + test_assert(r != NULL); + test_eq(*r, 11); + + r = minikv_get("two"); + test_assert(r != NULL); + test_eq(*r, 22); + + /* Replace an existing key's value and verify replacement */ + int *v2b = xmalloc(sizeof(int)); + test_assert(v2b); + *v2b = 99; + test_eq(minikv_set("two", v2b), 0); + r = minikv_get("two"); + test_assert(r != NULL); + test_eq(*r, 99); + + /* Delete a key and ensure it's gone */ + test_eq(minikv_delete("one"), 0); + test_assert(minikv_get("one") == NULL); + + /* Cleanup: remove remaining keys and free allocated values */ + test_eq(minikv_delete("two"), 0); + test_eq(minikv_delete("three"), 0); + + xfree(v1); + xfree(v2); + xfree(v3); + xfree(v2b); + + minikv_destroy(); + + return sel4test_get_result(); +} +DEFINE_TESTQ(MINIKV0001, "Test minimal key-value store: minikv", test_minikv, true) + #ifdef CONFIG_ARCH_IA32 static int test_dirty_accessed_bits(env_t env) @@ -316,4 +445,5 @@ test_dirty_accessed_bits(env_t env) return sel4test_get_result(); } DEFINE_TEST(VSPACE0010, "Test dirty and accessed bits on mappings", test_dirty_accessed_bits, true) + #endif diff --git a/projects/sel4test/easy-settings.cmake b/projects/sel4test/easy-settings.cmake index 1aabca180f8f3d18f689839d6a4119152f06a379..43619e232e875decbd3ece69fe1126861914baad 100755 --- a/projects/sel4test/easy-settings.cmake +++ b/projects/sel4test/easy-settings.cmake @@ -37,3 +37,7 @@ set(LibSel4TestPrinterHaltOnTestFailure OFF CACHE BOOL "Halt on the first test f set(KernelRootCNodeSizeBits "16" CACHE STRING "Root CNode size bits (default 13)") mark_as_advanced(CLEAR LibSel4TestPrinterRegex LibSel4TestPrinterHaltOnTestFailure) + + +# Let CMake export compile_commands.json for clangd. +set(CMAKE_EXPORT_COMPILE_COMMANDS ON)