From 6a4b056992f4577d55795feb10ad2a8327d8276f Mon Sep 17 00:00:00 2001 From: yanjuguang Date: Fri, 8 Sep 2023 10:25:16 +0800 Subject: [PATCH] initial commit --- .clang-format | 16 + .../workflows/actions/setup-musl/action.yml | 38 + .../workflows/actions/setup-qemu/action.yml | 46 + .github/workflows/build.yml | 176 ++ .github/workflows/docs.yml | 32 + .github/workflows/test.yml | 50 + .gitignore | 8 + Cargo.lock | 2191 +++++++++++++++++ Cargo.toml | 79 + LICENSE.Apache2 | 201 ++ LICENSE.GPLv3 | 674 +++++ LICENSE.MulanPSL2 | 127 + LICENSE.MulanPubL2 | 183 ++ Makefile | 212 ++ README.md | 183 ++ api/arceos_api/Cargo.toml | 39 + api/arceos_api/src/imp/display.rs | 11 + api/arceos_api/src/imp/fs.rs | 87 + api/arceos_api/src/imp/mem.rs | 12 + api/arceos_api/src/imp/mod.rs | 42 + api/arceos_api/src/imp/net.rs | 131 + api/arceos_api/src/imp/task.rs | 111 + api/arceos_api/src/lib.rs | 340 +++ api/arceos_api/src/macros.rs | 79 + api/arceos_posix_api/.gitignore | 1 + api/arceos_posix_api/Cargo.toml | 52 + api/arceos_posix_api/build.rs | 106 + api/arceos_posix_api/ctypes.h | 14 + api/arceos_posix_api/src/imp/fd_ops.rs | 128 + api/arceos_posix_api/src/imp/fs.rs | 217 ++ api/arceos_posix_api/src/imp/io.rs | 72 + api/arceos_posix_api/src/imp/io_mpx/epoll.rs | 205 ++ api/arceos_posix_api/src/imp/io_mpx/mod.rs | 16 + api/arceos_posix_api/src/imp/io_mpx/select.rs | 165 ++ api/arceos_posix_api/src/imp/mod.rs | 20 + api/arceos_posix_api/src/imp/net.rs | 581 +++++ api/arceos_posix_api/src/imp/pipe.rs | 214 ++ api/arceos_posix_api/src/imp/pthread/mod.rs | 154 ++ api/arceos_posix_api/src/imp/pthread/mutex.rs | 70 + api/arceos_posix_api/src/imp/resources.rs | 51 + api/arceos_posix_api/src/imp/stdio.rs | 170 ++ api/arceos_posix_api/src/imp/sys.rs | 29 + api/arceos_posix_api/src/imp/task.rs | 40 + api/arceos_posix_api/src/imp/time.rs | 84 + api/arceos_posix_api/src/lib.rs | 62 + api/arceos_posix_api/src/utils.rs | 61 + api/axfeat/Cargo.toml | 74 + api/axfeat/src/lib.rs | 40 + apps/.gitignore | 3 + apps/c/helloworld/expect_info.out | 17 + apps/c/helloworld/expect_info_smp4.out | 23 + apps/c/helloworld/main.c | 7 + apps/c/helloworld/test_cmd | 3 + apps/c/httpclient/axbuild.mk | 1 + apps/c/httpclient/expect_info.out | 38 + apps/c/httpclient/features.txt | 3 + apps/c/httpclient/httpclient.c | 56 + apps/c/httpclient/test_cmd | 2 + apps/c/httpserver/axbuild.mk | 1 + apps/c/httpserver/features.txt | 3 + apps/c/httpserver/httpserver.c | 90 + apps/c/iperf/.gitignore | 1 + apps/c/iperf/README.md | 54 + apps/c/iperf/axbuild.mk | 32 + apps/c/iperf/features.txt | 6 + apps/c/iperf/iperf.patch | 664 +++++ apps/c/memtest/axbuild.mk | 1 + apps/c/memtest/expect_trace.out | 43 + apps/c/memtest/features.txt | 2 + apps/c/memtest/memtest.c | 27 + apps/c/memtest/test_cmd | 2 + .../c/pthread/basic/expect_info_smp4_fifo.out | 26 + apps/c/pthread/basic/features.txt | 3 + apps/c/pthread/basic/main.c | 131 + apps/c/pthread/basic/test_cmd | 2 + .../parallel/expect_info_smp4_fifo.out | 60 + .../pthread/parallel/expect_info_smp4_rr.out | 61 + apps/c/pthread/parallel/features.txt | 3 + apps/c/pthread/parallel/main.c | 77 + apps/c/pthread/parallel/test_cmd | 3 + apps/c/pthread/pipe/expect_info_smp4_fifo.out | 30 + apps/c/pthread/pipe/features.txt | 4 + apps/c/pthread/pipe/main.c | 47 + apps/c/pthread/pipe/test_cmd | 2 + .../c/pthread/sleep/expect_info_smp4_fifo.out | 83 + apps/c/pthread/sleep/features.txt | 4 + apps/c/pthread/sleep/main.c | 104 + apps/c/pthread/sleep/test_cmd | 2 + apps/c/redis/.gitignore | 1 + apps/c/redis/Makefile | 45 + apps/c/redis/README.md | 333 +++ apps/c/redis/axbuild.mk | 34 + apps/c/redis/features.txt | 9 + apps/c/redis/redis.patch | 102 + apps/c/sqlite3/.gitignore | 1 + apps/c/sqlite3/Makefile | 13 + apps/c/sqlite3/axbuild.mk | 14 + apps/c/sqlite3/expect_info.out | 80 + apps/c/sqlite3/expect_info_again.out | 92 + apps/c/sqlite3/expect_info_ramdisk.out | 81 + apps/c/sqlite3/features.txt | 4 + apps/c/sqlite3/main.c | 96 + apps/c/sqlite3/test_cmd | 4 + apps/c/udpserver/axbuild.mk | 1 + apps/c/udpserver/features.txt | 3 + apps/c/udpserver/udpserver.c | 54 + apps/display/Cargo.toml | 11 + apps/display/src/display.rs | 51 + apps/display/src/main.rs | 86 + apps/exception/Cargo.toml | 10 + apps/exception/expect_debug_aarch64.out | 19 + apps/exception/expect_debug_riscv64.out | 19 + apps/exception/expect_debug_x86_64.out | 19 + apps/exception/src/main.rs | 26 + apps/exception/test_cmd | 1 + apps/fs/shell/Cargo.toml | 17 + apps/fs/shell/src/cmd.rs | 293 +++ apps/fs/shell/src/main.rs | 85 + apps/fs/shell/src/ramfs.rs | 15 + apps/helloworld/Cargo.toml | 10 + apps/helloworld/expect_info.out | 17 + apps/helloworld/expect_info_smp4.out | 23 + apps/helloworld/src/main.rs | 10 + apps/helloworld/test_cmd | 2 + apps/memtest/Cargo.toml | 11 + apps/memtest/expect_trace.out | 34 + apps/memtest/src/main.rs | 50 + apps/memtest/test_cmd | 1 + apps/net/bwbench/Cargo.toml | 11 + apps/net/bwbench/src/main.rs | 11 + apps/net/echoserver/Cargo.toml | 10 + apps/net/echoserver/src/main.rs | 62 + apps/net/httpclient/Cargo.toml | 14 + apps/net/httpclient/expect_info.out | 38 + apps/net/httpclient/expect_info_dns.out | 38 + apps/net/httpclient/src/main.rs | 40 + apps/net/httpclient/test_cmd | 2 + apps/net/httpserver/Cargo.toml | 10 + apps/net/httpserver/src/main.rs | 96 + apps/net/udpserver/Cargo.toml | 8 + apps/net/udpserver/src/main.rs | 38 + apps/task/parallel/Cargo.toml | 11 + apps/task/parallel/expect_info_smp1_fifo.out | 39 + apps/task/parallel/expect_info_smp4_cfs.out | 60 + apps/task/parallel/expect_info_smp4_rr.out | 60 + apps/task/parallel/src/main.rs | 98 + apps/task/parallel/test_cmd | 3 + apps/task/priority/Cargo.toml | 14 + apps/task/priority/expect_info_smp1_cfs.out | 37 + apps/task/priority/expect_info_smp1_fifo.out | 36 + apps/task/priority/expect_info_smp1_rr.out | 37 + apps/task/priority/expect_info_smp4_cfs.out | 43 + apps/task/priority/src/main.rs | 125 + apps/task/priority/test_cmd | 4 + apps/task/sleep/Cargo.toml | 10 + apps/task/sleep/expect_info_smp4_fifo.out | 82 + apps/task/sleep/expect_info_smp4_rr.out | 82 + apps/task/sleep/src/main.rs | 51 + apps/task/sleep/test_cmd | 2 + apps/task/tls/Cargo.toml | 10 + apps/task/tls/expect_info_smp1_fifo.out | 31 + apps/task/tls/expect_info_smp4_rr.out | 38 + apps/task/tls/src/main.rs | 111 + apps/task/tls/test_cmd | 2 + apps/task/yield/Cargo.toml | 14 + apps/task/yield/expect_debug_smp1_fifo.out | 54 + apps/task/yield/expect_info_smp4_fifo.out | 38 + apps/task/yield/expect_info_smp4_rr.out | 38 + apps/task/yield/src/main.rs | 36 + apps/task/yield/test_cmd | 3 + crates/allocator/Cargo.toml | 37 + crates/allocator/benches/collections.rs | 101 + crates/allocator/benches/utils/mod.rs | 25 + crates/allocator/src/bitmap.rs | 95 + crates/allocator/src/buddy.rs | 58 + crates/allocator/src/lib.rs | 181 ++ crates/allocator/src/slab.rs | 68 + crates/allocator/src/tlsf.rs | 77 + crates/allocator/tests/allocator.rs | 143 ++ crates/arm_gic/Cargo.toml | 13 + crates/arm_gic/src/gic_v2.rs | 275 +++ crates/arm_gic/src/lib.rs | 91 + crates/arm_pl011/Cargo.toml | 13 + crates/arm_pl011/src/lib.rs | 8 + crates/arm_pl011/src/pl011.rs | 107 + crates/axerrno/.gitignore | 1 + crates/axerrno/Cargo.toml | 13 + crates/axerrno/build.rs | 86 + crates/axerrno/src/errno.h | 150 ++ crates/axerrno/src/lib.rs | 298 +++ crates/axfs_devfs/Cargo.toml | 15 + crates/axfs_devfs/src/dir.rs | 138 ++ crates/axfs_devfs/src/lib.rs | 71 + crates/axfs_devfs/src/null.rs | 31 + crates/axfs_devfs/src/tests.rs | 115 + crates/axfs_devfs/src/zero.rs | 32 + crates/axfs_ramfs/Cargo.toml | 15 + crates/axfs_ramfs/src/dir.rs | 176 ++ crates/axfs_ramfs/src/file.rs | 56 + crates/axfs_ramfs/src/lib.rs | 62 + crates/axfs_ramfs/src/tests.rs | 136 + crates/axfs_vfs/Cargo.toml | 18 + crates/axfs_vfs/src/lib.rs | 177 ++ crates/axfs_vfs/src/macros.rs | 66 + crates/axfs_vfs/src/path.rs | 97 + crates/axfs_vfs/src/structs.rs | 305 +++ crates/axio/Cargo.toml | 17 + crates/axio/src/buffered/bufreader.rs | 158 ++ crates/axio/src/buffered/mod.rs | 3 + crates/axio/src/error.rs | 2 + crates/axio/src/impls.rs | 54 + crates/axio/src/lib.rs | 260 ++ crates/axio/src/prelude.rs | 11 + crates/capability/Cargo.toml | 14 + crates/capability/src/lib.rs | 139 ++ crates/crate_interface/Cargo.toml | 20 + crates/crate_interface/README.md | 38 + crates/crate_interface/src/lib.rs | 187 ++ .../tests/test_crate_interface.rs | 34 + crates/driver_block/Cargo.toml | 20 + crates/driver_block/src/bcm2835sdhci.rs | 88 + crates/driver_block/src/lib.rs | 39 + crates/driver_block/src/ramdisk.rs | 100 + crates/driver_common/Cargo.toml | 12 + crates/driver_common/src/lib.rs | 64 + crates/driver_display/Cargo.toml | 13 + crates/driver_display/src/lib.rs | 59 + crates/driver_net/Cargo.toml | 20 + crates/driver_net/src/ixgbe.rs | 160 ++ crates/driver_net/src/lib.rs | 107 + crates/driver_net/src/net_buf.rs | 208 ++ crates/driver_pci/Cargo.toml | 13 + crates/driver_pci/src/lib.rs | 53 + crates/driver_virtio/Cargo.toml | 22 + crates/driver_virtio/src/blk.rs | 60 + crates/driver_virtio/src/gpu.rs | 70 + crates/driver_virtio/src/lib.rs | 98 + crates/driver_virtio/src/net.rs | 193 ++ crates/dw_apb_uart/Cargo.toml | 13 + crates/dw_apb_uart/src/lib.rs | 118 + crates/flatten_objects/Cargo.toml | 13 + crates/flatten_objects/src/lib.rs | 158 ++ crates/handler_table/Cargo.toml | 14 + crates/handler_table/README.md | 23 + crates/handler_table/src/lib.rs | 49 + crates/kernel_guard/Cargo.toml | 20 + crates/kernel_guard/README.md | 56 + crates/kernel_guard/src/arch/aarch64.rs | 14 + crates/kernel_guard/src/arch/mod.rs | 14 + crates/kernel_guard/src/arch/riscv.rs | 18 + crates/kernel_guard/src/arch/x86.rs | 20 + crates/kernel_guard/src/lib.rs | 239 ++ crates/lazy_init/Cargo.toml | 12 + crates/lazy_init/src/lib.rs | 157 ++ crates/linked_list/Cargo.toml | 12 + crates/linked_list/src/lib.rs | 14 + crates/linked_list/src/linked_list.rs | 220 ++ crates/linked_list/src/unsafe_list.rs | 683 +++++ crates/memory_addr/Cargo.toml | 14 + crates/memory_addr/README.md | 20 + crates/memory_addr/src/lib.rs | 384 +++ crates/page_table/Cargo.toml | 15 + crates/page_table/src/arch/aarch64.rs | 22 + crates/page_table/src/arch/mod.rs | 8 + crates/page_table/src/arch/riscv.rs | 30 + crates/page_table/src/arch/x86_64.rs | 16 + crates/page_table/src/bits64.rs | 386 +++ crates/page_table/src/lib.rs | 124 + crates/page_table_entry/Cargo.toml | 18 + crates/page_table_entry/src/arch/aarch64.rs | 240 ++ crates/page_table_entry/src/arch/mod.rs | 9 + crates/page_table_entry/src/arch/riscv.rs | 130 + crates/page_table_entry/src/arch/x86_64.rs | 114 + crates/page_table_entry/src/lib.rs | 72 + crates/percpu/Cargo.toml | 30 + crates/percpu/build.rs | 9 + crates/percpu/src/imp.rs | 125 + crates/percpu/src/lib.rs | 69 + crates/percpu/src/naive.rs | 10 + crates/percpu/test_percpu.x | 19 + crates/percpu/tests/test_percpu.rs | 102 + crates/percpu_macros/Cargo.toml | 27 + crates/percpu_macros/src/arch.rs | 195 ++ crates/percpu_macros/src/lib.rs | 175 ++ crates/percpu_macros/src/naive.rs | 28 + crates/ratio/Cargo.toml | 12 + crates/ratio/src/lib.rs | 187 ++ crates/scheduler/Cargo.toml | 13 + crates/scheduler/src/cfs.rs | 190 ++ crates/scheduler/src/fifo.rs | 102 + crates/scheduler/src/lib.rs | 69 + crates/scheduler/src/round_robin.rs | 113 + crates/scheduler/src/tests.rs | 84 + crates/slab_allocator/Cargo.toml | 11 + crates/slab_allocator/src/lib.rs | 256 ++ crates/slab_allocator/src/slab.rs | 120 + crates/slab_allocator/src/tests.rs | 163 ++ crates/spinlock/Cargo.toml | 19 + crates/spinlock/src/base.rs | 393 +++ crates/spinlock/src/lib.rs | 45 + crates/timer_list/Cargo.toml | 12 + crates/timer_list/src/lib.rs | 234 ++ crates/tuple_for_each/Cargo.toml | 18 + crates/tuple_for_each/src/lib.rs | 165 ++ .../tests/test_tuple_for_each.rs | 106 + doc/README.md | 264 ++ doc/apps_display.md | 106 + doc/apps_echoserver.md | 106 + doc/apps_exception.md | 66 + doc/apps_fs_shell.md | 310 +++ doc/apps_helloworld.md | 59 + doc/apps_httpserver.md | 177 ++ doc/apps_memtest.md | 55 + doc/apps_net-httpclient.md | 88 + doc/apps_parallel.md | 184 ++ doc/apps_priority.md | 41 + doc/apps_sleep.md | 122 + doc/apps_yield.md | 91 + doc/build.md | 91 + doc/figures/ArceOS.svg | 4 + doc/figures/display.png | Bin 0 -> 124401 bytes doc/init.md | 44 + doc/ixgbe.md | 15 + doc/platform_raspi4.md | 28 + modules/axalloc/Cargo.toml | 24 + modules/axalloc/src/lib.rs | 233 ++ modules/axalloc/src/page.rs | 108 + modules/axconfig/Cargo.toml | 14 + modules/axconfig/build.rs | 172 ++ modules/axconfig/defconfig.toml | 41 + modules/axconfig/src/lib.rs | 19 + modules/axdisplay/Cargo.toml | 17 + modules/axdisplay/src/lib.rs | 36 + modules/axdriver/Cargo.toml | 45 + modules/axdriver/build.rs | 50 + modules/axdriver/src/bus/mmio.rs | 23 + modules/axdriver/src/bus/mod.rs | 4 + modules/axdriver/src/bus/pci.rs | 122 + modules/axdriver/src/drivers.rs | 130 + modules/axdriver/src/dummy.rs | 102 + modules/axdriver/src/ixgbe.rs | 38 + modules/axdriver/src/lib.rs | 184 ++ modules/axdriver/src/macros.rs | 68 + modules/axdriver/src/prelude.rs | 10 + modules/axdriver/src/structs/dyn.rs | 85 + modules/axdriver/src/structs/mod.rs | 51 + modules/axdriver/src/structs/static.rs | 79 + modules/axdriver/src/virtio.rs | 172 ++ modules/axfs/Cargo.toml | 54 + modules/axfs/resources/create_test_img.sh | 30 + modules/axfs/resources/fat16.img | Bin 0 -> 2560000 bytes modules/axfs/src/api/dir.rs | 150 ++ modules/axfs/src/api/file.rs | 187 ++ modules/axfs/src/api/mod.rs | 89 + modules/axfs/src/dev.rs | 92 + modules/axfs/src/fops.rs | 403 +++ modules/axfs/src/fs/fatfs.rs | 283 +++ modules/axfs/src/fs/mod.rs | 13 + modules/axfs/src/fs/myfs.rs | 16 + modules/axfs/src/lib.rs | 46 + modules/axfs/src/mounts.rs | 80 + modules/axfs/src/root.rs | 310 +++ modules/axfs/tests/test_common/mod.rs | 262 ++ modules/axfs/tests/test_fatfs.rs | 27 + modules/axfs/tests/test_ramfs.rs | 56 + modules/axhal/.gitignore | 1 + modules/axhal/Cargo.toml | 58 + modules/axhal/build.rs | 33 + modules/axhal/linker.lds.S | 86 + modules/axhal/src/arch/aarch64/context.rs | 179 ++ modules/axhal/src/arch/aarch64/mod.rs | 137 ++ modules/axhal/src/arch/aarch64/trap.S | 107 + modules/axhal/src/arch/aarch64/trap.rs | 86 + modules/axhal/src/arch/mod.rs | 14 + modules/axhal/src/arch/riscv/context.rs | 162 ++ modules/axhal/src/arch/riscv/macros.rs | 81 + modules/axhal/src/arch/riscv/mod.rs | 109 + modules/axhal/src/arch/riscv/trap.S | 65 + modules/axhal/src/arch/riscv/trap.rs | 32 + modules/axhal/src/arch/x86_64/context.rs | 221 ++ modules/axhal/src/arch/x86_64/gdt.rs | 88 + modules/axhal/src/arch/x86_64/idt.rs | 67 + modules/axhal/src/arch/x86_64/mod.rs | 110 + modules/axhal/src/arch/x86_64/trap.S | 84 + modules/axhal/src/arch/x86_64/trap.rs | 46 + modules/axhal/src/cpu.rs | 93 + modules/axhal/src/irq.rs | 35 + modules/axhal/src/lib.rs | 81 + modules/axhal/src/mem.rs | 163 ++ modules/axhal/src/paging.rs | 66 + .../platform/aarch64_bsta1000b/dw_apb_uart.rs | 44 + .../src/platform/aarch64_bsta1000b/mem.rs | 33 + .../src/platform/aarch64_bsta1000b/misc.rs | 60 + .../src/platform/aarch64_bsta1000b/mod.rs | 62 + .../src/platform/aarch64_bsta1000b/mp.rs | 23 + .../axhal/src/platform/aarch64_common/boot.rs | 173 ++ .../platform/aarch64_common/generic_timer.rs | 60 + .../axhal/src/platform/aarch64_common/gic.rs | 60 + .../axhal/src/platform/aarch64_common/mod.rs | 11 + .../src/platform/aarch64_common/pl011.rs | 51 + .../axhal/src/platform/aarch64_common/psci.rs | 129 + .../src/platform/aarch64_qemu_virt/mem.rs | 27 + .../src/platform/aarch64_qemu_virt/mod.rs | 64 + .../src/platform/aarch64_qemu_virt/mp.rs | 10 + .../axhal/src/platform/aarch64_raspi/mem.rs | 46 + .../axhal/src/platform/aarch64_raspi/mod.rs | 69 + .../axhal/src/platform/aarch64_raspi/mp.rs | 49 + modules/axhal/src/platform/dummy/mod.rs | 87 + modules/axhal/src/platform/mod.rs | 29 + .../src/platform/riscv64_qemu_virt/boot.rs | 89 + .../src/platform/riscv64_qemu_virt/console.rs | 14 + .../src/platform/riscv64_qemu_virt/irq.rs | 85 + .../src/platform/riscv64_qemu_virt/mem.rs | 6 + .../src/platform/riscv64_qemu_virt/misc.rs | 9 + .../src/platform/riscv64_qemu_virt/mod.rs | 50 + .../src/platform/riscv64_qemu_virt/mp.rs | 14 + .../src/platform/riscv64_qemu_virt/time.rs | 34 + modules/axhal/src/platform/x86_pc/ap_start.S | 69 + modules/axhal/src/platform/x86_pc/apic.rs | 124 + modules/axhal/src/platform/x86_pc/boot.rs | 52 + modules/axhal/src/platform/x86_pc/dtables.rs | 37 + modules/axhal/src/platform/x86_pc/mem.rs | 15 + modules/axhal/src/platform/x86_pc/misc.rs | 27 + modules/axhal/src/platform/x86_pc/mod.rs | 68 + modules/axhal/src/platform/x86_pc/mp.rs | 45 + modules/axhal/src/platform/x86_pc/multiboot.S | 143 ++ modules/axhal/src/platform/x86_pc/time.rs | 82 + .../axhal/src/platform/x86_pc/uart16550.rs | 105 + modules/axhal/src/time.rs | 48 + modules/axhal/src/tls.rs | 166 ++ modules/axhal/src/trap.rs | 23 + modules/axlog/Cargo.toml | 30 + modules/axlog/src/lib.rs | 262 ++ modules/axnet/Cargo.toml | 41 + modules/axnet/src/lib.rs | 49 + modules/axnet/src/smoltcp_impl/addr.rs | 34 + modules/axnet/src/smoltcp_impl/bench.rs | 74 + modules/axnet/src/smoltcp_impl/dns.rs | 90 + .../axnet/src/smoltcp_impl/listen_table.rs | 155 ++ modules/axnet/src/smoltcp_impl/mod.rs | 330 +++ modules/axnet/src/smoltcp_impl/tcp.rs | 527 ++++ modules/axnet/src/smoltcp_impl/udp.rs | 294 +++ modules/axruntime/Cargo.toml | 40 + modules/axruntime/src/lang_items.rs | 7 + modules/axruntime/src/lib.rs | 295 +++ modules/axruntime/src/mp.rs | 64 + modules/axruntime/src/trap.rs | 13 + modules/axsync/Cargo.toml | 23 + modules/axsync/src/lib.rs | 28 + modules/axsync/src/mutex.rs | 252 ++ modules/axtask/Cargo.toml | 46 + modules/axtask/src/api.rs | 166 ++ modules/axtask/src/api_s.rs | 22 + modules/axtask/src/lib.rs | 53 + modules/axtask/src/run_queue.rs | 233 ++ modules/axtask/src/task.rs | 391 +++ modules/axtask/src/tests.rs | 130 + modules/axtask/src/timers.rs | 48 + modules/axtask/src/wait_queue.rs | 216 ++ platforms/aarch64-bsta1000b.toml | 54 + platforms/aarch64-qemu-virt.toml | 81 + platforms/aarch64-raspi4.toml | 31 + platforms/riscv64-qemu-virt.toml | 50 + platforms/x86_64-pc-oslab.toml | 37 + platforms/x86_64-qemu-q35.toml | 37 + rust-toolchain.toml | 5 + scripts/make/bsta1000b-fada.mk | 4 + scripts/make/build.mk | 49 + scripts/make/build_c.mk | 77 + scripts/make/cargo.mk | 52 + scripts/make/features.mk | 60 + scripts/make/qemu.mk | 77 + scripts/make/raspi4.mk | 94 + scripts/make/test.mk | 16 + scripts/make/utils.mk | 23 + scripts/net/qemu-tap-ifdown.sh | 14 + scripts/net/qemu-tap-ifup.sh | 17 + scripts/test/app_test.sh | 142 ++ tools/.gitignore | 5 + tools/bsta1000b/bsta1000b-fada-arceos.its | 50 + tools/bsta1000b/bsta1000b-fada.dtb | Bin 0 -> 59621 bytes tools/bwbench_client/Cargo.toml | 13 + tools/bwbench_client/README.md | 37 + tools/bwbench_client/src/device.rs | 217 ++ tools/bwbench_client/src/main.rs | 133 + tools/deptool/Cargo.toml | 10 + tools/deptool/Makefile | 34 + tools/deptool/README.md | 17 + tools/deptool/src/cmd_builder.rs | 18 + tools/deptool/src/cmd_parser.rs | 91 + tools/deptool/src/d2_generator.rs | 39 + tools/deptool/src/lib.rs | 100 + tools/deptool/src/main.rs | 11 + tools/deptool/src/mermaid_generator.rs | 37 + tools/raspi4/common/docker.mk | 1 + tools/raspi4/common/format.mk | 12 + tools/raspi4/common/operating_system.mk | 9 + tools/raspi4/common/serial/minipush.rb | 131 + .../serial/minipush/progressbar_patch.rb | 29 + tools/raspi4/common/serial/miniterm.rb | 144 ++ ulib/axlibc/.gitignore | 3 + ulib/axlibc/Cargo.toml | 57 + ulib/axlibc/build.rs | 24 + ulib/axlibc/c/assert.c | 8 + ulib/axlibc/c/ctype.c | 17 + ulib/axlibc/c/dirent.c | 98 + ulib/axlibc/c/dlfcn.c | 39 + ulib/axlibc/c/env.c | 31 + ulib/axlibc/c/fcntl.c | 56 + ulib/axlibc/c/flock.c | 9 + ulib/axlibc/c/fnmatch.c | 15 + ulib/axlibc/c/glob.c | 329 +++ ulib/axlibc/c/ioctl.c | 9 + ulib/axlibc/c/libgen.c | 33 + ulib/axlibc/c/libm.c | 17 + ulib/axlibc/c/libm.h | 233 ++ ulib/axlibc/c/locale.c | 42 + ulib/axlibc/c/log.c | 395 +++ ulib/axlibc/c/math.c | 571 +++++ ulib/axlibc/c/mmap.c | 39 + ulib/axlibc/c/network.c | 226 ++ ulib/axlibc/c/poll.c | 9 + ulib/axlibc/c/pow.c | 815 ++++++ ulib/axlibc/c/printf.c | 1482 +++++++++++ ulib/axlibc/c/printf.h | 215 ++ ulib/axlibc/c/printf_config.h | 14 + ulib/axlibc/c/pthread.c | 110 + ulib/axlibc/c/pwd.c | 14 + ulib/axlibc/c/resource.c | 10 + ulib/axlibc/c/sched.c | 9 + ulib/axlibc/c/select.c | 17 + ulib/axlibc/c/signal.c | 87 + ulib/axlibc/c/socket.c | 47 + ulib/axlibc/c/stat.c | 38 + ulib/axlibc/c/stdio.c | 412 ++++ ulib/axlibc/c/stdlib.c | 385 +++ ulib/axlibc/c/string.c | 478 ++++ ulib/axlibc/c/syslog.c | 16 + ulib/axlibc/c/time.c | 203 ++ ulib/axlibc/c/unistd.c | 174 ++ ulib/axlibc/c/utsname.c | 9 + ulib/axlibc/c/wait.c | 17 + ulib/axlibc/ctypes.h | 2 + ulib/axlibc/include/arpa/inet.h | 14 + ulib/axlibc/include/assert.h | 14 + ulib/axlibc/include/ctype.h | 20 + ulib/axlibc/include/dirent.h | 45 + ulib/axlibc/include/dlfcn.h | 41 + ulib/axlibc/include/endian.h | 68 + ulib/axlibc/include/errno.h | 150 ++ ulib/axlibc/include/fcntl.h | 123 + ulib/axlibc/include/features.h | 11 + ulib/axlibc/include/float.h | 99 + ulib/axlibc/include/fnmatch.h | 24 + ulib/axlibc/include/glob.h | 34 + ulib/axlibc/include/inttypes.h | 10 + ulib/axlibc/include/langinfo.h | 82 + ulib/axlibc/include/libgen.h | 15 + ulib/axlibc/include/limits.h | 35 + ulib/axlibc/include/locale.h | 59 + ulib/axlibc/include/math.h | 321 +++ ulib/axlibc/include/memory.h | 6 + ulib/axlibc/include/netdb.h | 85 + ulib/axlibc/include/netinet/in.h | 129 + ulib/axlibc/include/netinet/tcp.h | 45 + ulib/axlibc/include/poll.h | 21 + ulib/axlibc/include/pthread.h | 84 + ulib/axlibc/include/pwd.h | 45 + ulib/axlibc/include/regex.h | 4 + ulib/axlibc/include/sched.h | 23 + ulib/axlibc/include/setjmp.h | 23 + ulib/axlibc/include/signal.h | 179 ++ ulib/axlibc/include/stdarg.h | 11 + ulib/axlibc/include/stdbool.h | 11 + ulib/axlibc/include/stddef.h | 27 + ulib/axlibc/include/stdint.h | 66 + ulib/axlibc/include/stdio.h | 116 + ulib/axlibc/include/stdlib.h | 60 + ulib/axlibc/include/string.h | 50 + ulib/axlibc/include/strings.h | 4 + ulib/axlibc/include/sys/epoll.h | 60 + ulib/axlibc/include/sys/file.h | 23 + ulib/axlibc/include/sys/ioctl.h | 65 + ulib/axlibc/include/sys/mman.h | 52 + ulib/axlibc/include/sys/param.h | 6 + ulib/axlibc/include/sys/prctl.h | 4 + ulib/axlibc/include/sys/resource.h | 63 + ulib/axlibc/include/sys/select.h | 36 + ulib/axlibc/include/sys/socket.h | 310 +++ ulib/axlibc/include/sys/stat.h | 78 + ulib/axlibc/include/sys/time.h | 41 + ulib/axlibc/include/sys/types.h | 20 + ulib/axlibc/include/sys/uio.h | 13 + ulib/axlibc/include/sys/un.h | 11 + ulib/axlibc/include/sys/utsname.h | 27 + ulib/axlibc/include/sys/wait.h | 12 + ulib/axlibc/include/syslog.h | 45 + ulib/axlibc/include/termios.h | 8 + ulib/axlibc/include/time.h | 43 + ulib/axlibc/include/unistd.h | 240 ++ ulib/axlibc/src/errno.rs | 39 + ulib/axlibc/src/fd_ops.rs | 54 + ulib/axlibc/src/fs.rs | 67 + ulib/axlibc/src/io.rs | 32 + ulib/axlibc/src/io_mpx.rs | 54 + ulib/axlibc/src/lib.rs | 125 + ulib/axlibc/src/malloc.rs | 56 + ulib/axlibc/src/mktime.rs | 58 + ulib/axlibc/src/net.rs | 180 ++ ulib/axlibc/src/pipe.rs | 14 + ulib/axlibc/src/pthread.rs | 59 + ulib/axlibc/src/rand.rs | 30 + ulib/axlibc/src/resource.rs | 17 + ulib/axlibc/src/setjmp.rs | 238 ++ ulib/axlibc/src/strftime.rs | 254 ++ ulib/axlibc/src/strtod.rs | 131 + ulib/axlibc/src/sys.rs | 10 + ulib/axlibc/src/time.rs | 21 + ulib/axlibc/src/unistd.rs | 20 + ulib/axlibc/src/utils.rs | 10 + ulib/axstd/Cargo.toml | 76 + ulib/axstd/src/env.rs | 19 + ulib/axstd/src/fs/dir.rs | 154 ++ ulib/axstd/src/fs/file.rs | 187 ++ ulib/axstd/src/fs/mod.rs | 77 + ulib/axstd/src/io/mod.rs | 28 + ulib/axstd/src/io/stdio.rs | 173 ++ ulib/axstd/src/lib.rs | 78 + ulib/axstd/src/macros.rs | 23 + ulib/axstd/src/net/mod.rs | 46 + ulib/axstd/src/net/socket_addr.rs | 190 ++ ulib/axstd/src/net/tcp.rs | 105 + ulib/axstd/src/net/udp.rs | 96 + ulib/axstd/src/os.rs | 6 + ulib/axstd/src/process.rs | 10 + ulib/axstd/src/sync/mod.rs | 19 + ulib/axstd/src/sync/mutex.rs | 198 ++ ulib/axstd/src/thread/mod.rs | 41 + ulib/axstd/src/thread/multi.rs | 189 ++ ulib/axstd/src/time.rs | 97 + 640 files changed, 54744 insertions(+) create mode 100644 .clang-format create mode 100644 .github/workflows/actions/setup-musl/action.yml create mode 100644 .github/workflows/actions/setup-qemu/action.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE.Apache2 create mode 100644 LICENSE.GPLv3 create mode 100644 LICENSE.MulanPSL2 create mode 100644 LICENSE.MulanPubL2 create mode 100644 Makefile create mode 100644 README.md create mode 100644 api/arceos_api/Cargo.toml create mode 100644 api/arceos_api/src/imp/display.rs create mode 100644 api/arceos_api/src/imp/fs.rs create mode 100644 api/arceos_api/src/imp/mem.rs create mode 100644 api/arceos_api/src/imp/mod.rs create mode 100644 api/arceos_api/src/imp/net.rs create mode 100644 api/arceos_api/src/imp/task.rs create mode 100644 api/arceos_api/src/lib.rs create mode 100644 api/arceos_api/src/macros.rs create mode 100644 api/arceos_posix_api/.gitignore create mode 100644 api/arceos_posix_api/Cargo.toml create mode 100644 api/arceos_posix_api/build.rs create mode 100644 api/arceos_posix_api/ctypes.h create mode 100644 api/arceos_posix_api/src/imp/fd_ops.rs create mode 100644 api/arceos_posix_api/src/imp/fs.rs create mode 100644 api/arceos_posix_api/src/imp/io.rs create mode 100644 api/arceos_posix_api/src/imp/io_mpx/epoll.rs create mode 100644 api/arceos_posix_api/src/imp/io_mpx/mod.rs create mode 100644 api/arceos_posix_api/src/imp/io_mpx/select.rs create mode 100644 api/arceos_posix_api/src/imp/mod.rs create mode 100644 api/arceos_posix_api/src/imp/net.rs create mode 100644 api/arceos_posix_api/src/imp/pipe.rs create mode 100644 api/arceos_posix_api/src/imp/pthread/mod.rs create mode 100644 api/arceos_posix_api/src/imp/pthread/mutex.rs create mode 100644 api/arceos_posix_api/src/imp/resources.rs create mode 100644 api/arceos_posix_api/src/imp/stdio.rs create mode 100644 api/arceos_posix_api/src/imp/sys.rs create mode 100644 api/arceos_posix_api/src/imp/task.rs create mode 100644 api/arceos_posix_api/src/imp/time.rs create mode 100644 api/arceos_posix_api/src/lib.rs create mode 100644 api/arceos_posix_api/src/utils.rs create mode 100644 api/axfeat/Cargo.toml create mode 100644 api/axfeat/src/lib.rs create mode 100644 apps/.gitignore create mode 100644 apps/c/helloworld/expect_info.out create mode 100644 apps/c/helloworld/expect_info_smp4.out create mode 100644 apps/c/helloworld/main.c create mode 100644 apps/c/helloworld/test_cmd create mode 100644 apps/c/httpclient/axbuild.mk create mode 100644 apps/c/httpclient/expect_info.out create mode 100644 apps/c/httpclient/features.txt create mode 100644 apps/c/httpclient/httpclient.c create mode 100644 apps/c/httpclient/test_cmd create mode 100644 apps/c/httpserver/axbuild.mk create mode 100644 apps/c/httpserver/features.txt create mode 100644 apps/c/httpserver/httpserver.c create mode 100644 apps/c/iperf/.gitignore create mode 100644 apps/c/iperf/README.md create mode 100644 apps/c/iperf/axbuild.mk create mode 100644 apps/c/iperf/features.txt create mode 100644 apps/c/iperf/iperf.patch create mode 100644 apps/c/memtest/axbuild.mk create mode 100644 apps/c/memtest/expect_trace.out create mode 100644 apps/c/memtest/features.txt create mode 100644 apps/c/memtest/memtest.c create mode 100644 apps/c/memtest/test_cmd create mode 100644 apps/c/pthread/basic/expect_info_smp4_fifo.out create mode 100644 apps/c/pthread/basic/features.txt create mode 100644 apps/c/pthread/basic/main.c create mode 100644 apps/c/pthread/basic/test_cmd create mode 100644 apps/c/pthread/parallel/expect_info_smp4_fifo.out create mode 100644 apps/c/pthread/parallel/expect_info_smp4_rr.out create mode 100644 apps/c/pthread/parallel/features.txt create mode 100644 apps/c/pthread/parallel/main.c create mode 100644 apps/c/pthread/parallel/test_cmd create mode 100644 apps/c/pthread/pipe/expect_info_smp4_fifo.out create mode 100644 apps/c/pthread/pipe/features.txt create mode 100644 apps/c/pthread/pipe/main.c create mode 100644 apps/c/pthread/pipe/test_cmd create mode 100644 apps/c/pthread/sleep/expect_info_smp4_fifo.out create mode 100644 apps/c/pthread/sleep/features.txt create mode 100644 apps/c/pthread/sleep/main.c create mode 100644 apps/c/pthread/sleep/test_cmd create mode 100644 apps/c/redis/.gitignore create mode 100644 apps/c/redis/Makefile create mode 100644 apps/c/redis/README.md create mode 100644 apps/c/redis/axbuild.mk create mode 100644 apps/c/redis/features.txt create mode 100644 apps/c/redis/redis.patch create mode 100644 apps/c/sqlite3/.gitignore create mode 100644 apps/c/sqlite3/Makefile create mode 100644 apps/c/sqlite3/axbuild.mk create mode 100644 apps/c/sqlite3/expect_info.out create mode 100644 apps/c/sqlite3/expect_info_again.out create mode 100644 apps/c/sqlite3/expect_info_ramdisk.out create mode 100644 apps/c/sqlite3/features.txt create mode 100644 apps/c/sqlite3/main.c create mode 100644 apps/c/sqlite3/test_cmd create mode 100644 apps/c/udpserver/axbuild.mk create mode 100644 apps/c/udpserver/features.txt create mode 100644 apps/c/udpserver/udpserver.c create mode 100644 apps/display/Cargo.toml create mode 100644 apps/display/src/display.rs create mode 100644 apps/display/src/main.rs create mode 100644 apps/exception/Cargo.toml create mode 100644 apps/exception/expect_debug_aarch64.out create mode 100644 apps/exception/expect_debug_riscv64.out create mode 100644 apps/exception/expect_debug_x86_64.out create mode 100644 apps/exception/src/main.rs create mode 100644 apps/exception/test_cmd create mode 100644 apps/fs/shell/Cargo.toml create mode 100644 apps/fs/shell/src/cmd.rs create mode 100644 apps/fs/shell/src/main.rs create mode 100644 apps/fs/shell/src/ramfs.rs create mode 100644 apps/helloworld/Cargo.toml create mode 100644 apps/helloworld/expect_info.out create mode 100644 apps/helloworld/expect_info_smp4.out create mode 100644 apps/helloworld/src/main.rs create mode 100644 apps/helloworld/test_cmd create mode 100644 apps/memtest/Cargo.toml create mode 100644 apps/memtest/expect_trace.out create mode 100644 apps/memtest/src/main.rs create mode 100644 apps/memtest/test_cmd create mode 100644 apps/net/bwbench/Cargo.toml create mode 100644 apps/net/bwbench/src/main.rs create mode 100644 apps/net/echoserver/Cargo.toml create mode 100644 apps/net/echoserver/src/main.rs create mode 100644 apps/net/httpclient/Cargo.toml create mode 100644 apps/net/httpclient/expect_info.out create mode 100644 apps/net/httpclient/expect_info_dns.out create mode 100644 apps/net/httpclient/src/main.rs create mode 100644 apps/net/httpclient/test_cmd create mode 100644 apps/net/httpserver/Cargo.toml create mode 100644 apps/net/httpserver/src/main.rs create mode 100644 apps/net/udpserver/Cargo.toml create mode 100644 apps/net/udpserver/src/main.rs create mode 100644 apps/task/parallel/Cargo.toml create mode 100644 apps/task/parallel/expect_info_smp1_fifo.out create mode 100644 apps/task/parallel/expect_info_smp4_cfs.out create mode 100644 apps/task/parallel/expect_info_smp4_rr.out create mode 100644 apps/task/parallel/src/main.rs create mode 100644 apps/task/parallel/test_cmd create mode 100644 apps/task/priority/Cargo.toml create mode 100644 apps/task/priority/expect_info_smp1_cfs.out create mode 100644 apps/task/priority/expect_info_smp1_fifo.out create mode 100644 apps/task/priority/expect_info_smp1_rr.out create mode 100644 apps/task/priority/expect_info_smp4_cfs.out create mode 100644 apps/task/priority/src/main.rs create mode 100644 apps/task/priority/test_cmd create mode 100644 apps/task/sleep/Cargo.toml create mode 100644 apps/task/sleep/expect_info_smp4_fifo.out create mode 100644 apps/task/sleep/expect_info_smp4_rr.out create mode 100644 apps/task/sleep/src/main.rs create mode 100644 apps/task/sleep/test_cmd create mode 100644 apps/task/tls/Cargo.toml create mode 100644 apps/task/tls/expect_info_smp1_fifo.out create mode 100644 apps/task/tls/expect_info_smp4_rr.out create mode 100644 apps/task/tls/src/main.rs create mode 100644 apps/task/tls/test_cmd create mode 100644 apps/task/yield/Cargo.toml create mode 100644 apps/task/yield/expect_debug_smp1_fifo.out create mode 100644 apps/task/yield/expect_info_smp4_fifo.out create mode 100644 apps/task/yield/expect_info_smp4_rr.out create mode 100644 apps/task/yield/src/main.rs create mode 100644 apps/task/yield/test_cmd create mode 100644 crates/allocator/Cargo.toml create mode 100644 crates/allocator/benches/collections.rs create mode 100644 crates/allocator/benches/utils/mod.rs create mode 100644 crates/allocator/src/bitmap.rs create mode 100644 crates/allocator/src/buddy.rs create mode 100644 crates/allocator/src/lib.rs create mode 100644 crates/allocator/src/slab.rs create mode 100644 crates/allocator/src/tlsf.rs create mode 100644 crates/allocator/tests/allocator.rs create mode 100644 crates/arm_gic/Cargo.toml create mode 100644 crates/arm_gic/src/gic_v2.rs create mode 100644 crates/arm_gic/src/lib.rs create mode 100644 crates/arm_pl011/Cargo.toml create mode 100644 crates/arm_pl011/src/lib.rs create mode 100644 crates/arm_pl011/src/pl011.rs create mode 100644 crates/axerrno/.gitignore create mode 100644 crates/axerrno/Cargo.toml create mode 100644 crates/axerrno/build.rs create mode 100644 crates/axerrno/src/errno.h create mode 100644 crates/axerrno/src/lib.rs create mode 100644 crates/axfs_devfs/Cargo.toml create mode 100644 crates/axfs_devfs/src/dir.rs create mode 100644 crates/axfs_devfs/src/lib.rs create mode 100644 crates/axfs_devfs/src/null.rs create mode 100644 crates/axfs_devfs/src/tests.rs create mode 100644 crates/axfs_devfs/src/zero.rs create mode 100644 crates/axfs_ramfs/Cargo.toml create mode 100644 crates/axfs_ramfs/src/dir.rs create mode 100644 crates/axfs_ramfs/src/file.rs create mode 100644 crates/axfs_ramfs/src/lib.rs create mode 100644 crates/axfs_ramfs/src/tests.rs create mode 100644 crates/axfs_vfs/Cargo.toml create mode 100644 crates/axfs_vfs/src/lib.rs create mode 100644 crates/axfs_vfs/src/macros.rs create mode 100644 crates/axfs_vfs/src/path.rs create mode 100644 crates/axfs_vfs/src/structs.rs create mode 100644 crates/axio/Cargo.toml create mode 100644 crates/axio/src/buffered/bufreader.rs create mode 100644 crates/axio/src/buffered/mod.rs create mode 100644 crates/axio/src/error.rs create mode 100644 crates/axio/src/impls.rs create mode 100644 crates/axio/src/lib.rs create mode 100644 crates/axio/src/prelude.rs create mode 100644 crates/capability/Cargo.toml create mode 100644 crates/capability/src/lib.rs create mode 100644 crates/crate_interface/Cargo.toml create mode 100644 crates/crate_interface/README.md create mode 100644 crates/crate_interface/src/lib.rs create mode 100644 crates/crate_interface/tests/test_crate_interface.rs create mode 100644 crates/driver_block/Cargo.toml create mode 100644 crates/driver_block/src/bcm2835sdhci.rs create mode 100644 crates/driver_block/src/lib.rs create mode 100644 crates/driver_block/src/ramdisk.rs create mode 100644 crates/driver_common/Cargo.toml create mode 100644 crates/driver_common/src/lib.rs create mode 100644 crates/driver_display/Cargo.toml create mode 100644 crates/driver_display/src/lib.rs create mode 100644 crates/driver_net/Cargo.toml create mode 100644 crates/driver_net/src/ixgbe.rs create mode 100644 crates/driver_net/src/lib.rs create mode 100644 crates/driver_net/src/net_buf.rs create mode 100644 crates/driver_pci/Cargo.toml create mode 100644 crates/driver_pci/src/lib.rs create mode 100644 crates/driver_virtio/Cargo.toml create mode 100644 crates/driver_virtio/src/blk.rs create mode 100644 crates/driver_virtio/src/gpu.rs create mode 100644 crates/driver_virtio/src/lib.rs create mode 100644 crates/driver_virtio/src/net.rs create mode 100644 crates/dw_apb_uart/Cargo.toml create mode 100644 crates/dw_apb_uart/src/lib.rs create mode 100644 crates/flatten_objects/Cargo.toml create mode 100644 crates/flatten_objects/src/lib.rs create mode 100644 crates/handler_table/Cargo.toml create mode 100644 crates/handler_table/README.md create mode 100644 crates/handler_table/src/lib.rs create mode 100644 crates/kernel_guard/Cargo.toml create mode 100644 crates/kernel_guard/README.md create mode 100644 crates/kernel_guard/src/arch/aarch64.rs create mode 100644 crates/kernel_guard/src/arch/mod.rs create mode 100644 crates/kernel_guard/src/arch/riscv.rs create mode 100644 crates/kernel_guard/src/arch/x86.rs create mode 100644 crates/kernel_guard/src/lib.rs create mode 100644 crates/lazy_init/Cargo.toml create mode 100644 crates/lazy_init/src/lib.rs create mode 100644 crates/linked_list/Cargo.toml create mode 100644 crates/linked_list/src/lib.rs create mode 100644 crates/linked_list/src/linked_list.rs create mode 100644 crates/linked_list/src/unsafe_list.rs create mode 100644 crates/memory_addr/Cargo.toml create mode 100644 crates/memory_addr/README.md create mode 100644 crates/memory_addr/src/lib.rs create mode 100644 crates/page_table/Cargo.toml create mode 100644 crates/page_table/src/arch/aarch64.rs create mode 100644 crates/page_table/src/arch/mod.rs create mode 100644 crates/page_table/src/arch/riscv.rs create mode 100644 crates/page_table/src/arch/x86_64.rs create mode 100644 crates/page_table/src/bits64.rs create mode 100644 crates/page_table/src/lib.rs create mode 100644 crates/page_table_entry/Cargo.toml create mode 100644 crates/page_table_entry/src/arch/aarch64.rs create mode 100644 crates/page_table_entry/src/arch/mod.rs create mode 100644 crates/page_table_entry/src/arch/riscv.rs create mode 100644 crates/page_table_entry/src/arch/x86_64.rs create mode 100644 crates/page_table_entry/src/lib.rs create mode 100644 crates/percpu/Cargo.toml create mode 100644 crates/percpu/build.rs create mode 100644 crates/percpu/src/imp.rs create mode 100644 crates/percpu/src/lib.rs create mode 100644 crates/percpu/src/naive.rs create mode 100644 crates/percpu/test_percpu.x create mode 100644 crates/percpu/tests/test_percpu.rs create mode 100644 crates/percpu_macros/Cargo.toml create mode 100644 crates/percpu_macros/src/arch.rs create mode 100644 crates/percpu_macros/src/lib.rs create mode 100644 crates/percpu_macros/src/naive.rs create mode 100644 crates/ratio/Cargo.toml create mode 100644 crates/ratio/src/lib.rs create mode 100644 crates/scheduler/Cargo.toml create mode 100644 crates/scheduler/src/cfs.rs create mode 100644 crates/scheduler/src/fifo.rs create mode 100644 crates/scheduler/src/lib.rs create mode 100644 crates/scheduler/src/round_robin.rs create mode 100644 crates/scheduler/src/tests.rs create mode 100644 crates/slab_allocator/Cargo.toml create mode 100644 crates/slab_allocator/src/lib.rs create mode 100644 crates/slab_allocator/src/slab.rs create mode 100644 crates/slab_allocator/src/tests.rs create mode 100644 crates/spinlock/Cargo.toml create mode 100644 crates/spinlock/src/base.rs create mode 100644 crates/spinlock/src/lib.rs create mode 100644 crates/timer_list/Cargo.toml create mode 100644 crates/timer_list/src/lib.rs create mode 100644 crates/tuple_for_each/Cargo.toml create mode 100644 crates/tuple_for_each/src/lib.rs create mode 100644 crates/tuple_for_each/tests/test_tuple_for_each.rs create mode 100644 doc/README.md create mode 100644 doc/apps_display.md create mode 100644 doc/apps_echoserver.md create mode 100644 doc/apps_exception.md create mode 100644 doc/apps_fs_shell.md create mode 100644 doc/apps_helloworld.md create mode 100644 doc/apps_httpserver.md create mode 100644 doc/apps_memtest.md create mode 100644 doc/apps_net-httpclient.md create mode 100644 doc/apps_parallel.md create mode 100644 doc/apps_priority.md create mode 100644 doc/apps_sleep.md create mode 100644 doc/apps_yield.md create mode 100644 doc/build.md create mode 100644 doc/figures/ArceOS.svg create mode 100644 doc/figures/display.png create mode 100644 doc/init.md create mode 100644 doc/ixgbe.md create mode 100644 doc/platform_raspi4.md create mode 100644 modules/axalloc/Cargo.toml create mode 100644 modules/axalloc/src/lib.rs create mode 100644 modules/axalloc/src/page.rs create mode 100644 modules/axconfig/Cargo.toml create mode 100644 modules/axconfig/build.rs create mode 100644 modules/axconfig/defconfig.toml create mode 100644 modules/axconfig/src/lib.rs create mode 100644 modules/axdisplay/Cargo.toml create mode 100644 modules/axdisplay/src/lib.rs create mode 100644 modules/axdriver/Cargo.toml create mode 100644 modules/axdriver/build.rs create mode 100644 modules/axdriver/src/bus/mmio.rs create mode 100644 modules/axdriver/src/bus/mod.rs create mode 100644 modules/axdriver/src/bus/pci.rs create mode 100644 modules/axdriver/src/drivers.rs create mode 100644 modules/axdriver/src/dummy.rs create mode 100644 modules/axdriver/src/ixgbe.rs create mode 100644 modules/axdriver/src/lib.rs create mode 100644 modules/axdriver/src/macros.rs create mode 100644 modules/axdriver/src/prelude.rs create mode 100644 modules/axdriver/src/structs/dyn.rs create mode 100644 modules/axdriver/src/structs/mod.rs create mode 100644 modules/axdriver/src/structs/static.rs create mode 100644 modules/axdriver/src/virtio.rs create mode 100644 modules/axfs/Cargo.toml create mode 100755 modules/axfs/resources/create_test_img.sh create mode 100644 modules/axfs/resources/fat16.img create mode 100644 modules/axfs/src/api/dir.rs create mode 100644 modules/axfs/src/api/file.rs create mode 100644 modules/axfs/src/api/mod.rs create mode 100644 modules/axfs/src/dev.rs create mode 100644 modules/axfs/src/fops.rs create mode 100644 modules/axfs/src/fs/fatfs.rs create mode 100644 modules/axfs/src/fs/mod.rs create mode 100644 modules/axfs/src/fs/myfs.rs create mode 100644 modules/axfs/src/lib.rs create mode 100644 modules/axfs/src/mounts.rs create mode 100644 modules/axfs/src/root.rs create mode 100644 modules/axfs/tests/test_common/mod.rs create mode 100644 modules/axfs/tests/test_fatfs.rs create mode 100644 modules/axfs/tests/test_ramfs.rs create mode 100644 modules/axhal/.gitignore create mode 100644 modules/axhal/Cargo.toml create mode 100644 modules/axhal/build.rs create mode 100644 modules/axhal/linker.lds.S create mode 100644 modules/axhal/src/arch/aarch64/context.rs create mode 100644 modules/axhal/src/arch/aarch64/mod.rs create mode 100644 modules/axhal/src/arch/aarch64/trap.S create mode 100644 modules/axhal/src/arch/aarch64/trap.rs create mode 100644 modules/axhal/src/arch/mod.rs create mode 100644 modules/axhal/src/arch/riscv/context.rs create mode 100644 modules/axhal/src/arch/riscv/macros.rs create mode 100644 modules/axhal/src/arch/riscv/mod.rs create mode 100644 modules/axhal/src/arch/riscv/trap.S create mode 100644 modules/axhal/src/arch/riscv/trap.rs create mode 100644 modules/axhal/src/arch/x86_64/context.rs create mode 100644 modules/axhal/src/arch/x86_64/gdt.rs create mode 100644 modules/axhal/src/arch/x86_64/idt.rs create mode 100644 modules/axhal/src/arch/x86_64/mod.rs create mode 100644 modules/axhal/src/arch/x86_64/trap.S create mode 100644 modules/axhal/src/arch/x86_64/trap.rs create mode 100644 modules/axhal/src/cpu.rs create mode 100644 modules/axhal/src/irq.rs create mode 100644 modules/axhal/src/lib.rs create mode 100644 modules/axhal/src/mem.rs create mode 100644 modules/axhal/src/paging.rs create mode 100644 modules/axhal/src/platform/aarch64_bsta1000b/dw_apb_uart.rs create mode 100644 modules/axhal/src/platform/aarch64_bsta1000b/mem.rs create mode 100644 modules/axhal/src/platform/aarch64_bsta1000b/misc.rs create mode 100644 modules/axhal/src/platform/aarch64_bsta1000b/mod.rs create mode 100644 modules/axhal/src/platform/aarch64_bsta1000b/mp.rs create mode 100644 modules/axhal/src/platform/aarch64_common/boot.rs create mode 100644 modules/axhal/src/platform/aarch64_common/generic_timer.rs create mode 100644 modules/axhal/src/platform/aarch64_common/gic.rs create mode 100644 modules/axhal/src/platform/aarch64_common/mod.rs create mode 100644 modules/axhal/src/platform/aarch64_common/pl011.rs create mode 100644 modules/axhal/src/platform/aarch64_common/psci.rs create mode 100644 modules/axhal/src/platform/aarch64_qemu_virt/mem.rs create mode 100644 modules/axhal/src/platform/aarch64_qemu_virt/mod.rs create mode 100644 modules/axhal/src/platform/aarch64_qemu_virt/mp.rs create mode 100644 modules/axhal/src/platform/aarch64_raspi/mem.rs create mode 100644 modules/axhal/src/platform/aarch64_raspi/mod.rs create mode 100644 modules/axhal/src/platform/aarch64_raspi/mp.rs create mode 100644 modules/axhal/src/platform/dummy/mod.rs create mode 100644 modules/axhal/src/platform/mod.rs create mode 100644 modules/axhal/src/platform/riscv64_qemu_virt/boot.rs create mode 100644 modules/axhal/src/platform/riscv64_qemu_virt/console.rs create mode 100644 modules/axhal/src/platform/riscv64_qemu_virt/irq.rs create mode 100644 modules/axhal/src/platform/riscv64_qemu_virt/mem.rs create mode 100644 modules/axhal/src/platform/riscv64_qemu_virt/misc.rs create mode 100644 modules/axhal/src/platform/riscv64_qemu_virt/mod.rs create mode 100644 modules/axhal/src/platform/riscv64_qemu_virt/mp.rs create mode 100644 modules/axhal/src/platform/riscv64_qemu_virt/time.rs create mode 100644 modules/axhal/src/platform/x86_pc/ap_start.S create mode 100644 modules/axhal/src/platform/x86_pc/apic.rs create mode 100644 modules/axhal/src/platform/x86_pc/boot.rs create mode 100644 modules/axhal/src/platform/x86_pc/dtables.rs create mode 100644 modules/axhal/src/platform/x86_pc/mem.rs create mode 100644 modules/axhal/src/platform/x86_pc/misc.rs create mode 100644 modules/axhal/src/platform/x86_pc/mod.rs create mode 100644 modules/axhal/src/platform/x86_pc/mp.rs create mode 100644 modules/axhal/src/platform/x86_pc/multiboot.S create mode 100644 modules/axhal/src/platform/x86_pc/time.rs create mode 100644 modules/axhal/src/platform/x86_pc/uart16550.rs create mode 100644 modules/axhal/src/time.rs create mode 100644 modules/axhal/src/tls.rs create mode 100644 modules/axhal/src/trap.rs create mode 100644 modules/axlog/Cargo.toml create mode 100644 modules/axlog/src/lib.rs create mode 100644 modules/axnet/Cargo.toml create mode 100644 modules/axnet/src/lib.rs create mode 100644 modules/axnet/src/smoltcp_impl/addr.rs create mode 100644 modules/axnet/src/smoltcp_impl/bench.rs create mode 100644 modules/axnet/src/smoltcp_impl/dns.rs create mode 100644 modules/axnet/src/smoltcp_impl/listen_table.rs create mode 100644 modules/axnet/src/smoltcp_impl/mod.rs create mode 100644 modules/axnet/src/smoltcp_impl/tcp.rs create mode 100644 modules/axnet/src/smoltcp_impl/udp.rs create mode 100644 modules/axruntime/Cargo.toml create mode 100644 modules/axruntime/src/lang_items.rs create mode 100644 modules/axruntime/src/lib.rs create mode 100644 modules/axruntime/src/mp.rs create mode 100644 modules/axruntime/src/trap.rs create mode 100644 modules/axsync/Cargo.toml create mode 100644 modules/axsync/src/lib.rs create mode 100644 modules/axsync/src/mutex.rs create mode 100644 modules/axtask/Cargo.toml create mode 100644 modules/axtask/src/api.rs create mode 100644 modules/axtask/src/api_s.rs create mode 100644 modules/axtask/src/lib.rs create mode 100644 modules/axtask/src/run_queue.rs create mode 100644 modules/axtask/src/task.rs create mode 100644 modules/axtask/src/tests.rs create mode 100644 modules/axtask/src/timers.rs create mode 100644 modules/axtask/src/wait_queue.rs create mode 100644 platforms/aarch64-bsta1000b.toml create mode 100644 platforms/aarch64-qemu-virt.toml create mode 100644 platforms/aarch64-raspi4.toml create mode 100644 platforms/riscv64-qemu-virt.toml create mode 100644 platforms/x86_64-pc-oslab.toml create mode 100644 platforms/x86_64-qemu-q35.toml create mode 100644 rust-toolchain.toml create mode 100644 scripts/make/bsta1000b-fada.mk create mode 100644 scripts/make/build.mk create mode 100644 scripts/make/build_c.mk create mode 100644 scripts/make/cargo.mk create mode 100644 scripts/make/features.mk create mode 100644 scripts/make/qemu.mk create mode 100644 scripts/make/raspi4.mk create mode 100644 scripts/make/test.mk create mode 100644 scripts/make/utils.mk create mode 100755 scripts/net/qemu-tap-ifdown.sh create mode 100755 scripts/net/qemu-tap-ifup.sh create mode 100755 scripts/test/app_test.sh create mode 100644 tools/.gitignore create mode 100755 tools/bsta1000b/bsta1000b-fada-arceos.its create mode 100644 tools/bsta1000b/bsta1000b-fada.dtb create mode 100644 tools/bwbench_client/Cargo.toml create mode 100644 tools/bwbench_client/README.md create mode 100644 tools/bwbench_client/src/device.rs create mode 100644 tools/bwbench_client/src/main.rs create mode 100644 tools/deptool/Cargo.toml create mode 100644 tools/deptool/Makefile create mode 100644 tools/deptool/README.md create mode 100644 tools/deptool/src/cmd_builder.rs create mode 100644 tools/deptool/src/cmd_parser.rs create mode 100644 tools/deptool/src/d2_generator.rs create mode 100644 tools/deptool/src/lib.rs create mode 100644 tools/deptool/src/main.rs create mode 100644 tools/deptool/src/mermaid_generator.rs create mode 100644 tools/raspi4/common/docker.mk create mode 100644 tools/raspi4/common/format.mk create mode 100644 tools/raspi4/common/operating_system.mk create mode 100755 tools/raspi4/common/serial/minipush.rb create mode 100644 tools/raspi4/common/serial/minipush/progressbar_patch.rb create mode 100755 tools/raspi4/common/serial/miniterm.rb create mode 100644 ulib/axlibc/.gitignore create mode 100644 ulib/axlibc/Cargo.toml create mode 100644 ulib/axlibc/build.rs create mode 100644 ulib/axlibc/c/assert.c create mode 100644 ulib/axlibc/c/ctype.c create mode 100644 ulib/axlibc/c/dirent.c create mode 100644 ulib/axlibc/c/dlfcn.c create mode 100644 ulib/axlibc/c/env.c create mode 100644 ulib/axlibc/c/fcntl.c create mode 100644 ulib/axlibc/c/flock.c create mode 100644 ulib/axlibc/c/fnmatch.c create mode 100644 ulib/axlibc/c/glob.c create mode 100644 ulib/axlibc/c/ioctl.c create mode 100644 ulib/axlibc/c/libgen.c create mode 100644 ulib/axlibc/c/libm.c create mode 100644 ulib/axlibc/c/libm.h create mode 100644 ulib/axlibc/c/locale.c create mode 100644 ulib/axlibc/c/log.c create mode 100644 ulib/axlibc/c/math.c create mode 100644 ulib/axlibc/c/mmap.c create mode 100644 ulib/axlibc/c/network.c create mode 100644 ulib/axlibc/c/poll.c create mode 100644 ulib/axlibc/c/pow.c create mode 100644 ulib/axlibc/c/printf.c create mode 100644 ulib/axlibc/c/printf.h create mode 100644 ulib/axlibc/c/printf_config.h create mode 100644 ulib/axlibc/c/pthread.c create mode 100644 ulib/axlibc/c/pwd.c create mode 100644 ulib/axlibc/c/resource.c create mode 100644 ulib/axlibc/c/sched.c create mode 100644 ulib/axlibc/c/select.c create mode 100644 ulib/axlibc/c/signal.c create mode 100644 ulib/axlibc/c/socket.c create mode 100644 ulib/axlibc/c/stat.c create mode 100644 ulib/axlibc/c/stdio.c create mode 100644 ulib/axlibc/c/stdlib.c create mode 100644 ulib/axlibc/c/string.c create mode 100644 ulib/axlibc/c/syslog.c create mode 100644 ulib/axlibc/c/time.c create mode 100644 ulib/axlibc/c/unistd.c create mode 100644 ulib/axlibc/c/utsname.c create mode 100644 ulib/axlibc/c/wait.c create mode 100644 ulib/axlibc/ctypes.h create mode 100644 ulib/axlibc/include/arpa/inet.h create mode 100644 ulib/axlibc/include/assert.h create mode 100644 ulib/axlibc/include/ctype.h create mode 100644 ulib/axlibc/include/dirent.h create mode 100644 ulib/axlibc/include/dlfcn.h create mode 100644 ulib/axlibc/include/endian.h create mode 100644 ulib/axlibc/include/errno.h create mode 100644 ulib/axlibc/include/fcntl.h create mode 100644 ulib/axlibc/include/features.h create mode 100644 ulib/axlibc/include/float.h create mode 100644 ulib/axlibc/include/fnmatch.h create mode 100644 ulib/axlibc/include/glob.h create mode 100644 ulib/axlibc/include/inttypes.h create mode 100644 ulib/axlibc/include/langinfo.h create mode 100644 ulib/axlibc/include/libgen.h create mode 100644 ulib/axlibc/include/limits.h create mode 100644 ulib/axlibc/include/locale.h create mode 100644 ulib/axlibc/include/math.h create mode 100644 ulib/axlibc/include/memory.h create mode 100644 ulib/axlibc/include/netdb.h create mode 100644 ulib/axlibc/include/netinet/in.h create mode 100644 ulib/axlibc/include/netinet/tcp.h create mode 100644 ulib/axlibc/include/poll.h create mode 100644 ulib/axlibc/include/pthread.h create mode 100644 ulib/axlibc/include/pwd.h create mode 100644 ulib/axlibc/include/regex.h create mode 100644 ulib/axlibc/include/sched.h create mode 100644 ulib/axlibc/include/setjmp.h create mode 100644 ulib/axlibc/include/signal.h create mode 100644 ulib/axlibc/include/stdarg.h create mode 100644 ulib/axlibc/include/stdbool.h create mode 100644 ulib/axlibc/include/stddef.h create mode 100644 ulib/axlibc/include/stdint.h create mode 100644 ulib/axlibc/include/stdio.h create mode 100644 ulib/axlibc/include/stdlib.h create mode 100644 ulib/axlibc/include/string.h create mode 100644 ulib/axlibc/include/strings.h create mode 100644 ulib/axlibc/include/sys/epoll.h create mode 100644 ulib/axlibc/include/sys/file.h create mode 100644 ulib/axlibc/include/sys/ioctl.h create mode 100644 ulib/axlibc/include/sys/mman.h create mode 100644 ulib/axlibc/include/sys/param.h create mode 100644 ulib/axlibc/include/sys/prctl.h create mode 100644 ulib/axlibc/include/sys/resource.h create mode 100644 ulib/axlibc/include/sys/select.h create mode 100644 ulib/axlibc/include/sys/socket.h create mode 100644 ulib/axlibc/include/sys/stat.h create mode 100644 ulib/axlibc/include/sys/time.h create mode 100644 ulib/axlibc/include/sys/types.h create mode 100644 ulib/axlibc/include/sys/uio.h create mode 100644 ulib/axlibc/include/sys/un.h create mode 100644 ulib/axlibc/include/sys/utsname.h create mode 100644 ulib/axlibc/include/sys/wait.h create mode 100644 ulib/axlibc/include/syslog.h create mode 100644 ulib/axlibc/include/termios.h create mode 100644 ulib/axlibc/include/time.h create mode 100644 ulib/axlibc/include/unistd.h create mode 100644 ulib/axlibc/src/errno.rs create mode 100644 ulib/axlibc/src/fd_ops.rs create mode 100644 ulib/axlibc/src/fs.rs create mode 100644 ulib/axlibc/src/io.rs create mode 100644 ulib/axlibc/src/io_mpx.rs create mode 100644 ulib/axlibc/src/lib.rs create mode 100644 ulib/axlibc/src/malloc.rs create mode 100644 ulib/axlibc/src/mktime.rs create mode 100644 ulib/axlibc/src/net.rs create mode 100644 ulib/axlibc/src/pipe.rs create mode 100644 ulib/axlibc/src/pthread.rs create mode 100644 ulib/axlibc/src/rand.rs create mode 100644 ulib/axlibc/src/resource.rs create mode 100644 ulib/axlibc/src/setjmp.rs create mode 100644 ulib/axlibc/src/strftime.rs create mode 100644 ulib/axlibc/src/strtod.rs create mode 100644 ulib/axlibc/src/sys.rs create mode 100644 ulib/axlibc/src/time.rs create mode 100644 ulib/axlibc/src/unistd.rs create mode 100644 ulib/axlibc/src/utils.rs create mode 100644 ulib/axstd/Cargo.toml create mode 100644 ulib/axstd/src/env.rs create mode 100644 ulib/axstd/src/fs/dir.rs create mode 100644 ulib/axstd/src/fs/file.rs create mode 100644 ulib/axstd/src/fs/mod.rs create mode 100644 ulib/axstd/src/io/mod.rs create mode 100644 ulib/axstd/src/io/stdio.rs create mode 100644 ulib/axstd/src/lib.rs create mode 100644 ulib/axstd/src/macros.rs create mode 100644 ulib/axstd/src/net/mod.rs create mode 100644 ulib/axstd/src/net/socket_addr.rs create mode 100644 ulib/axstd/src/net/tcp.rs create mode 100644 ulib/axstd/src/net/udp.rs create mode 100644 ulib/axstd/src/os.rs create mode 100644 ulib/axstd/src/process.rs create mode 100644 ulib/axstd/src/sync/mod.rs create mode 100644 ulib/axstd/src/sync/mutex.rs create mode 100644 ulib/axstd/src/thread/mod.rs create mode 100644 ulib/axstd/src/thread/multi.rs create mode 100644 ulib/axstd/src/time.rs diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..10aa9f00c --- /dev/null +++ b/.clang-format @@ -0,0 +1,16 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +IndentWidth: 4 +ColumnLimit: 100 +AllowShortBlocksOnASingleLine: Empty +AllowShortFunctionsOnASingleLine: Inline +AllowShortLoopsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: Never +AlignConsecutiveMacros: true +AlignEscapedNewlines: Left +BreakBeforeBraces: Custom +BraceWrapping: + AfterFunction: true +PointerAlignment: Right +--- diff --git a/.github/workflows/actions/setup-musl/action.yml b/.github/workflows/actions/setup-musl/action.yml new file mode 100644 index 000000000..b8aee3661 --- /dev/null +++ b/.github/workflows/actions/setup-musl/action.yml @@ -0,0 +1,38 @@ +name: Download musl toolchain + +inputs: + arch: + description: 'Architecture' + required: true + type: string + +runs: + using: "composite" + steps: + - name: Cache musl + id: cache-musl + uses: actions/cache/restore@v3 + with: + path: ${{ inputs.arch }}-linux-musl-cross + key: ${{ inputs.arch }}-linux-musl-cross + - name: Download musl toolchain + if: steps.cache-musl.outputs.cache-hit != 'true' + shell: bash + run: | + MUSL_PATH=${{ inputs.arch }}-linux-musl-cross + wget https://musl.cc/${MUSL_PATH}.tgz + tar -xf ${MUSL_PATH}.tgz + - uses: actions/cache/save@v3 + if: steps.cache-musl.outputs.cache-hit != 'true' + with: + path: ${{ inputs.arch }}-linux-musl-cross + key: ${{ inputs.arch }}-linux-musl-cross + + - name: Add to PATH environment variable + shell: bash + run: | + echo "$PWD/${{ inputs.arch }}-linux-musl-cross/bin" >> $GITHUB_PATH + - name: Verify installation + shell: bash + run: | + ${{ inputs.arch }}-linux-musl-gcc --version diff --git a/.github/workflows/actions/setup-qemu/action.yml b/.github/workflows/actions/setup-qemu/action.yml new file mode 100644 index 000000000..8ac690e6d --- /dev/null +++ b/.github/workflows/actions/setup-qemu/action.yml @@ -0,0 +1,46 @@ +name: Download and build QEMU + +inputs: + qemu-version: + description: 'QEMU version' + required: true + type: string + +runs: + using: "composite" + steps: + - name: Cache QEMU + id: cache-qemu + uses: actions/cache/restore@v3 + with: + path: qemu_build + key: qemu-${{ inputs.qemu-version }} + - name: Download and build QEMU + if: steps.cache-qemu.outputs.cache-hit != 'true' + env: + QEMU_PATH: qemu-${{ inputs.qemu-version }} + PREFIX: ${{ github.workspace }}/qemu_build + shell: bash + run: | + sudo apt-get update && sudo apt-get install -y ninja-build + wget https://download.qemu.org/$QEMU_PATH.tar.xz && tar -xJf $QEMU_PATH.tar.xz + cd $QEMU_PATH \ + && ./configure --prefix=$PREFIX --target-list=x86_64-softmmu,riscv64-softmmu,aarch64-softmmu \ + && make -j > /dev/null 2>&1 \ + && make install + - uses: actions/cache/save@v3 + if: steps.cache-qemu.outputs.cache-hit != 'true' + with: + path: qemu_build + key: qemu-${{ inputs.qemu-version }} + + - name: Install QEMU + shell: bash + run: | + echo "$PWD/qemu_build/bin" >> $GITHUB_PATH + - name: Verify installation + shell: bash + run: | + qemu-system-x86_64 --version + qemu-system-aarch64 --version + qemu-system-riscv64 --version diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..70f374757 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,176 @@ +name: Build CI + +on: [push, pull_request] + +env: + rust-toolchain: nightly + +jobs: + clippy: + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ env.rust-toolchain }} + components: rust-src, clippy, rustfmt + - name: Clippy for the default target + run: make clippy + - name: Clippy for x86_64 + run: make clippy ARCH=x86_64 + - name: Clippy for riscv64 + run: make clippy ARCH=riscv64 + - name: Clippy for aarch64 + run: make clippy ARCH=aarch64 + - name: Check code format + run: cargo fmt --all -- --check + + build: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + arch: [x86_64, riscv64, aarch64] + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ env.rust-toolchain }} + components: rust-src + - uses: actions-rs/install@v0.1 + with: + crate: cargo-binutils + version: latest + use-tool-cache: true + - name: Build helloworld + run: make ARCH=${{ matrix.arch }} A=apps/helloworld + - name: Build memtest + run: make ARCH=${{ matrix.arch }} A=apps/memtest + - name: Build exception + run: make ARCH=${{ matrix.arch }} A=apps/exception + - name: Build display + run: make ARCH=${{ matrix.arch }} A=apps/display + - name: Build task/yield + run: make ARCH=${{ matrix.arch }} A=apps/task/yield + - name: Build task/parallel + run: make ARCH=${{ matrix.arch }} A=apps/task/parallel + - name: Build task/sleep + run: make ARCH=${{ matrix.arch }} A=apps/task/sleep + - name: Build task/priority + run: make ARCH=${{ matrix.arch }} A=apps/task/priority + - name: Build task/tls + run: make ARCH=${{ matrix.arch }} A=apps/task/tls + - name: Build fs/shell + run: make ARCH=${{ matrix.arch }} A=apps/fs/shell + - name: Build net/echoserver + run: make ARCH=${{ matrix.arch }} A=apps/net/echoserver + - name: Build net/httpclient + run: make ARCH=${{ matrix.arch }} A=apps/net/httpclient + - name: Build net/httpserver + run: make ARCH=${{ matrix.arch }} A=apps/net/httpserver + - name: Build net/udpserver + run: make ARCH=${{ matrix.arch }} A=apps/net/udpserver + + - uses: ./.github/workflows/actions/setup-musl + with: + arch: ${{ matrix.arch }} + - name: Build c/helloworld + run: make ARCH=${{ matrix.arch }} A=apps/c/helloworld + - name: Build c/memtest + run: make ARCH=${{ matrix.arch }} A=apps/c/memtest + - name: Build c/sqlite3 + run: make ARCH=${{ matrix.arch }} A=apps/c/sqlite3 + - name: Build c/httpclient + run: make ARCH=${{ matrix.arch }} A=apps/c/httpclient + - name: Build c/httpserver + run: make ARCH=${{ matrix.arch }} A=apps/c/httpserver + - name: Build c/udpserver + run: make ARCH=${{ matrix.arch }} A=apps/c/udpserver + - name: Build c/iperf + run: make ARCH=${{ matrix.arch }} A=apps/c/iperf + - name: Build c/redis + run: make ARCH=${{ matrix.arch }} A=apps/c/redis SMP=4 + + build-apps-for-other-platforms: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ env.rust-toolchain }} + components: rust-src + - uses: actions-rs/install@v0.1 + with: + crate: cargo-binutils + version: latest + use-tool-cache: true + - uses: ./.github/workflows/actions/setup-musl + with: + arch: x86_64 + + - name: Build helloworld for x86_64-pc-oslab + run: make PLATFORM=x86_64-pc-oslab A=apps/helloworld + - name: Build net/httpserver for x86_64-pc-oslab + run: make PLATFORM=x86_64-pc-oslab A=apps/net/httpserver FEATURES=driver-ixgbe + - name: Build c/iperf for x86_64-pc-oslab + run: make PLATFORM=x86_64-pc-oslab A=apps/c/iperf FEATURES=driver-ixgbe,driver-ramdisk + - name: Build c/redis for x86_64-pc-oslab + run: make PLATFORM=x86_64-pc-oslab A=apps/c/redis FEATURES=driver-ixgbe,driver-ramdisk SMP=4 + + - name: Build helloworld for aarch64-raspi4 + run: make PLATFORM=aarch64-raspi4 A=apps/helloworld + - name: Build fs/shell for aarch64-raspi4 + run: make PLATFORM=aarch64-raspi4 A=apps/fs/shell FEATURES=driver-bcm2835-sdhci + + - name: Build helloworld for aarch64-bsta1000b + run: make PLATFORM=aarch64-bsta1000b A=apps/helloworld + + build-apps-for-std: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + arch: [x86_64] + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ env.rust-toolchain }} + - name: Build helloworld + run: cargo build -p arceos-helloworld + - name: Build memtest + run: cargo build -p arceos-memtest + - name: Build exception + run: cargo build -p arceos-exception + - name: Build task/yield + run: cargo build -p arceos-yield + - name: Build task/parallel + run: cargo build -p arceos-parallel + - name: Build task/sleep + run: cargo build -p arceos-sleep + - name: Build task/priority + run: cargo build -p arceos-priority + - name: Build task/tls + run: cargo build -p arceos-tls + - name: Build fs/shell + run: cargo build -p arceos-shell + - name: Build net/echoserver + run: cargo build -p arceos-echoserver + - name: Build net/httpclient + run: cargo build -p arceos-httpclient + - name: Build net/httpserver + run: cargo build -p arceos-httpserver + - name: Build net/udpserver + run: cargo build -p arceos-udpserver diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..fdd060d05 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,32 @@ +name: Build & Deploy docs + +on: [push, pull_request] + +env: + rust-toolchain: nightly + +jobs: + doc: + runs-on: ubuntu-latest + strategy: + fail-fast: false + permissions: + contents: write + env: + default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }} + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ env.rust-toolchain }} + - name: Build docs + continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} + run: make doc_check_missing + - name: Deploy to Github Pages + if: ${{ github.ref == env.default-branch }} + uses: JamesIves/github-pages-deploy-action@v4 + with: + single-commit: true + branch: gh-pages + folder: target/doc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..73e36bc02 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,50 @@ +name: Test CI + +on: [push, pull_request] + +env: + qemu-version: 7.1.0 + rust-toolchain: nightly + +jobs: + unit-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ env.rust-toolchain }} + components: rust-src + - name: Run unit tests + run: make unittest_no_fail_fast + + app-test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + arch: [x86_64, riscv64, aarch64] + steps: + - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ env.rust-toolchain }} + components: rust-src + - uses: actions-rs/install@v0.1 + with: + crate: cargo-binutils + version: latest + use-tool-cache: true + - uses: ./.github/workflows/actions/setup-qemu + with: + qemu-version: ${{ env.qemu-version }} + - uses: ./.github/workflows/actions/setup-musl + with: + arch: ${{ matrix.arch }} + - name: Run app tests + run: | + make disk_img + make test ARCH=${{ matrix.arch }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..9d91ac572 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/target +/.vscode +.DS_Store +*.asm +*.img +actual.out +qemu.log +rusty-tags.vi diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..b305bb432 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2191 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aarch64-cpu" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb711c57d60565ba8f6523eb371e6243639617d817b4df1ae09af250af1af411" +dependencies = [ + "tock-registers", +] + +[[package]] +name = "aho-corasick" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator" +version = "0.1.0" +dependencies = [ + "allocator", + "bitmap-allocator", + "buddy_system_allocator", + "criterion", + "rand", + "rlsf", + "slab_allocator", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "arceos-bwbench" +version = "0.1.0" +dependencies = [ + "axnet", + "axstd", +] + +[[package]] +name = "arceos-display" +version = "0.1.0" +dependencies = [ + "axstd", + "embedded-graphics", +] + +[[package]] +name = "arceos-echoserver" +version = "0.1.0" +dependencies = [ + "axstd", +] + +[[package]] +name = "arceos-exception" +version = "0.1.0" +dependencies = [ + "axstd", +] + +[[package]] +name = "arceos-helloworld" +version = "0.1.0" +dependencies = [ + "axstd", +] + +[[package]] +name = "arceos-httpclient" +version = "0.1.0" +dependencies = [ + "axstd", +] + +[[package]] +name = "arceos-httpserver" +version = "0.1.0" +dependencies = [ + "axstd", +] + +[[package]] +name = "arceos-memtest" +version = "0.1.0" +dependencies = [ + "axstd", + "rand", +] + +[[package]] +name = "arceos-parallel" +version = "0.1.0" +dependencies = [ + "axstd", + "rand", +] + +[[package]] +name = "arceos-priority" +version = "0.1.0" +dependencies = [ + "axstd", +] + +[[package]] +name = "arceos-shell" +version = "0.1.0" +dependencies = [ + "axfs_ramfs", + "axfs_vfs", + "axstd", + "crate_interface", +] + +[[package]] +name = "arceos-sleep" +version = "0.1.0" +dependencies = [ + "axstd", +] + +[[package]] +name = "arceos-tls" +version = "0.1.0" +dependencies = [ + "axstd", +] + +[[package]] +name = "arceos-udpserver" +version = "0.1.0" +dependencies = [ + "axstd", +] + +[[package]] +name = "arceos-yield" +version = "0.1.0" +dependencies = [ + "axstd", +] + +[[package]] +name = "arceos_api" +version = "0.1.0" +dependencies = [ + "axalloc", + "axconfig", + "axdisplay", + "axerrno", + "axfeat", + "axfs", + "axhal", + "axio", + "axlog", + "axnet", + "axruntime", + "axtask", +] + +[[package]] +name = "arceos_posix_api" +version = "0.1.0" +dependencies = [ + "axalloc", + "axconfig", + "axerrno", + "axfeat", + "axfs", + "axhal", + "axio", + "axlog", + "axnet", + "axruntime", + "axsync", + "axtask", + "bindgen", + "flatten_objects", + "lazy_static", + "spin 0.9.8", + "static_assertions", +] + +[[package]] +name = "arm_gic" +version = "0.1.0" +dependencies = [ + "tock-registers", +] + +[[package]] +name = "arm_pl011" +version = "0.1.0" +dependencies = [ + "tock-registers", +] + +[[package]] +name = "atomic-polyfill" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axalloc" +version = "0.1.0" +dependencies = [ + "allocator", + "axerrno", + "cfg-if", + "log", + "memory_addr", + "spinlock", +] + +[[package]] +name = "axconfig" +version = "0.1.0" +dependencies = [ + "serde", + "toml_edit", +] + +[[package]] +name = "axdisplay" +version = "0.1.0" +dependencies = [ + "axdriver", + "axsync", + "driver_display", + "lazy_init", + "log", +] + +[[package]] +name = "axdriver" +version = "0.1.0" +dependencies = [ + "axalloc", + "axconfig", + "axhal", + "cfg-if", + "driver_block", + "driver_common", + "driver_display", + "driver_net", + "driver_pci", + "driver_virtio", + "log", +] + +[[package]] +name = "axerrno" +version = "0.1.0" +dependencies = [ + "log", +] + +[[package]] +name = "axfeat" +version = "0.1.0" +dependencies = [ + "axalloc", + "axdisplay", + "axdriver", + "axfs", + "axhal", + "axlog", + "axnet", + "axruntime", + "axsync", + "axtask", + "spinlock", +] + +[[package]] +name = "axfs" +version = "0.1.0" +dependencies = [ + "axdriver", + "axerrno", + "axfs_devfs", + "axfs_ramfs", + "axfs_vfs", + "axio", + "axsync", + "axtask", + "capability", + "cfg-if", + "crate_interface", + "driver_block", + "fatfs", + "lazy_init", + "log", +] + +[[package]] +name = "axfs_devfs" +version = "0.1.0" +dependencies = [ + "axfs_vfs", + "log", + "spin 0.9.8", +] + +[[package]] +name = "axfs_ramfs" +version = "0.1.0" +dependencies = [ + "axfs_vfs", + "log", + "spin 0.9.8", +] + +[[package]] +name = "axfs_vfs" +version = "0.1.0" +dependencies = [ + "axerrno", + "bitflags 2.4.0", + "log", +] + +[[package]] +name = "axhal" +version = "0.1.0" +dependencies = [ + "aarch64-cpu", + "arm_gic", + "arm_pl011", + "axalloc", + "axconfig", + "axlog", + "bitflags 2.4.0", + "cfg-if", + "crate_interface", + "dw_apb_uart", + "handler_table", + "kernel_guard", + "lazy_init", + "log", + "memory_addr", + "page_table", + "page_table_entry", + "percpu", + "ratio", + "raw-cpuid 11.0.1", + "riscv", + "sbi-rt", + "spinlock", + "static_assertions", + "tock-registers", + "x2apic", + "x86", + "x86_64", +] + +[[package]] +name = "axio" +version = "0.1.0" +dependencies = [ + "axerrno", +] + +[[package]] +name = "axlibc" +version = "0.1.0" +dependencies = [ + "arceos_posix_api", + "axerrno", + "axfeat", + "axio", + "bindgen", +] + +[[package]] +name = "axlog" +version = "0.1.0" +dependencies = [ + "axlog", + "cfg-if", + "chrono", + "crate_interface", + "log", + "spinlock", +] + +[[package]] +name = "axnet" +version = "0.1.0" +dependencies = [ + "axdriver", + "axerrno", + "axhal", + "axio", + "axsync", + "axtask", + "cfg-if", + "driver_net", + "lazy_init", + "log", + "smoltcp", + "spin 0.9.8", +] + +[[package]] +name = "axruntime" +version = "0.1.0" +dependencies = [ + "axalloc", + "axconfig", + "axdisplay", + "axdriver", + "axfs", + "axhal", + "axlog", + "axnet", + "axtask", + "crate_interface", + "kernel_guard", + "lazy_init", + "percpu", +] + +[[package]] +name = "axstd" +version = "0.1.0" +dependencies = [ + "arceos_api", + "axerrno", + "axfeat", + "axio", + "spinlock", +] + +[[package]] +name = "axsync" +version = "0.1.0" +dependencies = [ + "axsync", + "axtask", + "rand", + "spinlock", +] + +[[package]] +name = "axtask" +version = "0.1.0" +dependencies = [ + "axconfig", + "axhal", + "axtask", + "cfg-if", + "crate_interface", + "kernel_guard", + "lazy_init", + "log", + "memory_addr", + "percpu", + "rand", + "scheduler", + "spinlock", + "timer_list", +] + +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bcm2835-sdhci" +version = "0.1.0" +source = "git+https://github.com/lhw2002426/bcm2835-sdhci.git?rev=e974f16#e974f168efa72b470a01f61bdef32240c66f54fc" +dependencies = [ + "aarch64-cpu", + "log", + "tock-registers", + "volatile 0.2.7", +] + +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags 2.4.0", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.29", + "which", +] + +[[package]] +name = "bit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b645c5c09a7d4035949cfce1a915785aaad6f17800c35fda8a8c311c491f284" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" + +[[package]] +name = "bitmap-allocator" +version = "0.1.0" +source = "git+https://github.com/rcore-os/bitmap-allocator.git?rev=88e871a#88e871a54f28a3d6795478f237466b3332e2fb1d" +dependencies = [ + "bit_field", +] + +[[package]] +name = "bitmaps" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703642b98a00b3b90513279a8ede3fcfa479c126c5fb46e78f3051522f021403" + +[[package]] +name = "buddy_system_allocator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f9365b6b0c9e1663ca4ca9440c00eda46bc85a3407070be8b5e0d8d1f29629" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "capability" +version = "0.1.0" +dependencies = [ + "axerrno", + "bitflags 2.4.0", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ce6fffb678c9b80a70b6b6de0aad31df727623a70fd9a842c30cd573e2fa98" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "crate_interface" +version = "0.1.1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "defmt" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2d011b2fee29fb7d659b83c43fce9a2cb4df453e16d441a51448e448f3f98" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f0216f6c5acb5ae1a47050a6645024e6edafc2ee32d421955eccfef12ef92e" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "defmt-parser" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "269924c02afd7f94bc4cecbfa5c379f6ffcf9766b3408fe63d22c728654eccd0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "driver_block" +version = "0.1.0" +dependencies = [ + "bcm2835-sdhci", + "driver_common", + "log", +] + +[[package]] +name = "driver_common" +version = "0.1.0" + +[[package]] +name = "driver_display" +version = "0.1.0" +dependencies = [ + "driver_common", +] + +[[package]] +name = "driver_net" +version = "0.1.0" +dependencies = [ + "driver_common", + "ixgbe-driver", + "log", + "spin 0.9.8", +] + +[[package]] +name = "driver_pci" +version = "0.1.0" +dependencies = [ + "virtio-drivers", +] + +[[package]] +name = "driver_virtio" +version = "0.1.0" +dependencies = [ + "driver_block", + "driver_common", + "driver_display", + "driver_net", + "virtio-drivers", +] + +[[package]] +name = "dw_apb_uart" +version = "0.1.0" +dependencies = [ + "tock-registers", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "embedded-graphics" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0649998afacf6d575d126d83e68b78c0ab0e00ca2ac7e9b3db11b4cbe8274ef0" +dependencies = [ + "az", + "byteorder", + "embedded-graphics-core", + "float-cmp", + "micromath", +] + +[[package]] +name = "embedded-graphics-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba9ecd261f991856250d2207f6d8376946cd9f412a2165d3b75bc87a0bc7a044" +dependencies = [ + "az", + "byteorder", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fatfs" +version = "0.4.0" +source = "git+https://github.com/rafalh/rust-fatfs?rev=a3a834e#a3a834ef92d94dd227c316c0887654dab00ae085" +dependencies = [ + "bitflags 1.3.2", + "log", +] + +[[package]] +name = "flatten_objects" +version = "0.1.0" +dependencies = [ + "bitmaps", +] + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "handler_table" +version = "0.1.0" + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "heapless" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "spin 0.9.8", + "stable_deref_trait", +] + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "ixgbe-driver" +version = "0.1.0" +source = "git+https://github.com/KuangjuX/ixgbe-driver.git?rev=8e5eb74#8e5eb741299d7d95c373ec745e39a1473fe84563" +dependencies = [ + "bit_field", + "core_detect", + "log", + "smoltcp", + "volatile 0.3.0", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kernel_guard" +version = "0.1.0" +dependencies = [ + "cfg-if", + "crate_interface", +] + +[[package]] +name = "lazy_init" +version = "0.1.0" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "linked_list" +version = "0.1.0" + +[[package]] +name = "linux-raw-sys" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memory_addr" +version = "0.1.0" + +[[package]] +name = "micromath" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39617bc909d64b068dcffd0e3e31679195b5576d0c83fadc52690268cc2b2b55" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "page_table" +version = "0.1.0" +dependencies = [ + "log", + "memory_addr", + "page_table_entry", +] + +[[package]] +name = "page_table_entry" +version = "0.1.0" +dependencies = [ + "aarch64-cpu", + "bitflags 2.4.0", + "memory_addr", + "x86_64", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percpu" +version = "0.1.0" +dependencies = [ + "cfg-if", + "kernel_guard", + "percpu_macros", + "spin 0.9.8", + "x86", +] + +[[package]] +name = "percpu_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62" +dependencies = [ + "proc-macro2", + "syn 2.0.29", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ratio" +version = "0.1.0" + +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "raw-cpuid" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d86a7c4638d42c44551f4791a20e687dbb4c3de1f33c43dd71e355cd429def1" +dependencies = [ + "bitflags 2.4.0", +] + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "regex" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "riscv" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa3145d2fae3778b1e31ec2e827b228bdc6abd9b74bb5705ba46dcb82069bc4f" +dependencies = [ + "bit_field", + "critical-section", + "embedded-hal", +] + +[[package]] +name = "rlsf" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222fb240c3286247ecdee6fa5341e7cdad0ffdf8e7e401d9937f2d58482a20bf" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "svgbobdoc", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "sbi-rt" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c113c53291db8ac141e01f43224ed488b8d6001ab66737b82e04695a43a42b7" +dependencies = [ + "sbi-spec", +] + +[[package]] +name = "sbi-spec" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d4027cf9bb591a9fd0fc0e283be6165c5abe96cb73e9f0e24738c227f425377" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "scheduler" +version = "0.1.0" +dependencies = [ + "linked_list", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + +[[package]] +name = "serde" +version = "1.0.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc59dfdcbad1437773485e0367fea4b090a2e0a16d9ffc46af47764536a298ec" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + +[[package]] +name = "slab_allocator" +version = "0.3.1" +dependencies = [ + "buddy_system_allocator", +] + +[[package]] +name = "smoltcp" +version = "0.10.0" +source = "git+https://github.com/rcore-os/smoltcp.git?rev=2ade274#2ade2747abc4d779d0836154b0413d13ce16cd5b" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "defmt", + "heapless", + "log", + "managed", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spinlock" +version = "0.1.0" +dependencies = [ + "cfg-if", + "kernel_guard", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "timer_list" +version = "0.1.0" + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tock-registers" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "696941a0aee7e276a165a978b37918fd5d22c55c3d6bda197813070ca9c0f21c" + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tuple_for_each" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "virtio-drivers" +version = "0.4.0" +source = "git+https://github.com/rcore-os/virtio-drivers.git?rev=409ee72#409ee723c92adf309e825a7b87f53049707ed306" +dependencies = [ + "bitflags 1.3.2", + "log", + "zerocopy", +] + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b06ad3ed06fef1713569d547cdbdb439eafed76341820fb0e0344f29a41945" + +[[package]] +name = "volatile" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e76fae08f03f96e166d2dfda232190638c10e0383841252416f9cfe2ae60e6" + +[[package]] +name = "volatile" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" + +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.29", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d09770118a7eb1ccaf4a594a221334119a44a814fcb0d31c5b85e83e97227a97" +dependencies = [ + "memchr", +] + +[[package]] +name = "x2apic" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "547152b57dd1ae0ce7a4ef1c6470f6039aa7ed22e2179d5bc4f3eda1304e0db3" +dependencies = [ + "bit", + "bitflags 1.3.2", + "paste", + "raw-cpuid 10.7.0", + "x86_64", +] + +[[package]] +name = "x86" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385" +dependencies = [ + "bit_field", + "bitflags 1.3.2", + "raw-cpuid 10.7.0", +] + +[[package]] +name = "x86_64" +version = "0.14.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "100555a863c0092238c2e0e814c1096c1e5cf066a309c696a87e907b5f8c5d69" +dependencies = [ + "bit_field", + "bitflags 1.3.2", + "rustversion", + "volatile 0.4.6", +] + +[[package]] +name = "zerocopy" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b9c234616391070b0b173963ebc65a9195068e7ed3731c6edac2ec45ebe106" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f7f3a471f98d0a61c34322fbbfd10c384b07687f680d4119813713f72308d91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..1db098f12 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,79 @@ +[workspace] +resolver = "2" + +members = [ + "crates/allocator", + "crates/arm_gic", + "crates/arm_pl011", + "crates/dw_apb_uart", + "crates/axerrno", + "crates/axfs_devfs", + "crates/axfs_ramfs", + "crates/axfs_vfs", + "crates/axio", + "crates/capability", + "crates/crate_interface", + "crates/driver_block", + "crates/driver_common", + "crates/driver_display", + "crates/driver_net", + "crates/driver_pci", + "crates/driver_virtio", + "crates/flatten_objects", + "crates/handler_table", + "crates/kernel_guard", + "crates/lazy_init", + "crates/linked_list", + "crates/memory_addr", + "crates/page_table", + "crates/page_table_entry", + "crates/percpu", + "crates/percpu_macros", + "crates/ratio", + "crates/scheduler", + "crates/slab_allocator", + "crates/spinlock", + "crates/timer_list", + "crates/tuple_for_each", + + "modules/axalloc", + "modules/axconfig", + "modules/axdisplay", + "modules/axdriver", + "modules/axfs", + "modules/axhal", + "modules/axlog", + "modules/axnet", + "modules/axruntime", + "modules/axsync", + "modules/axtask", + + "api/axfeat", + "api/arceos_api", + "api/arceos_posix_api", + + "ulib/axstd", + "ulib/axlibc", + + "apps/display", + "apps/exception", + "apps/helloworld", + "apps/memtest", + "apps/fs/shell", + "apps/net/echoserver", + "apps/net/httpclient", + "apps/net/httpserver", + "apps/net/udpserver", + "apps/net/bwbench", + "apps/task/parallel", + "apps/task/sleep", + "apps/task/yield", + "apps/task/priority", + "apps/task/tls", +] + +[profile.release] +lto = true + +[patch.crates-io] +crate_interface = { path = "crates/crate_interface" } diff --git a/LICENSE.Apache2 b/LICENSE.Apache2 new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/LICENSE.Apache2 @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE.GPLv3 b/LICENSE.GPLv3 new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/LICENSE.GPLv3 @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/LICENSE.MulanPSL2 b/LICENSE.MulanPSL2 new file mode 100644 index 000000000..5729580d9 --- /dev/null +++ b/LICENSE.MulanPSL2 @@ -0,0 +1,127 @@ + 木兰宽松许可证, 第2版 + + 木兰宽松许可证, 第2版 + 2020年1月 http://license.coscl.org.cn/MulanPSL2 + + + 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: + + 0. 定义 + + “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 + + “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 + + “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 + + “法人实体”是指提交贡献的机构及其“关联实体”。 + + “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 + + 1. 授予版权许可 + + 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 + + 2. 授予专利许可 + + 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 + + 3. 无商标许可 + + “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 + + 4. 分发限制 + + 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 + + 5. 免责声明与责任限制 + + “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 + + 6. 语言 + “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 + + 条款结束 + + 如何将木兰宽松许可证,第2版,应用到您的软件 + + 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: + + 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; + + 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; + + 3, 请将如下声明文本放入每个源文件的头部注释中。 + + Copyright (c) [Year] [name of copyright holder] + [Software Name] is licensed under Mulan PSL v2. + You can use this software according to the terms and conditions of the Mulan PSL v2. + You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + See the Mulan PSL v2 for more details. + + + Mulan Permissive Software License,Version 2 + + Mulan Permissive Software License,Version 2 (Mulan PSL v2) + January 2020 http://license.coscl.org.cn/MulanPSL2 + + Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: + + 0. Definition + + Software means the program and related documents which are licensed under this License and comprise all Contribution(s). + + Contribution means the copyrightable work licensed by a particular Contributor under this License. + + Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. + + Legal Entity means the entity making a Contribution and all its Affiliates. + + Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. + + 1. Grant of Copyright License + + Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. + + 2. Grant of Patent License + + Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. + + 3. No Trademark License + + No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. + + 4. Distribution Restriction + + You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. + + 5. Disclaimer of Warranty and Limitation of Liability + + THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + 6. Language + + THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. + + END OF THE TERMS AND CONDITIONS + + How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software + + To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: + + i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; + + ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; + + iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. + + + Copyright (c) [Year] [name of copyright holder] + [Software Name] is licensed under Mulan PSL v2. + You can use this software according to the terms and conditions of the Mulan PSL v2. + You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + See the Mulan PSL v2 for more details. diff --git a/LICENSE.MulanPubL2 b/LICENSE.MulanPubL2 new file mode 100644 index 000000000..2fcd23f58 --- /dev/null +++ b/LICENSE.MulanPubL2 @@ -0,0 +1,183 @@ + 木兰公共许可证, 第2版 + +木兰公共许可证, 第2版 + +2021年5月 http://license.coscl.org.cn/MulanPubL-2.0 + +您对“贡献”的复制、使用、修改及分发受木兰公共许可证,第2版(以下简称“本许可证”)的如下条款的约束: + +0. 定义 + +“贡献” 是指由“贡献者”许可在“本许可证”下的受版权法保护的作品,包括最初“贡献者”许可在“本许可证”下的作品及后续“贡献者”许可在“本许可证”下的“衍生作品”。 + +“贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 + +“法人实体” 是指提交贡献的机构及其“关联实体”。 + +“关联实体” 是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的“控制”是指拥有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 + +“衍生作品” 是指基于“贡献”创作的作品,具体包括对全部或部分“贡献”进行修改、重写、翻译、注释、组合或与之链接(包括动态链接或静态链接)而形成的作品。仅与“贡献”进行进程间通信或系统调用的作品是独立作品,不属于“衍生作品”。 + +“对应源代码” 是指生成、安装和(对于可执行作品)运行目标代码所需的所有源文件和与之关联的接口定义文件,以及控制这些活动的脚本,但不包括编译环境、编译工具、云服务平台(如果有)。 + +“分发” 是指通过任何媒介向他人提供“贡献”或“衍生作品”的行为,以及利用“贡献”或“衍生作品”通过网络远程给用户提供服务的行为,例如:通过利用“贡献”或“衍生作品”搭建的云服务平台提供在线服务的行为。 + +1. 授予版权许可 + +每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、“分发”其“贡献”或“衍生作品”,不论修改与否。 + +2. 授予专利许可 + +每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销的情形除外)专利许可,供您使用、制造、委托制造、销售、许诺销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”中的专利权利要求,而不包括仅因您对“贡献”的修改而将必然会侵犯到的专利权利要求。如果您或您的“关联实体”直接或间接地,就“贡献”对任何人发起专利侵权诉讼(包括在诉讼中提出反诉请求或交叉请求)或发起其他专利维权行动,则“贡献者”根据“本许可证”授予您的专利许可自您发起专利诉讼或专利维权行动之日终止。 + +3. 无商标许可 + +“贡献者”在“本许可证”下不提供对其商品名称、商标、服务标识或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用的情形除外。 + +4. 分发限制 + +您可以将您接收到的“贡献”或您的“衍生作品”以源程序形式或可执行形式重新“分发”,但必须满足下列条件: + +(1)您必须向接收者提供“本许可证”的副本,并保留“贡献”中的版权、商标、专利及免责声明;并且, + +(2)如果您“分发”您接收到的“贡献”,您必须使用“本许可证”提供该“贡献”的源代码副本;如果您 “分发”您的“衍生作品”,您必须: + +(i)随“衍生作品”提供使用“本许可证”“分发”的您的“衍生作品”的“对应源代码”。如果您通过下载链接提供前述“对应源代码”,则您应将下载链接地址置于“衍生作品”或其随附文档中的明显位置,有效期自该“衍生作品”“分发”之日起不少于三年,并确保接收者可以获得“对应源代码”;或者, + +(ii)随“衍生作品”向接收者提供一个书面要约,表明您愿意提供根据“本许可证”“分发”的您“衍生作品”的“对应源代码”。该书面要约应置于“衍生作品”中的明显位置,并确保接收者根据书面要约可获取“对应源代码”的时间从您接到该请求之日起不得超过三个月,且有效期自该“衍生作品”“分发”之日起不少于三年。 + +5. 违约与终止 + +如果您违反“本许可证”,任何“贡献者”有权书面通知您终止其根据“本许可证”授予您的许可。该“贡献者”授予您的许可自您接到其终止通知之日起终止。仅在如下两种情形下,即使您收到“贡献者”的通知也并不终止其授予您的许可: + +(1)您在接到该终止通知之前已停止所有违反行为; + +(2)您是首次收到该“贡献者”根据“本许可证”发出的书面终止通知,并且您在收到该通知后30天内已停止所有违反行为。 + +只要您下游的接收者遵守“本许可证”的相关规定,即使您在“本许可证”下被授予的许可终止,不影响下游的接收者根据“本许可证”享有的权利。 + +6. 例外 + +如果您将“贡献”与采用GNU AFFERO GENERAL PUBLIC LICENSE Version 3(以下简称“AGPLv3”)或其后续版本的作品结合形成新的“衍生作品”,且根据“AGPLv3”或其后续版本的要求您有义务将新形成的“衍生作品”以“AGPLv3”或其后续版本进行许可的,您可以根据“AGPLv3”或其后续版本进行许可,只要您在“分发”该“衍生作品”的同时向接收者提供“本许可证”的副本,并保留“贡献”中的版权、商标、专利及免责声明。但任何“贡献者”不会因您选择“AGPLv3”或其后续版本而授予该“衍生作品”的接收者更多权利。 + +7. 免责声明与责任限制 + +“贡献”在提供时不带有任何明示或默示的担保。在任何情况下,“贡献者”或版权人不对任何人因使用“贡献”而引发的任何直接或间接损失承担任何责任,不论该等损失因何种原因导致或者基于何种法律理论,即使其曾被告知有该等损失的可能性。 + +8. 语言 + +“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何不一致,以中文版为准。 + +条款结束 + +如何将木兰公共许可证,第2版,应用到您的软件 + +如果您希望将木兰公共许可证,第2版,应用到您的软件,为了方便接收者查阅,建议您完成如下三步: + +1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; + +2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; + +3, 请将如下声明文本放入每个源文件的头部注释中。 + +Copyright (c) [Year] [name of copyright holder] +[Software Name] is licensed under Mulan PubL v2. +You can use this software according to the terms and conditions of the Mulan PubL v2. +You may obtain a copy of Mulan PubL v2 at: + http://license.coscl.org.cn/MulanPubL-2.0 +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +See the Mulan PubL v2 for more details. + + + Mulan Public License,Version 2 + +Mulan Public License,Version 2 (Mulan PubL v2) + +May 2021 http://license.coscl.org.cn/MulanPubL-2.0 + +Your reproduction, use, modification and Distribution of the Contribution shall be subject to Mulan Public License, Version 2 (this License) with following terms and conditions: + +0. Definition + +Contribution means the copyrightable work licensed by a particular Contributor under this License, including the work licensed by the initial Contributor under this License and its Derivative Work licensed by any subsequent Contributor under this License. + +Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. + +Legal Entity means the entity making a Contribution and all its Affiliates. + +Affiliates mmeans entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. + +Derivative Work means works created based on Contribution, specifically including works formed by modifying, rewriting, translating, annotating, combining or linking to all or part of Contribution (including dynamic linking or static linking). Works which only communicate with Contribution through inter-process communication or system call, are independent works, rather than Derivative Work. + +Corresponding Source Code means all the source code needed to generate, install, and (for an executable work) run the object code including the interface definition files associated with source files for the work, and scripts to control those activities, excluding of compilation environment and compilation tools, cloud services platform (if any). + +Distribute (or Distribution) means the act of making the Contribution or Derivative Work available to others through any medium, and using the Contribution or Derivative Work to provide online services to users, such as the act of providing online services through a cloud service platform built using Contributions or Derivative Works. + +1. Grant of Copyright License + +Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or Distribute its Contribution or Derivative Work, with modification or not. + +2. Grant of Patent License + +Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to use, make, have made, sell, offer for sale, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, excluding of any patent claims solely be infringed by your modification. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that any Contribution infringes patents, then any patent license granted to you under this License for the Contribution shall terminate as of the date such litigation or activity is filed or taken. + +3. No Trademark License + +No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. + +4. Distribution Restriction + +You may Distribute the Contribution you received or your Derivative Work, whether in source or executable forms, provided that you meet the following conditions: + +1) You must provide recipients with a copy of this License and retain copyright, trademark, patent and disclaimer statements in the Contribution; and, + +2) If you Distribute the Contribution you received, you must provide copies of the Contribution’s source code under this License; + +If you Distribute your Derivative Work, you have to: + +(i) accompanying the Derivative work, provide recipients with Corresponding Source Code of your Derivative Work under this License. If you provide the Corresponding Source Code through a download link, you should place such link address prominently in the Derivative Work or its accompanying documents, and be valid no less than three years from your Distribution of the particular Derivative Work, and ensure that the recipients can acquire the Corresponding Source Code through the link; or, + +(ii) accompanying the Derivative Work, provide recipients with a written offer indicating your willingness to provide the Corresponding Source Code of the Derivative Work licensed under this License. Such written offer shall be placed prominently in the Derivative Work or its accompanying documents. Without reasonable excuse, the recipient shall be able to acquire the Corresponding Source code of the Derivative work for no more than three months from your receipt of a valid request, and be valid no less than three years from your Distribution of the particular Derivative Work. + +5. Breach and Termination + +If you breach this License, any Contributor has the right to notify you in writing to terminate its license granted to you under this License. The license granted to you by such Contributor terminates upon your receipt of such notice of termination. Notwithstanding the foregoing, your license will not be terminated even if you receive a notice of termination from Contributor, provided that: + +1) you have cured all the breaches prior to receiving such notice of termination; or, + +2) it’s your first time to receive a notice of termination from such Contributor pursuant to this License, and you have cured all the breaches within 30 days of receipt of such notice. + +Termination of your license under this License shall not affect the downstream recipient's rights under this License, provided that the downstream recipient complies with this License. + +6. Exceptions + +If you combine Contribution or your Derivative Work with a work licensed under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 (hereinafter referred to as “AGPLv3”) or its subsequent versions, and according to the AGPLv3 or its subsequent versions, you have an obligation to make the combined work to be licensed under the corresponding license, you can license such combined work under the license, provided that when you Distribute the combined work, you also provide a copy of this License to the recipients, and retain copyright, trademarks, patents, and disclaimer statements in the Contribution. No Contributor will grant additional rights to the recipients of the combined work for your license under AGPLv3 or its subsequent versions. + +7. Disclaimer of Warranty and Limitation of liability + +CONTRIBUTION ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE CONTRIBUTION, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +8. Language + +THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. + +END OF THE TERMS AND CONDITIONS + +How to apply the Mulan Public License,Version 2 (Mulan PubL v2), to your software + +To apply the Mulan Public License,Version 2 to your work, for easy identification by recipients, you are suggested to complete following three steps: + +Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; +Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; +Attach the statement to the appropriate annotated syntax at the beginning of each source file. +Copyright (c) [Year] [name of copyright holder] +[Software Name] is licensed under Mulan PubL v2. +You can use this software according to the terms and conditions of the Mulan PubL v2. +You may obtain a copy of Mulan PubL v2 at: + http://license.coscl.org.cn/MulanPubL-2.0 +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +See the Mulan PubL v2 for more details. diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..b35159658 --- /dev/null +++ b/Makefile @@ -0,0 +1,212 @@ +# Available arguments: +# * General options: +# - `ARCH`: Target architecture: x86_64, riscv64, aarch64 +# - `PLATFORM`: Target platform in the `platforms` directory +# - `SMP`: Number of CPUs +# - `MODE`: Build mode: release, debug +# - `LOG:` Logging level: warn, error, info, debug, trace +# - `V`: Verbose level: (empty), 1, 2 +# * App options: +# - `A` or `APP`: Path to the application +# - `FEATURES`: Features os ArceOS modules to be enabled. +# - `APP_FEATURES`: Features of (rust) apps to be enabled. +# * QEMU options: +# - `BLK`: Enable storage devices (virtio-blk) +# - `NET`: Enable network devices (virtio-net) +# - `GRAPHIC`: Enable display devices and graphic output (virtio-gpu) +# - `BUS`: Device bus type: mmio, pci +# - `DISK_IMG`: Path to the virtual disk image +# - `ACCEL`: Enable hardware acceleration (KVM on linux) +# - `QEMU_LOG`: Enable QEMU logging (log file is "qemu.log") +# - `NET_DUMP`: Enable network packet dump (log file is "netdump.pcap") +# - `NET_DEV`: QEMU netdev backend types: user, tap +# * Network options: +# - `IP`: ArceOS IPv4 address (default is 10.0.2.15 for QEMU user netdev) +# - `GW`: Gateway IPv4 address (default is 10.0.2.2 for QEMU user netdev) + +# General options +ARCH ?= x86_64 +PLATFORM ?= +SMP ?= 1 +MODE ?= release +LOG ?= warn +V ?= + +# App options +A ?= apps/helloworld +APP ?= $(A) +FEATURES ?= +APP_FEATURES ?= + +# QEMU options +BLK ?= n +NET ?= n +GRAPHIC ?= n +BUS ?= mmio + +DISK_IMG ?= disk.img +QEMU_LOG ?= n +NET_DUMP ?= n +NET_DEV ?= user + +# Network options +IP ?= 10.0.2.15 +GW ?= 10.0.2.2 + +# App type +ifeq ($(wildcard $(APP)),) + $(error Application path "$(APP)" is not valid) +endif + +ifneq ($(wildcard $(APP)/Cargo.toml),) + APP_TYPE := rust +else + APP_TYPE := c +endif + +# Architecture, platform and target +ifneq ($(filter $(MAKECMDGOALS),unittest unittest_no_fail_fast),) + PLATFORM_NAME := +else ifneq ($(PLATFORM),) + # `PLATFORM` is specified, override the `ARCH` variables + builtin_platforms := $(patsubst platforms/%.toml,%,$(wildcard platforms/*)) + ifneq ($(filter $(PLATFORM),$(builtin_platforms)),) + # builtin platform + PLATFORM_NAME := $(PLATFORM) + _arch := $(word 1,$(subst -, ,$(PLATFORM))) + else ifneq ($(wildcard $(PLATFORM)),) + # custom platform, read the "platform" field from the toml file + PLATFORM_NAME := $(shell cat $(PLATFORM) | sed -n 's/^platform = "\([a-z0-9A-Z_\-]*\)"/\1/p') + _arch := $(shell cat $(PLATFORM) | sed -n 's/^arch = "\([a-z0-9A-Z_\-]*\)"/\1/p') + else + $(error "PLATFORM" must be one of "$(builtin_platforms)" or a valid path to a toml file) + endif + ifeq ($(origin ARCH),command line) + ifneq ($(ARCH),$(_arch)) + $(error "ARCH=$(ARCH)" is not compatible with "PLATFORM=$(PLATFORM)") + endif + endif + ARCH := $(_arch) +endif + +ifeq ($(ARCH), x86_64) + # Don't enable kvm for WSL/WSL2. + ACCEL ?= $(if $(findstring -microsoft, $(shell uname -r | tr '[:upper:]' '[:lower:]')),n,y) + PLATFORM_NAME ?= x86_64-qemu-q35 + TARGET := x86_64-unknown-none + BUS := pci +else ifeq ($(ARCH), riscv64) + ACCEL ?= n + PLATFORM_NAME ?= riscv64-qemu-virt + TARGET := riscv64gc-unknown-none-elf +else ifeq ($(ARCH), aarch64) + ACCEL ?= n + PLATFORM_NAME ?= aarch64-qemu-virt + TARGET := aarch64-unknown-none-softfloat +else + $(error "ARCH" must be one of "x86_64", "riscv64", or "aarch64") +endif + +export AX_ARCH=$(ARCH) +export AX_PLATFORM=$(PLATFORM_NAME) +export AX_SMP=$(SMP) +export AX_MODE=$(MODE) +export AX_LOG=$(LOG) +export AX_TARGET=$(TARGET) +export AX_IP=$(IP) +export AX_GW=$(GW) + +# Binutils +CROSS_COMPILE ?= $(ARCH)-linux-musl- +CC := $(CROSS_COMPILE)gcc +AR := $(CROSS_COMPILE)ar +RANLIB := $(CROSS_COMPILE)ranlib +LD := rust-lld -flavor gnu + +OBJDUMP ?= rust-objdump -d --print-imm-hex --x86-asm-syntax=intel +OBJCOPY ?= rust-objcopy --binary-architecture=$(ARCH) +GDB ?= gdb-multiarch + +# Paths +OUT_DIR ?= $(APP) + +APP_NAME := $(shell basename $(APP)) +LD_SCRIPT := $(CURDIR)/modules/axhal/linker_$(PLATFORM_NAME).lds +OUT_ELF := $(OUT_DIR)/$(APP_NAME)_$(PLATFORM_NAME).elf +OUT_BIN := $(OUT_DIR)/$(APP_NAME)_$(PLATFORM_NAME).bin + +all: build + +include scripts/make/utils.mk +include scripts/make/build.mk +include scripts/make/qemu.mk +include scripts/make/test.mk +ifeq ($(PLATFORM_NAME), aarch64-raspi4) + include scripts/make/raspi4.mk +else ifeq ($(PLATFORM_NAME), aarch64-bsta1000b) + include scripts/make/bsta1000b-fada.mk +endif + +build: $(OUT_DIR) $(OUT_BIN) + +disasm: + $(OBJDUMP) $(OUT_ELF) | less + +run: build justrun + +justrun: + $(call run_qemu) + +debug: build + $(call run_qemu_debug) & + sleep 1 + $(GDB) $(OUT_ELF) \ + -ex 'target remote localhost:1234' \ + -ex 'b rust_entry' \ + -ex 'continue' \ + -ex 'disp /16i $$pc' + +clippy: +ifeq ($(origin ARCH), command line) + $(call cargo_clippy,--target $(TARGET)) +else + $(call cargo_clippy) +endif + +doc: + $(call cargo_doc) + +doc_check_missing: + $(call cargo_doc) + +fmt: + cargo fmt --all + +fmt_c: + @clang-format --style=file -i $(shell find ulib/axlibc -iname '*.c' -o -iname '*.h') + +test: + $(call app_test) + +unittest: + $(call unit_test) + +unittest_no_fail_fast: + $(call unit_test,--no-fail-fast) + +disk_img: +ifneq ($(wildcard $(DISK_IMG)),) + @printf "$(YELLOW_C)warning$(END_C): disk image \"$(DISK_IMG)\" already exists!\n" +else + $(call make_disk_image,fat32,$(DISK_IMG)) +endif + +clean: clean_c + rm -rf $(APP)/*.bin $(APP)/*.elf + cargo clean + +clean_c:: + rm -rf ulib/axlibc/build_* + rm -rf $(app-objs) + +.PHONY: all build disasm run justrun debug clippy fmt fmt_c test test_no_fail_fast clean clean_c doc disk_image diff --git a/README.md b/README.md new file mode 100644 index 000000000..63fe3a64c --- /dev/null +++ b/README.md @@ -0,0 +1,183 @@ +# RukOS + +[![CI](https://github.com/syswonder/rukos/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/syswonder/rukos/actions/workflows/build.yml) +[![CI](https://github.com/syswonder/rukos/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/syswonder/rukos/actions/workflows/test.yml) + +An experimental modular operating system (or unikernel) written in Rust. + +RukOS was inspired by [Unikraft](https://github.com/unikraft/unikraft) and [ArceOS](https://github.com/rcore-os/arceos) + +🚧 Working In Progress. + +## Features & TODOs + +* [x] Architecture: x86_64, riscv64, aarch64 +* [x] Platform: QEMU pc-q35 (x86_64), virt (riscv64/aarch64) +* [x] Multi-thread +* [x] FIFO/RR/CFS scheduler +* [x] VirtIO net/blk/gpu drivers +* [x] TCP/UDP net stack using [smoltcp](https://github.com/smoltcp-rs/smoltcp) +* [x] Synchronization/Mutex +* [x] SMP scheduling with single run queue +* [x] File system +* [ ] Compatible with Linux apps +* [ ] Interrupt driven device I/O +* [ ] Async I/O + +## Example apps + +Example applications can be found in the [apps/](apps/) directory. All applications must at least depend on the following modules, while other modules are optional: + +* [axruntime](modules/axruntime/): Bootstrapping from the bare-metal environment, and initialization. +* [axhal](modules/axhal/): Hardware abstraction layer, provides unified APIs for cross-platform. +* [axconfig](modules/axconfig/): Platform constants and kernel parameters, such as physical memory base, kernel load addresses, stack size, etc. +* [axlog](modules/axlog/): Multi-level formatted logging. + +The currently supported applications (Rust), as well as their dependent modules and features, are shown in the following table: + +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [helloworld](apps/helloworld/) | | | A minimal app that just prints a string | +| [exception](apps/exception/) | | paging | Exception handling test | +| [memtest](apps/memtest/) | axalloc | alloc, paging | Dynamic memory allocation test | +| [display](apps/display/) | axalloc, axdisplay | alloc, paging, display | Graphic/GUI test | +| [yield](apps/task/yield/) | axalloc, axtask | alloc, paging, multitask, sched_fifo | Multi-threaded yielding test | +| [parallel](apps/task/parallel/) | axalloc, axtask | alloc, paging, multitask, sched_fifo | Parallel computing test (to test synchronization & mutex) | +| [sleep](apps/task/sleep/) | axalloc, axtask | alloc, paging, multitask, sched_fifo | Thread sleeping test | +| [shell](apps/fs/shell/) | axalloc, axdriver, axfs | alloc, paging, fs | A simple shell that responds to filesystem operations | +| [httpclient](apps/net/httpclient/) | axalloc, axdriver, axnet | alloc, paging, net | A simple client that sends an HTTP request and then prints the response | +| [echoserver](apps/net/echoserver/) | axalloc, axdriver, axnet, axtask | alloc, paging, net, multitask | A multi-threaded TCP server that reverses messages sent by the client | +| [httpserver](apps/net/httpserver/) | axalloc, axdriver, axnet, axtask | alloc, paging, net, multitask | A multi-threaded HTTP server that serves a static web page | + +## Build & Run + +### Install build dependencies + +Install [cargo-binutils](https://github.com/rust-embedded/cargo-binutils) to use `rust-objcopy` and `rust-objdump` tools: + +```bash +cargo install cargo-binutils +``` + +#### for build&run C apps +Install `libclang-dev`: + +```bash +sudo apt install libclang-dev +``` + +Download&Install `cross-musl-based toolchains`: +``` +# download +wget https://musl.cc/aarch64-linux-musl-cross.tgz +wget https://musl.cc/riscv64-linux-musl-cross.tgz +wget https://musl.cc/x86_64-linux-musl-cross.tgz +# install +tar zxf aarch64-linux-musl-cross.tgz +tar zxf riscv64-linux-musl-cross.tgz +tar zxf x86_64-linux-musl-cross.tgz +# exec below command in bash OR add below info in ~/.bashrc +export PATH=`pwd`/x86_64-linux-musl-cross/bin:`pwd`/aarch64-linux-musl-cross/bin:`pwd`/riscv64-linux-musl-cross/bin:$PATH +``` + +### Example apps + +```bash +# in rukos directory +make A=path/to/app ARCH= LOG= +``` + +Where `` should be one of `riscv64`, `aarch64`,`x86_64`. + +`` should be one of `off`, `error`, `warn`, `info`, `debug`, `trace`. + +`path/to/app` is the relative path to the example application. + +More arguments and targets can be found in [Makefile](Makefile). + +For example, to run the [httpserver](apps/net/httpserver/) on `qemu-system-aarch64` with 4 cores: + +```bash +make A=apps/net/httpserver ARCH=aarch64 LOG=info SMP=4 run NET=y +``` + +Note that the `NET=y` argument is required to enable the network device in QEMU. These arguments (`BLK`, `GRAPHIC`, etc.) only take effect at runtime not build time. + +### Your custom apps + +#### Rust + +1. Create a new rust package with `no_std` and `no_main` environment. +2. Add `axstd` dependency and features to enable to `Cargo.toml`: + + ```toml + [dependencies] + axstd = { path = "/path/to/rukos/ulib/axstd", features = ["..."] } + ``` + +3. Call library functions from `axstd` in your code, just like the Rust [std](https://doc.rust-lang.org/std/) library. +4. Build your application with RukOS, by running the `make` command in the application directory: + + ```bash + # in app directory + make -C /path/to/rukos A=$(pwd) ARCH= run + # more args: LOG= SMP= NET=[y|n] ... + ``` + + All arguments and targets are the same as above. + +#### C + +1. Create `axbuild.mk` and `features.txt` in your project: + + ```bash + app/ + ├── foo.c + ├── bar.c + ├── axbuild.mk # optional, if there is only one `main.c` + └── features.txt # optional, if only use default features + ``` + +2. Add build targets to `axbuild.mk`, add features to enable to `features.txt` (see this [example](apps/c/sqlite3/)): + + ```bash + # in axbuild.mk + app-objs := foo.o bar.o + ``` + + ```bash + # in features.txt + alloc + paging + net + ``` + +3. Build your application with RukOS, by running the `make` command in the application directory: + + ```bash + # in app directory + make -C /path/to/rukos A=$(pwd) ARCH= run + # more args: LOG= SMP= NET=[y|n] ... + ``` + +### How to build RukOS for specific platforms and devices + +Set the `PLATFORM` variable when run `make`: + +```bash +# Build helloworld for raspi4 +make PLATFORM=aarch64-raspi4 A=apps/helloworld +``` + +You may also need to select the corrsponding device drivers by setting the `FEATURES` variable: + +```bash +# Build the shell app for raspi4, and use the SD card driver +make PLATFORM=aarch64-raspi4 A=apps/fs/shell FEATURES=driver-bcm2835-sdhci +# Build Redis for the bare-metal x86_64 platform, and use the ixgbe and ramdisk driver +make PLATFORM=x86_64-pc-oslab A=apps/c/redis FEATURES=driver-ixgbe,driver-ramdisk SMP=4 +``` + +## Design + +![](doc/figures/ArceOS.svg) diff --git a/api/arceos_api/Cargo.toml b/api/arceos_api/Cargo.toml new file mode 100644 index 000000000..3699a6c46 --- /dev/null +++ b/api/arceos_api/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "arceos_api" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Public APIs and types for ArceOS modules" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/api/arceos_api" +documentation = "https://rcore-os.github.io/arceos/arceos_api/index.html" + +[features] +default = [] + +irq = ["axfeat/irq"] +alloc = ["dep:axalloc", "axfeat/alloc"] +multitask = ["axtask/multitask", "axfeat/multitask"] +fs = ["dep:axfs", "axfeat/fs"] +net = ["dep:axnet", "axfeat/net"] +display = ["dep:axdisplay", "axfeat/display"] + +myfs = ["axfeat/myfs"] + +# Use dummy functions if the feature is not enabled +dummy-if-not-enabled = [] + +[dependencies] +axfeat = { path = "../axfeat" } +axruntime = { path = "../../modules/axruntime" } +axconfig = { path = "../../modules/axconfig" } +axlog = { path = "../../modules/axlog" } +axio = { path = "../../crates/axio" } +axerrno = { path = "../../crates/axerrno" } +axhal = { path = "../../modules/axhal" } +axalloc = { path = "../../modules/axalloc", optional = true } +axtask = { path = "../../modules/axtask", optional = true } +axfs = { path = "../../modules/axfs", optional = true } +axnet = { path = "../../modules/axnet", optional = true } +axdisplay = { path = "../../modules/axdisplay", optional = true } diff --git a/api/arceos_api/src/imp/display.rs b/api/arceos_api/src/imp/display.rs new file mode 100644 index 000000000..1abe25e59 --- /dev/null +++ b/api/arceos_api/src/imp/display.rs @@ -0,0 +1,11 @@ +pub use axdisplay::DisplayInfo as AxDisplayInfo; + +/// Gets the framebuffer information. +pub fn ax_framebuffer_info() -> AxDisplayInfo { + axdisplay::framebuffer_info() +} + +/// Flushes the framebuffer, i.e. show on the screen. +pub fn ax_framebuffer_flush() { + axdisplay::framebuffer_flush() +} diff --git a/api/arceos_api/src/imp/fs.rs b/api/arceos_api/src/imp/fs.rs new file mode 100644 index 000000000..fd7dac3ed --- /dev/null +++ b/api/arceos_api/src/imp/fs.rs @@ -0,0 +1,87 @@ +use alloc::string::String; +use axerrno::AxResult; +use axfs::fops::{Directory, File}; + +pub use axfs::fops::DirEntry as AxDirEntry; +pub use axfs::fops::FileAttr as AxFileAttr; +pub use axfs::fops::FilePerm as AxFilePerm; +pub use axfs::fops::FileType as AxFileType; +pub use axfs::fops::OpenOptions as AxOpenOptions; +pub use axio::SeekFrom as AxSeekFrom; + +#[cfg(feature = "myfs")] +pub use axfs::fops::{Disk as AxDisk, MyFileSystemIf}; + +/// A handle to an opened file. +pub struct AxFileHandle(File); + +/// A handle to an opened directory. +pub struct AxDirHandle(Directory); + +pub fn ax_open_file(path: &str, opts: &AxOpenOptions) -> AxResult { + Ok(AxFileHandle(File::open(path, opts)?)) +} + +pub fn ax_open_dir(path: &str, opts: &AxOpenOptions) -> AxResult { + Ok(AxDirHandle(Directory::open_dir(path, opts)?)) +} + +pub fn ax_read_file(file: &mut AxFileHandle, buf: &mut [u8]) -> AxResult { + file.0.read(buf) +} + +pub fn ax_read_file_at(file: &AxFileHandle, offset: u64, buf: &mut [u8]) -> AxResult { + file.0.read_at(offset, buf) +} + +pub fn ax_write_file(file: &mut AxFileHandle, buf: &[u8]) -> AxResult { + file.0.write(buf) +} + +pub fn ax_write_file_at(file: &AxFileHandle, offset: u64, buf: &[u8]) -> AxResult { + file.0.write_at(offset, buf) +} + +pub fn ax_truncate_file(file: &AxFileHandle, size: u64) -> AxResult { + file.0.truncate(size) +} + +pub fn ax_flush_file(file: &AxFileHandle) -> AxResult { + file.0.flush() +} + +pub fn ax_seek_file(file: &mut AxFileHandle, pos: AxSeekFrom) -> AxResult { + file.0.seek(pos) +} + +pub fn ax_file_attr(file: &AxFileHandle) -> AxResult { + file.0.get_attr() +} + +pub fn ax_read_dir(dir: &mut AxDirHandle, dirents: &mut [AxDirEntry]) -> AxResult { + dir.0.read_dir(dirents) +} + +pub fn ax_create_dir(path: &str) -> AxResult { + axfs::api::create_dir(path) +} + +pub fn ax_remove_dir(path: &str) -> AxResult { + axfs::api::remove_dir(path) +} + +pub fn ax_remove_file(path: &str) -> AxResult { + axfs::api::remove_file(path) +} + +pub fn ax_rename(old: &str, new: &str) -> AxResult { + axfs::api::rename(old, new) +} + +pub fn ax_current_dir() -> AxResult { + axfs::api::current_dir() +} + +pub fn ax_set_current_dir(path: &str) -> AxResult { + axfs::api::set_current_dir(path) +} diff --git a/api/arceos_api/src/imp/mem.rs b/api/arceos_api/src/imp/mem.rs new file mode 100644 index 000000000..3dd1ded12 --- /dev/null +++ b/api/arceos_api/src/imp/mem.rs @@ -0,0 +1,12 @@ +cfg_alloc! { + use core::alloc::Layout; + use core::ptr::NonNull; + + pub fn ax_alloc(layout: Layout) -> Option> { + axalloc::global_allocator().alloc(layout).ok() + } + + pub fn ax_dealloc(ptr: NonNull, layout: Layout) { + axalloc::global_allocator().dealloc(ptr, layout) + } +} diff --git a/api/arceos_api/src/imp/mod.rs b/api/arceos_api/src/imp/mod.rs new file mode 100644 index 000000000..865fdc482 --- /dev/null +++ b/api/arceos_api/src/imp/mod.rs @@ -0,0 +1,42 @@ +mod mem; +mod task; + +cfg_fs! { + mod fs; + pub use fs::*; +} + +cfg_net! { + mod net; + pub use net::*; +} + +cfg_display! { + mod display; + pub use display::*; +} + +mod stdio { + use core::fmt; + + pub fn ax_console_read_byte() -> Option { + axhal::console::getchar().map(|c| if c == b'\r' { b'\n' } else { c }) + } + + pub fn ax_console_write_bytes(buf: &[u8]) -> crate::AxResult { + axhal::console::write_bytes(buf); + Ok(buf.len()) + } + + pub fn ax_console_write_fmt(args: fmt::Arguments) -> fmt::Result { + axlog::print_fmt(args) + } +} + +pub use self::mem::*; +pub use self::stdio::*; +pub use self::task::*; + +pub use axhal::misc::terminate as ax_terminate; +pub use axhal::time::{current_time as ax_current_time, TimeValue as AxTimeValue}; +pub use axio::PollState as AxPollState; diff --git a/api/arceos_api/src/imp/net.rs b/api/arceos_api/src/imp/net.rs new file mode 100644 index 000000000..88d4f7722 --- /dev/null +++ b/api/arceos_api/src/imp/net.rs @@ -0,0 +1,131 @@ +use crate::io::AxPollState; +use axerrno::AxResult; +use axnet::{UdpSocket, TcpSocket}; +use core::net::{IpAddr, SocketAddr}; + +/// A handle to a TCP socket. +pub struct AxTcpSocketHandle(TcpSocket); + +/// A handle to a UDP socket. +pub struct AxUdpSocketHandle(UdpSocket); + +//////////////////////////////////////////////////////////////////////////////// +// TCP socket +//////////////////////////////////////////////////////////////////////////////// + +pub fn ax_tcp_socket() -> AxTcpSocketHandle { + AxTcpSocketHandle(TcpSocket::new()) +} + +pub fn ax_tcp_socket_addr(socket: &AxTcpSocketHandle) -> AxResult { + socket.0.local_addr() +} + +pub fn ax_tcp_peer_addr(socket: &AxTcpSocketHandle) -> AxResult { + socket.0.peer_addr() +} + +pub fn ax_tcp_set_nonblocking(socket: &AxTcpSocketHandle, nonblocking: bool) -> AxResult { + socket.0.set_nonblocking(nonblocking); + Ok(()) +} + +pub fn ax_tcp_connect(socket: &AxTcpSocketHandle, addr: SocketAddr) -> AxResult { + socket.0.connect(addr) +} + +pub fn ax_tcp_bind(socket: &AxTcpSocketHandle, addr: SocketAddr) -> AxResult { + socket.0.bind(addr) +} + +pub fn ax_tcp_listen(socket: &AxTcpSocketHandle, _backlog: usize) -> AxResult { + socket.0.listen() +} + +pub fn ax_tcp_accept(socket: &AxTcpSocketHandle) -> AxResult<(AxTcpSocketHandle, SocketAddr)> { + let new_sock = socket.0.accept()?; + let addr = new_sock.peer_addr()?; + Ok((AxTcpSocketHandle(new_sock), addr)) +} + +pub fn ax_tcp_send(socket: &AxTcpSocketHandle, buf: &[u8]) -> AxResult { + socket.0.send(buf) +} + +pub fn ax_tcp_recv(socket: &AxTcpSocketHandle, buf: &mut [u8]) -> AxResult { + socket.0.recv(buf) +} + +pub fn ax_tcp_poll(socket: &AxTcpSocketHandle) -> AxResult { + socket.0.poll() +} + +pub fn ax_tcp_shutdown(socket: &AxTcpSocketHandle) -> AxResult { + socket.0.shutdown() +} + +//////////////////////////////////////////////////////////////////////////////// +// UDP socket +//////////////////////////////////////////////////////////////////////////////// + +pub fn ax_udp_socket() -> AxUdpSocketHandle { + AxUdpSocketHandle(UdpSocket::new()) +} + +pub fn ax_udp_socket_addr(socket: &AxUdpSocketHandle) -> AxResult { + socket.0.local_addr() +} + +pub fn ax_udp_peer_addr(socket: &AxUdpSocketHandle) -> AxResult { + socket.0.peer_addr() +} + +pub fn ax_udp_set_nonblocking(socket: &AxUdpSocketHandle, nonblocking: bool) -> AxResult { + socket.0.set_nonblocking(nonblocking); + Ok(()) +} + +pub fn ax_udp_bind(socket: &AxUdpSocketHandle, addr: SocketAddr) -> AxResult { + socket.0.bind(addr) +} + +pub fn ax_udp_recv_from(socket: &AxUdpSocketHandle, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)> { + socket.0.recv_from(buf) +} + +pub fn ax_udp_peek_from(socket: &AxUdpSocketHandle, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)> { + socket.0.peek_from(buf) +} + +pub fn ax_udp_send_to(socket: &AxUdpSocketHandle, buf: &[u8], addr: SocketAddr) -> AxResult { + socket.0.send_to(buf, addr) +} + +pub fn ax_udp_connect(socket: &AxUdpSocketHandle, addr: SocketAddr) -> AxResult { + socket.0.connect(addr) +} + +pub fn ax_udp_send(socket: &AxUdpSocketHandle, buf: &[u8]) -> AxResult { + socket.0.send(buf) +} + +pub fn ax_udp_recv(socket: &AxUdpSocketHandle, buf: &mut [u8]) -> AxResult { + socket.0.recv(buf) +} + +pub fn ax_udp_poll(socket: &AxUdpSocketHandle) -> AxResult { + socket.0.poll() +} + +//////////////////////////////////////////////////////////////////////////////// +// Miscellaneous +//////////////////////////////////////////////////////////////////////////////// + +pub fn ax_dns_query(domain_name: &str) -> AxResult> { + axnet::dns_query(domain_name) +} + +pub fn ax_poll_interfaces() -> AxResult { + axnet::poll_interfaces(); + Ok(()) +} diff --git a/api/arceos_api/src/imp/task.rs b/api/arceos_api/src/imp/task.rs new file mode 100644 index 000000000..e59c48384 --- /dev/null +++ b/api/arceos_api/src/imp/task.rs @@ -0,0 +1,111 @@ +pub fn ax_sleep_until(deadline: crate::time::AxTimeValue) { + #[cfg(feature = "multitask")] + axtask::sleep_until(deadline); + #[cfg(not(feature = "multitask"))] + axhal::time::busy_wait_until(deadline); +} + +pub fn ax_yield_now() { + #[cfg(feature = "multitask")] + axtask::yield_now(); + #[cfg(not(feature = "multitask"))] + if cfg!(feature = "irq") { + axhal::arch::wait_for_irqs(); + } else { + core::hint::spin_loop(); + } +} + +pub fn ax_exit(_exit_code: i32) -> ! { + #[cfg(feature = "multitask")] + axtask::exit(_exit_code); + #[cfg(not(feature = "multitask"))] + axhal::misc::terminate(); +} + +cfg_task! { + use core::time::Duration; + + /// A handle to a task. + pub struct AxTaskHandle { + inner: axtask::AxTaskRef, + id: u64, + } + + impl AxTaskHandle { + /// Returns the task ID. + pub fn id(&self) -> u64 { + self.id + } + } + + /// A handle to a wait queue. + /// + /// A wait queue is used to store sleeping tasks waiting for a certain event + /// to happen. + pub struct AxWaitQueueHandle(axtask::WaitQueue); + + impl AxWaitQueueHandle { + /// Creates a new empty wait queue. + pub const fn new() -> Self { + Self(axtask::WaitQueue::new()) + } + } + + pub fn ax_current_task_id() -> u64 { + axtask::current().id().as_u64() + } + + pub fn ax_spawn(f: F, name: alloc::string::String, stack_size: usize) -> AxTaskHandle + where + F: FnOnce() + Send + 'static, + { + let inner = axtask::spawn_raw(f, name, stack_size); + AxTaskHandle { + id: inner.id().as_u64(), + inner, + } + } + + pub fn ax_wait_for_exit(task: AxTaskHandle) -> Option { + task.inner.join() + } + + pub fn ax_set_current_priority(prio: isize) -> crate::AxResult { + if axtask::set_priority(prio) { + Ok(()) + } else { + axerrno::ax_err!( + BadState, + "ax_set_current_priority: failed to set task priority" + ) + } + } + + pub fn ax_wait_queue_wait( + wq: &AxWaitQueueHandle, + until_condition: impl Fn() -> bool, + timeout: Option, + ) -> bool { + #[cfg(feature = "irq")] + if let Some(dur) = timeout { + return wq.0.wait_timeout_until(dur, until_condition); + } + + if timeout.is_some() { + axlog::warn!("ax_wait_queue_wait: the `timeout` argument is ignored without the `irq` feature"); + } + wq.0.wait_until(until_condition); + false + } + + pub fn ax_wait_queue_wake(wq: &AxWaitQueueHandle, count: u32) { + if count == u32::MAX { + wq.0.notify_all(true); + } else { + for _ in 0..count { + wq.0.notify_one(true); + } + } + } +} diff --git a/api/arceos_api/src/lib.rs b/api/arceos_api/src/lib.rs new file mode 100644 index 000000000..69fec6eaf --- /dev/null +++ b/api/arceos_api/src/lib.rs @@ -0,0 +1,340 @@ +//! Public APIs and types for [ArceOS] modules +//! +//! [ArceOS]: https://github.com/rcore-os/arceos + +#![no_std] +#![feature(ip_in_core)] +#![feature(doc_auto_cfg)] +#![feature(doc_cfg)] +#![allow(unused_imports)] + +#[cfg(any( + feature = "alloc", + feature = "fs", + feature = "net", + feature = "multitask", + feature = "dummy-if-not-enabled" +))] +extern crate alloc; +extern crate axruntime; + +#[macro_use] +mod macros; +mod imp; + +pub use axerrno::{AxError, AxResult}; + +/// Platform-specific constants and parameters. +pub mod config { + pub use axconfig::*; +} + +/// System operations. +pub mod sys { + define_api! { + /// Shutdown the whole system and all CPUs. + pub fn ax_terminate() -> !; + } +} + +/// Time-related operations. +pub mod time { + define_api_type! { + pub type AxTimeValue; + } + + define_api! { + /// Returns the current clock time. + pub fn ax_current_time() -> AxTimeValue; + } +} + +/// Memory management. +pub mod mem { + use core::{alloc::Layout, ptr::NonNull}; + + define_api! { + @cfg "alloc"; + /// Allocate a continuous memory blocks with the given `layout` in + /// the global allocator. + /// + /// Returns [`None`] if the allocation fails. + pub fn ax_alloc(layout: Layout) -> Option>; + /// Deallocate the memory block at the given `ptr` pointer with the given + /// `layout`, which should be allocated by [`ax_alloc`]. + pub fn ax_dealloc(ptr: NonNull, layout: Layout); + } +} + +/// Standard input and output. +pub mod stdio { + use core::fmt; + define_api! { + /// Reads a byte from the console, or returns [`None`] if no input is available. + pub fn ax_console_read_byte() -> Option; + /// Writes a slice of bytes to the console, returns the number of bytes written. + pub fn ax_console_write_bytes(buf: &[u8]) -> crate::AxResult; + /// Writes a formatted string to the console. + pub fn ax_console_write_fmt(args: fmt::Arguments) -> fmt::Result; + } +} + +/// Multi-threading management. +pub mod task { + define_api_type! { + @cfg "multitask"; + pub type AxTaskHandle; + pub type AxWaitQueueHandle; + } + + define_api! { + /// Current task is going to sleep, it will be woken up at the given deadline. + /// + /// If the feature `multitask` is not enabled, it uses busy-wait instead + pub fn ax_sleep_until(deadline: crate::time::AxTimeValue); + + /// Current task gives up the CPU time voluntarily, and switches to another + /// ready task. + /// + /// If the feature `multitask` is not enabled, it does nothing. + pub fn ax_yield_now(); + + /// Exits the current task with the given exit code. + pub fn ax_exit(exit_code: i32) -> !; + } + + define_api! { + @cfg "multitask"; + + /// Returns the current task's ID. + pub fn ax_current_task_id() -> u64; + /// Spawns a new task with the given entry point and other arguments. + pub fn ax_spawn( + f: impl FnOnce() + Send + 'static, + name: alloc::string::String, + stack_size: usize + ) -> AxTaskHandle; + /// Waits for the given task to exit, and returns its exit code (the + /// argument of [`ax_exit`]). + pub fn ax_wait_for_exit(task: AxTaskHandle) -> Option; + /// Sets the priority of the current task. + pub fn ax_set_current_priority(prio: isize) -> crate::AxResult; + + /// Blocks the current task and put it into the wait queue, until the + /// given condition becomes true, or the the given duration has elapsed + /// (if specified). + pub fn ax_wait_queue_wait( + wq: &AxWaitQueueHandle, + until_condition: impl Fn() -> bool, + timeout: Option, + ) -> bool; + /// Wakes up one or more tasks in the wait queue. + /// + /// The maximum number of tasks to wake up is specified by `count`. If + /// `count` is `u32::MAX`, it will wake up all tasks in the wait queue. + pub fn ax_wait_queue_wake(wq: &AxWaitQueueHandle, count: u32); + } +} + +/// Filesystem manipulation operations. +pub mod fs { + use crate::AxResult; + + define_api_type! { + @cfg "fs"; + pub type AxFileHandle; + pub type AxDirHandle; + pub type AxOpenOptions; + pub type AxFileAttr; + pub type AxFileType; + pub type AxFilePerm; + pub type AxDirEntry; + pub type AxSeekFrom; + #[cfg(feature = "myfs")] + pub type AxDisk; + #[cfg(feature = "myfs")] + pub type MyFileSystemIf; + } + + define_api! { + @cfg "fs"; + + /// Opens a file at the path relative to the current directory with the + /// options specified by `opts`. + pub fn ax_open_file(path: &str, opts: &AxOpenOptions) -> AxResult; + /// Opens a directory at the path relative to the current directory with + /// the options specified by `opts`. + pub fn ax_open_dir(path: &str, opts: &AxOpenOptions) -> AxResult; + + /// Reads the file at the current position, returns the number of bytes read. + /// + /// After the read, the cursor will be advanced by the number of bytes read. + pub fn ax_read_file(file: &mut AxFileHandle, buf: &mut [u8]) -> AxResult; + /// Reads the file at the given position, returns the number of bytes read. + /// + /// It does not update the file cursor. + pub fn ax_read_file_at(file: &AxFileHandle, offset: u64, buf: &mut [u8]) -> AxResult; + /// Writes the file at the current position, returns the number of bytes + /// written. + /// + /// After the write, the cursor will be advanced by the number of bytes + /// written. + pub fn ax_write_file(file: &mut AxFileHandle, buf: &[u8]) -> AxResult; + /// Writes the file at the given position, returns the number of bytes + /// written. + /// + /// It does not update the file cursor. + pub fn ax_write_file_at(file: &AxFileHandle, offset: u64, buf: &[u8]) -> AxResult; + /// Truncates the file to the specified size. + pub fn ax_truncate_file(file: &AxFileHandle, size: u64) -> AxResult; + /// Flushes the file, writes all buffered data to the underlying device. + pub fn ax_flush_file(file: &AxFileHandle) -> AxResult; + /// Sets the cursor of the file to the specified offset. Returns the new + /// position after the seek. + pub fn ax_seek_file(file: &mut AxFileHandle, pos: AxSeekFrom) -> AxResult; + /// Returns attributes of the file. + pub fn ax_file_attr(file: &AxFileHandle) -> AxResult; + + /// Reads directory entries starts from the current position into the + /// given buffer, returns the number of entries read. + /// + /// After the read, the cursor of the directory will be advanced by the + /// number of entries read. + pub fn ax_read_dir(dir: &mut AxDirHandle, dirents: &mut [AxDirEntry]) -> AxResult; + /// Creates a new, empty directory at the provided path. + pub fn ax_create_dir(path: &str) -> AxResult; + /// Removes an empty directory. + /// + /// If the directory is not empty, it will return an error. + pub fn ax_remove_dir(path: &str) -> AxResult; + /// Removes a file from the filesystem. + pub fn ax_remove_file(path: &str) -> AxResult; + /// Rename a file or directory to a new name. + /// + /// It will delete the original file if `old` already exists. + pub fn ax_rename(old: &str, new: &str) -> AxResult; + + /// Returns the current working directory. + pub fn ax_current_dir() -> AxResult; + /// Changes the current working directory to the specified path. + pub fn ax_set_current_dir(path: &str) -> AxResult; + } +} + +/// Networking primitives for TCP/UDP communication. +pub mod net { + use crate::{io::AxPollState, AxResult}; + use core::net::{IpAddr, SocketAddr}; + + define_api_type! { + @cfg "net"; + pub type AxTcpSocketHandle; + pub type AxUdpSocketHandle; + } + + define_api! { + @cfg "net"; + + // TCP socket + + /// Creates a new TCP socket. + pub fn ax_tcp_socket() -> AxTcpSocketHandle; + /// Returns the local address and port of the TCP socket. + pub fn ax_tcp_socket_addr(socket: &AxTcpSocketHandle) -> AxResult; + /// Returns the remote address and port of the TCP socket. + pub fn ax_tcp_peer_addr(socket: &AxTcpSocketHandle) -> AxResult; + /// Moves this TCP socket into or out of nonblocking mode. + pub fn ax_tcp_set_nonblocking(socket: &AxTcpSocketHandle, nonblocking: bool) -> AxResult; + + /// Connects the TCP socket to the given address and port. + pub fn ax_tcp_connect(handle: &AxTcpSocketHandle, addr: SocketAddr) -> AxResult; + /// Binds the TCP socket to the given address and port. + pub fn ax_tcp_bind(socket: &AxTcpSocketHandle, addr: SocketAddr) -> AxResult; + /// Starts listening on the bound address and port. + pub fn ax_tcp_listen(socket: &AxTcpSocketHandle, _backlog: usize) -> AxResult; + /// Accepts a new connection on the TCP socket. + /// + /// This function will block the calling thread until a new TCP connection + /// is established. When established, a new TCP socket is returned. + pub fn ax_tcp_accept(socket: &AxTcpSocketHandle) -> AxResult<(AxTcpSocketHandle, SocketAddr)>; + + /// Transmits data in the given buffer on the TCP socket. + pub fn ax_tcp_send(socket: &AxTcpSocketHandle, buf: &[u8]) -> AxResult; + /// Receives data on the TCP socket, and stores it in the given buffer. + /// On success, returns the number of bytes read. + pub fn ax_tcp_recv(socket: &AxTcpSocketHandle, buf: &mut [u8]) -> AxResult; + /// Returns whether the TCP socket is readable or writable. + pub fn ax_tcp_poll(socket: &AxTcpSocketHandle) -> AxResult; + /// Closes the connection on the TCP socket. + pub fn ax_tcp_shutdown(socket: &AxTcpSocketHandle) -> AxResult; + + // UDP socket + + /// Creates a new UDP socket. + pub fn ax_udp_socket() -> AxUdpSocketHandle; + /// Returns the local address and port of the UDP socket. + pub fn ax_udp_socket_addr(socket: &AxUdpSocketHandle) -> AxResult; + /// Returns the remote address and port of the UDP socket. + pub fn ax_udp_peer_addr(socket: &AxUdpSocketHandle) -> AxResult; + /// Moves this UDP socket into or out of nonblocking mode. + pub fn ax_udp_set_nonblocking(socket: &AxUdpSocketHandle, nonblocking: bool) -> AxResult; + + /// Binds the UDP socket to the given address and port. + pub fn ax_udp_bind(socket: &AxUdpSocketHandle, addr: SocketAddr) -> AxResult; + /// Receives a single datagram message on the UDP socket. + pub fn ax_udp_recv_from(socket: &AxUdpSocketHandle, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)>; + /// Receives a single datagram message on the UDP socket, without + /// removing it from the queue. + pub fn ax_udp_peek_from(socket: &AxUdpSocketHandle, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)>; + /// Sends data on the UDP socket to the given address. On success, + /// returns the number of bytes written. + pub fn ax_udp_send_to(socket: &AxUdpSocketHandle, buf: &[u8], addr: SocketAddr) -> AxResult; + + /// Connects this UDP socket to a remote address, allowing the `send` and + /// `recv` to be used to send data and also applies filters to only receive + /// data from the specified address. + pub fn ax_udp_connect(socket: &AxUdpSocketHandle, addr: SocketAddr) -> AxResult; + /// Sends data on the UDP socket to the remote address to which it is + /// connected. + pub fn ax_udp_send(socket: &AxUdpSocketHandle, buf: &[u8]) -> AxResult; + /// Receives a single datagram message on the UDP socket from the remote + /// address to which it is connected. On success, returns the number of + /// bytes read. + pub fn ax_udp_recv(socket: &AxUdpSocketHandle, buf: &mut [u8]) -> AxResult; + /// Returns whether the UDP socket is readable or writable. + pub fn ax_udp_poll(socket: &AxUdpSocketHandle) -> AxResult; + + // Miscellaneous + + /// Resolves the host name to a list of IP addresses. + pub fn ax_dns_query(domain_name: &str) -> AxResult>; + /// Poll the network stack. + /// + /// It may receive packets from the NIC and process them, and transmit queued + /// packets to the NIC. + pub fn ax_poll_interfaces() -> AxResult; + } +} + +/// Graphics manipulation operations. +pub mod display { + define_api_type! { + @cfg "display"; + pub type AxDisplayInfo; + } + + define_api! { + @cfg "display"; + /// Gets the framebuffer information. + pub fn ax_framebuffer_info() -> AxDisplayInfo; + /// Flushes the framebuffer, i.e. show on the screen. + pub fn ax_framebuffer_flush(); + } +} + +/// Input/output operations. +pub mod io { + define_api_type! { + pub type AxPollState; + } +} diff --git a/api/arceos_api/src/macros.rs b/api/arceos_api/src/macros.rs new file mode 100644 index 000000000..09ab1e23c --- /dev/null +++ b/api/arceos_api/src/macros.rs @@ -0,0 +1,79 @@ +#![allow(unused_macros)] + +macro_rules! define_api_type { + ($( $(#[$attr:meta])* $vis:vis type $name:ident; )+) => { + $( + $vis use $crate::imp::$name; + )+ + }; + ( @cfg $feature:literal; $( $(#[$attr:meta])* $vis:vis type $name:ident; )+ ) => { + $( + #[cfg(feature = $feature)] + $(#[$attr])* + $vis use $crate::imp::$name; + + #[cfg(all(feature = "dummy-if-not-enabled", not(feature = $feature)))] + $(#[$attr])* + $vis struct $name; + )+ + }; +} + +macro_rules! define_api { + ($( $(#[$attr:meta])* $vis:vis fn $name:ident( $($arg:ident : $type:ty),* $(,)? ) $( -> $ret:ty )? ; )+) => { + $( + $(#[$attr])* + $vis fn $name( $($arg : $type),* ) $( -> $ret )? { + $crate::imp::$name( $($arg),* ) + } + )+ + }; + ( + @cfg $feature:literal; + $( $(#[$attr:meta])* $vis:vis fn $name:ident( $($arg:ident : $type:ty),* $(,)? ) $( -> $ret:ty )? ; )+ + ) => { + $( + #[cfg(feature = $feature)] + $(#[$attr])* + $vis fn $name( $($arg : $type),* ) $( -> $ret )? { + $crate::imp::$name( $($arg),* ) + } + + #[allow(unused_variables)] + #[cfg(all(feature = "dummy-if-not-enabled", not(feature = $feature)))] + $(#[$attr])* + $vis fn $name( $($arg : $type),* ) $( -> $ret )? { + unimplemented!(stringify!($name)) + } + )+ + }; +} + +macro_rules! _cfg_common { + ( $feature:literal $($item:item)* ) => { + $( + #[cfg(feature = $feature)] + $item + )* + } +} + +macro_rules! cfg_alloc { + ($($item:item)*) => { _cfg_common!{ "alloc" $($item)* } } +} + +macro_rules! cfg_fs { + ($($item:item)*) => { _cfg_common!{ "fs" $($item)* } } +} + +macro_rules! cfg_net { + ($($item:item)*) => { _cfg_common!{ "net" $($item)* } } +} + +macro_rules! cfg_display { + ($($item:item)*) => { _cfg_common!{ "display" $($item)* } } +} + +macro_rules! cfg_task { + ($($item:item)*) => { _cfg_common!{ "multitask" $($item)* } } +} diff --git a/api/arceos_posix_api/.gitignore b/api/arceos_posix_api/.gitignore new file mode 100644 index 000000000..5e6d7ebe4 --- /dev/null +++ b/api/arceos_posix_api/.gitignore @@ -0,0 +1 @@ +ctypes_gen.rs diff --git a/api/arceos_posix_api/Cargo.toml b/api/arceos_posix_api/Cargo.toml new file mode 100644 index 000000000..03081419b --- /dev/null +++ b/api/arceos_posix_api/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "arceos_posix_api" +version = "0.1.0" +edition = "2021" +authors = [ + "Yuekai Jia ", + "yanjuguang ", + "wudashuai ", + "yfblock <321353225@qq.com>", + "scPointer ", + "Shiping Yuan ", +] +description = "POSIX-compatible APIs for ArceOS modules" +license = "GPL-3.0-or-later OR Apache-2.0" +repository = "https://github.com/rcore-os/arceos/tree/main/api/arceos_posix_api" + +[features] +default = [] + +smp = ["axfeat/smp"] +alloc = ["dep:axalloc", "axfeat/alloc"] +multitask = ["axtask/multitask", "axfeat/multitask", "axsync/multitask"] +fd = ["alloc"] +fs = ["dep:axfs", "axfeat/fs", "fd"] +net = ["dep:axnet", "axfeat/net", "fd"] +pipe = ["fd"] +select = ["fd"] +epoll = ["fd"] + +[dependencies] +# ArceOS modules +axfeat = { path = "../axfeat" } +axruntime = { path = "../../modules/axruntime" } +axconfig = { path = "../../modules/axconfig" } +axlog = { path = "../../modules/axlog" } +axhal = { path = "../../modules/axhal" } +axsync = { path = "../../modules/axsync" } +axalloc = { path = "../../modules/axalloc", optional = true } +axtask = { path = "../../modules/axtask", optional = true } +axfs = { path = "../../modules/axfs", optional = true } +axnet = { path = "../../modules/axnet", optional = true } + +# Other crates +axio = { path = "../../crates/axio" } +axerrno = { path = "../../crates/axerrno" } +static_assertions = "1.1.0" +spin = { version = "0.9" } +lazy_static = { version = "1.4", features = ["spin_no_std"] } +flatten_objects = { path = "../../crates/flatten_objects" } + +[build-dependencies] +bindgen ={ version = "0.66" } diff --git a/api/arceos_posix_api/build.rs b/api/arceos_posix_api/build.rs new file mode 100644 index 000000000..3c9aa161b --- /dev/null +++ b/api/arceos_posix_api/build.rs @@ -0,0 +1,106 @@ +fn main() { + use std::io::Write; + + fn gen_pthread_mutex(out_file: &str) -> std::io::Result<()> { + // TODO: generate size and initial content automatically. + let (mutex_size, mutex_init) = if cfg!(feature = "multitask") { + if cfg!(feature = "smp") { + (6, "{0, 8, 0, 0, 0, 0}") // core::mem::transmute::<_, [usize; 6]>(axsync::Mutex::new(())) + } else { + (5, "{8, 0, 0, 0, 0}") // core::mem::transmute::<_, [usize; 5]>(axsync::Mutex::new(())) + } + } else { + (1, "{0}") + }; + + let mut output = Vec::new(); + writeln!( + output, + "// Generated by arceos_posix_api/build.rs, DO NOT edit!" + )?; + writeln!( + output, + r#" +typedef struct {{ + long __l[{mutex_size}]; +}} pthread_mutex_t; + +#define PTHREAD_MUTEX_INITIALIZER {{ .__l = {mutex_init}}} +"# + )?; + std::fs::write(out_file, output)?; + Ok(()) + } + + fn gen_c_to_rust_bindings(in_file: &str, out_file: &str) { + println!("cargo:rerun-if-changed={in_file}"); + + let allow_types = [ + "stat", + "size_t", + "ssize_t", + "off_t", + "mode_t", + "sock.*", + "fd_set", + "timeval", + "pthread_t", + "pthread_attr_t", + "pthread_mutex_t", + "pthread_mutexattr_t", + "epoll_event", + "iovec", + "clockid_t", + "rlimit", + "aibuf", + ]; + let allow_vars = [ + "O_.*", + "AF_.*", + "SOCK_.*", + "IPPROTO_.*", + "FD_.*", + "F_.*", + "_SC_.*", + "EPOLL_CTL_.*", + "EPOLL.*", + "RLIMIT_.*", + "EAI_.*", + "MAXADDRS", + ]; + + #[derive(Debug)] + struct MyCallbacks; + + impl bindgen::callbacks::ParseCallbacks for MyCallbacks { + fn include_file(&self, fname: &str) { + if !fname.contains("ax_pthread_mutex.h") { + println!("cargo:rerun-if-changed={}", fname); + } + } + } + + let mut builder = bindgen::Builder::default() + .header(in_file) + .clang_arg("-I./../../ulib/axlibc/include") + .parse_callbacks(Box::new(MyCallbacks)) + .derive_default(true) + .size_t_is_usize(false) + .use_core(); + for ty in allow_types { + builder = builder.allowlist_type(ty); + } + for var in allow_vars { + builder = builder.allowlist_var(var); + } + + builder + .generate() + .expect("Unable to generate c->rust bindings") + .write_to_file(out_file) + .expect("Couldn't write bindings!"); + } + + gen_pthread_mutex("../../ulib/axlibc/include/ax_pthread_mutex.h").unwrap(); + gen_c_to_rust_bindings("ctypes.h", "src/ctypes_gen.rs"); +} diff --git a/api/arceos_posix_api/ctypes.h b/api/arceos_posix_api/ctypes.h new file mode 100644 index 000000000..7d745f80d --- /dev/null +++ b/api/arceos_posix_api/ctypes.h @@ -0,0 +1,14 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/api/arceos_posix_api/src/imp/fd_ops.rs b/api/arceos_posix_api/src/imp/fd_ops.rs new file mode 100644 index 000000000..75ee61061 --- /dev/null +++ b/api/arceos_posix_api/src/imp/fd_ops.rs @@ -0,0 +1,128 @@ +use alloc::sync::Arc; +use core::ffi::c_int; + +use axerrno::{LinuxError, LinuxResult}; +use axio::PollState; +use flatten_objects::FlattenObjects; +use spin::RwLock; + +use super::stdio::{stdin, stdout}; +use crate::ctypes; + +pub const AX_FILE_LIMIT: usize = 1024; + +pub trait FileLike: Send + Sync { + fn read(&self, buf: &mut [u8]) -> LinuxResult; + fn write(&self, buf: &[u8]) -> LinuxResult; + fn stat(&self) -> LinuxResult; + fn into_any(self: Arc) -> Arc; + fn poll(&self) -> LinuxResult; + fn set_nonblocking(&self, nonblocking: bool) -> LinuxResult; +} + +lazy_static::lazy_static! { + static ref FD_TABLE: RwLock, AX_FILE_LIMIT>> = { + let mut fd_table = FlattenObjects::new(); + fd_table.add_at(0, Arc::new(stdin()) as _).unwrap(); // stdin + fd_table.add_at(1, Arc::new(stdout()) as _).unwrap(); // stdout + fd_table.add_at(2, Arc::new(stdout()) as _).unwrap(); // stderr + RwLock::new(fd_table) + }; +} + +pub fn get_file_like(fd: c_int) -> LinuxResult> { + FD_TABLE + .read() + .get(fd as usize) + .cloned() + .ok_or(LinuxError::EBADF) +} + +pub fn add_file_like(f: Arc) -> LinuxResult { + Ok(FD_TABLE.write().add(f).ok_or(LinuxError::EMFILE)? as c_int) +} + +pub fn close_file_like(fd: c_int) -> LinuxResult { + let f = FD_TABLE + .write() + .remove(fd as usize) + .ok_or(LinuxError::EBADF)?; + drop(f); + Ok(()) +} + +/// Close a file by `fd`. +pub fn sys_close(fd: c_int) -> c_int { + debug!("sys_close <= {}", fd); + if (0..=2).contains(&fd) { + return 0; // stdin, stdout, stderr + } + syscall_body!(sys_close, close_file_like(fd).map(|_| 0)) +} + +fn dup_fd(old_fd: c_int) -> LinuxResult { + let f = get_file_like(old_fd)?; + let new_fd = add_file_like(f)?; + Ok(new_fd) +} + +/// Duplicate a file descriptor. +pub fn sys_dup(old_fd: c_int) -> c_int { + debug!("sys_dup <= {}", old_fd); + syscall_body!(sys_dup, dup_fd(old_fd)) +} + +/// Duplicate a file descriptor, but it uses the file descriptor number specified in `new_fd`. +/// +/// TODO: `dup2` should forcibly close new_fd if it is already opened. +pub fn sys_dup2(old_fd: c_int, new_fd: c_int) -> c_int { + debug!("sys_dup2 <= old_fd: {}, new_fd: {}", old_fd, new_fd); + syscall_body!(sys_dup2, { + if old_fd == new_fd { + let r = sys_fcntl(old_fd, ctypes::F_GETFD as _, 0); + if r >= 0 { + return Ok(old_fd); + } else { + return Ok(r); + } + } + if new_fd as usize >= AX_FILE_LIMIT { + return Err(LinuxError::EBADF); + } + + let f = get_file_like(old_fd)?; + FD_TABLE + .write() + .add_at(new_fd as usize, f) + .ok_or(LinuxError::EMFILE)?; + + Ok(new_fd) + }) +} + +/// Manipulate file descriptor. +/// +/// TODO: `SET/GET` command is ignored, hard-code stdin/stdout +pub fn sys_fcntl(fd: c_int, cmd: c_int, arg: usize) -> c_int { + debug!("sys_fcntl <= fd: {} cmd: {} arg: {}", fd, cmd, arg); + syscall_body!(sys_fcntl, { + match cmd as u32 { + ctypes::F_DUPFD => dup_fd(fd), + ctypes::F_DUPFD_CLOEXEC => { + // TODO: Change fd flags + dup_fd(fd) + } + ctypes::F_SETFL => { + if fd == 0 || fd == 1 || fd == 2 { + return Ok(0); + } + get_file_like(fd)?.set_nonblocking(arg & (ctypes::O_NONBLOCK as usize) > 0)?; + Ok(0) + } + _ => { + warn!("unsupported fcntl parameters: cmd {}", cmd); + Ok(0) + } + } + }) +} diff --git a/api/arceos_posix_api/src/imp/fs.rs b/api/arceos_posix_api/src/imp/fs.rs new file mode 100644 index 000000000..6c92f2da6 --- /dev/null +++ b/api/arceos_posix_api/src/imp/fs.rs @@ -0,0 +1,217 @@ +use alloc::sync::Arc; +use core::ffi::{c_char, c_int}; + +use axerrno::{LinuxError, LinuxResult}; +use axfs::fops::OpenOptions; +use axio::{PollState, SeekFrom}; +use axsync::Mutex; + +use super::fd_ops::{get_file_like, FileLike}; +use crate::{ctypes, utils::char_ptr_to_str}; + +pub struct File { + inner: Mutex, +} + +impl File { + fn new(inner: axfs::fops::File) -> Self { + Self { + inner: Mutex::new(inner), + } + } + + fn add_to_fd_table(self) -> LinuxResult { + super::fd_ops::add_file_like(Arc::new(self)) + } + + fn from_fd(fd: c_int) -> LinuxResult> { + let f = super::fd_ops::get_file_like(fd)?; + f.into_any() + .downcast::() + .map_err(|_| LinuxError::EINVAL) + } +} + +impl FileLike for File { + fn read(&self, buf: &mut [u8]) -> LinuxResult { + Ok(self.inner.lock().read(buf)?) + } + + fn write(&self, buf: &[u8]) -> LinuxResult { + Ok(self.inner.lock().write(buf)?) + } + + fn stat(&self) -> LinuxResult { + let metadata = self.inner.lock().get_attr()?; + let ty = metadata.file_type() as u8; + let perm = metadata.perm().bits() as u32; + let st_mode = ((ty as u32) << 12) | perm; + Ok(ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + st_uid: 1000, + st_gid: 1000, + st_size: metadata.size() as _, + st_blocks: metadata.blocks() as _, + st_blksize: 512, + ..Default::default() + }) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn poll(&self) -> LinuxResult { + Ok(PollState { + readable: true, + writable: true, + }) + } + + fn set_nonblocking(&self, _nonblocking: bool) -> LinuxResult { + Ok(()) + } +} + +/// Convert open flags to [`OpenOptions`]. +fn flags_to_options(flags: c_int, _mode: ctypes::mode_t) -> OpenOptions { + let flags = flags as u32; + let mut options = OpenOptions::new(); + match flags & 0b11 { + ctypes::O_RDONLY => options.read(true), + ctypes::O_WRONLY => options.write(true), + _ => { + options.read(true); + options.write(true); + } + }; + if flags & ctypes::O_APPEND != 0 { + options.append(true); + } + if flags & ctypes::O_TRUNC != 0 { + options.truncate(true); + } + if flags & ctypes::O_CREAT != 0 { + options.create(true); + } + if flags & ctypes::O_EXEC != 0 { + options.create_new(true); + } + options +} + +/// Open a file by `filename` and insert it into the file descriptor table. +/// +/// Return its index in the file table (`fd`). Return `EMFILE` if it already +/// has the maximum number of files open. +pub fn sys_open(filename: *const c_char, flags: c_int, mode: ctypes::mode_t) -> c_int { + let filename = char_ptr_to_str(filename); + debug!("sys_open <= {:?} {:#o} {:#o}", filename, flags, mode); + syscall_body!(sys_open, { + let options = flags_to_options(flags, mode); + let file = axfs::fops::File::open(filename?, &options)?; + File::new(file).add_to_fd_table() + }) +} + +/// Set the position of the file indicated by `fd`. +/// +/// Return its position after seek. +pub fn sys_lseek(fd: c_int, offset: ctypes::off_t, whence: c_int) -> ctypes::off_t { + debug!("sys_lseek <= {} {} {}", fd, offset, whence); + syscall_body!(sys_lseek, { + let pos = match whence { + 0 => SeekFrom::Start(offset as _), + 1 => SeekFrom::Current(offset as _), + 2 => SeekFrom::End(offset as _), + _ => return Err(LinuxError::EINVAL), + }; + let off = File::from_fd(fd)?.inner.lock().seek(pos)?; + Ok(off) + }) +} + +/// Get the file metadata by `path` and write into `buf`. +/// +/// Return 0 if success. +pub unsafe fn sys_stat(path: *const c_char, buf: *mut ctypes::stat) -> c_int { + let path = char_ptr_to_str(path); + debug!("sys_stat <= {:?} {:#x}", path, buf as usize); + syscall_body!(sys_stat, { + if buf.is_null() { + return Err(LinuxError::EFAULT); + } + let mut options = OpenOptions::new(); + options.read(true); + let file = axfs::fops::File::open(path?, &options)?; + let st = File::new(file).stat()?; + unsafe { *buf = st }; + Ok(0) + }) +} + +/// Get file metadata by `fd` and write into `buf`. +/// +/// Return 0 if success. +pub unsafe fn sys_fstat(fd: c_int, buf: *mut ctypes::stat) -> c_int { + debug!("sys_fstat <= {} {:#x}", fd, buf as usize); + syscall_body!(sys_fstat, { + if buf.is_null() { + return Err(LinuxError::EFAULT); + } + + unsafe { *buf = get_file_like(fd)?.stat()? }; + Ok(0) + }) +} + +/// Get the metadata of the symbolic link and write into `buf`. +/// +/// Return 0 if success. +pub unsafe fn sys_lstat(path: *const c_char, buf: *mut ctypes::stat) -> ctypes::ssize_t { + let path = char_ptr_to_str(path); + debug!("sys_lstat <= {:?} {:#x}", path, buf as usize); + syscall_body!(sys_lstat, { + if buf.is_null() { + return Err(LinuxError::EFAULT); + } + unsafe { *buf = Default::default() }; // TODO + Ok(0) + }) +} + +/// Get the path of the current directory. +pub fn sys_getcwd(buf: *mut c_char, size: usize) -> *mut c_char { + debug!("sys_getcwd <= {:#x} {}", buf as usize, size); + syscall_body!(sys_getcwd, { + if buf.is_null() { + return Ok(core::ptr::null::() as _); + } + let dst = unsafe { core::slice::from_raw_parts_mut(buf as *mut u8, size as _) }; + let cwd = axfs::api::current_dir()?; + let cwd = cwd.as_bytes(); + if cwd.len() < size { + dst[..cwd.len()].copy_from_slice(cwd); + dst[cwd.len()] = 0; + Ok(buf) + } else { + Err(LinuxError::ERANGE) + } + }) +} + +/// Rename `old` to `new` +/// If new exists, it is first removed. +/// +/// Return 0 if the operation succeeds, otherwise return -1. +pub fn sys_rename(old: *const c_char, new: *const c_char) -> c_int { + syscall_body!(sys_rename, { + let old_path = char_ptr_to_str(old)?; + let new_path = char_ptr_to_str(new)?; + debug!("sys_rename <= old: {:?}, new: {:?}", old_path, new_path); + axfs::api::rename(old_path, new_path)?; + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/imp/io.rs b/api/arceos_posix_api/src/imp/io.rs new file mode 100644 index 000000000..269ce2035 --- /dev/null +++ b/api/arceos_posix_api/src/imp/io.rs @@ -0,0 +1,72 @@ +use crate::ctypes; +use axerrno::LinuxError; +use core::ffi::{c_int, c_void}; + +#[cfg(feature = "fd")] +use crate::imp::fd_ops::get_file_like; +#[cfg(not(feature = "fd"))] +use axio::prelude::*; + +/// Read data from the file indicated by `fd`. +/// +/// Return the read size if success. +pub fn sys_read(fd: c_int, buf: *mut c_void, count: usize) -> ctypes::ssize_t { + debug!("sys_read <= {} {:#x} {}", fd, buf as usize, count); + syscall_body!(sys_read, { + if buf.is_null() { + return Err(LinuxError::EFAULT); + } + let dst = unsafe { core::slice::from_raw_parts_mut(buf as *mut u8, count) }; + #[cfg(feature = "fd")] + { + Ok(get_file_like(fd)?.read(dst)? as ctypes::ssize_t) + } + #[cfg(not(feature = "fd"))] + match fd { + 0 => Ok(super::stdio::stdin().read(dst)? as ctypes::ssize_t), + 1 | 2 => Err(LinuxError::EPERM), + _ => Err(LinuxError::EBADF), + } + }) +} + +/// Write data to the file indicated by `fd`. +/// +/// Return the written size if success. +pub fn sys_write(fd: c_int, buf: *const c_void, count: usize) -> ctypes::ssize_t { + debug!("sys_write <= {} {:#x} {}", fd, buf as usize, count); + syscall_body!(sys_write, { + if buf.is_null() { + return Err(LinuxError::EFAULT); + } + let src = unsafe { core::slice::from_raw_parts(buf as *const u8, count) }; + #[cfg(feature = "fd")] + { + Ok(get_file_like(fd)?.write(src)? as ctypes::ssize_t) + } + #[cfg(not(feature = "fd"))] + match fd { + 0 => Err(LinuxError::EPERM), + 1 | 2 => Ok(super::stdio::stdout().write(src)? as ctypes::ssize_t), + _ => Err(LinuxError::EBADF), + } + }) +} + +/// Write a vector. +pub unsafe fn sys_writev(fd: c_int, iov: *const ctypes::iovec, iocnt: c_int) -> ctypes::ssize_t { + debug!("sys_writev <= fd: {}", fd); + syscall_body!(sys_writev, { + if !(0..=1024).contains(&iocnt) { + return Err(LinuxError::EINVAL); + } + + let iovs = unsafe { core::slice::from_raw_parts(iov, iocnt as usize) }; + let mut ret = 0; + for iov in iovs.iter() { + ret += sys_write(fd, iov.iov_base, iov.iov_len); + } + + Ok(ret) + }) +} diff --git a/api/arceos_posix_api/src/imp/io_mpx/epoll.rs b/api/arceos_posix_api/src/imp/io_mpx/epoll.rs new file mode 100644 index 000000000..5404b2f13 --- /dev/null +++ b/api/arceos_posix_api/src/imp/io_mpx/epoll.rs @@ -0,0 +1,205 @@ +//! `epoll` implementation. +//! +//! TODO: do not support `EPOLLET` flag + +use alloc::collections::btree_map::Entry; +use alloc::collections::BTreeMap; +use alloc::sync::Arc; +use core::{ffi::c_int, time::Duration}; + +use axerrno::{LinuxError, LinuxResult}; +use axhal::time::current_time; +use axsync::Mutex; + +use crate::ctypes; +use crate::imp::fd_ops::{add_file_like, get_file_like, FileLike}; + +pub struct EpollInstance { + events: Mutex>, +} + +unsafe impl Send for ctypes::epoll_event {} +unsafe impl Sync for ctypes::epoll_event {} + +impl EpollInstance { + // TODO: parse flags + pub fn new(_flags: usize) -> Self { + Self { + events: Mutex::new(BTreeMap::new()), + } + } + + fn from_fd(fd: c_int) -> LinuxResult> { + get_file_like(fd)? + .into_any() + .downcast::() + .map_err(|_| LinuxError::EINVAL) + } + + fn control(&self, op: usize, fd: usize, event: &ctypes::epoll_event) -> LinuxResult { + match get_file_like(fd as c_int) { + Ok(_) => {} + Err(e) => return Err(e), + } + + match op as u32 { + ctypes::EPOLL_CTL_ADD => { + if let Entry::Vacant(e) = self.events.lock().entry(fd) { + e.insert(*event); + } else { + return Err(LinuxError::EEXIST); + } + } + ctypes::EPOLL_CTL_MOD => { + let mut events = self.events.lock(); + if let Entry::Occupied(mut ocp) = events.entry(fd) { + ocp.insert(*event); + } else { + return Err(LinuxError::ENOENT); + } + } + ctypes::EPOLL_CTL_DEL => { + let mut events = self.events.lock(); + if let Entry::Occupied(ocp) = events.entry(fd) { + ocp.remove_entry(); + } else { + return Err(LinuxError::ENOENT); + } + } + _ => { + return Err(LinuxError::EINVAL); + } + } + Ok(0) + } + + fn poll_all(&self, events: &mut [ctypes::epoll_event]) -> LinuxResult { + let ready_list = self.events.lock(); + let mut events_num = 0; + + for (infd, ev) in ready_list.iter() { + match get_file_like(*infd as c_int)?.poll() { + Err(_) => { + if (ev.events & ctypes::EPOLLERR) != 0 { + events[events_num].events = ctypes::EPOLLERR; + events[events_num].data = ev.data; + events_num += 1; + } + } + Ok(state) => { + if state.readable && (ev.events & ctypes::EPOLLIN != 0) { + events[events_num].events = ctypes::EPOLLIN; + events[events_num].data = ev.data; + events_num += 1; + } + + if state.writable && (ev.events & ctypes::EPOLLOUT != 0) { + events[events_num].events = ctypes::EPOLLOUT; + events[events_num].data = ev.data; + events_num += 1; + } + } + } + } + Ok(events_num) + } +} + +impl FileLike for EpollInstance { + fn read(&self, _buf: &mut [u8]) -> LinuxResult { + Err(LinuxError::ENOSYS) + } + + fn write(&self, _buf: &[u8]) -> LinuxResult { + Err(LinuxError::ENOSYS) + } + + fn stat(&self) -> LinuxResult { + let st_mode = 0o600u32; // rw------- + Ok(ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + ..Default::default() + }) + } + + fn into_any(self: Arc) -> alloc::sync::Arc { + self + } + + fn poll(&self) -> LinuxResult { + Err(LinuxError::ENOSYS) + } + + fn set_nonblocking(&self, _nonblocking: bool) -> LinuxResult { + Ok(()) + } +} + +/// Creates a new epoll instance. +/// +/// It returns a file descriptor referring to the new epoll instance. +pub fn sys_epoll_create(size: c_int) -> c_int { + debug!("sys_epoll_create <= {}", size); + syscall_body!(sys_epoll_create, { + if size < 0 { + return Err(LinuxError::EINVAL); + } + let epoll_instance = EpollInstance::new(0); + add_file_like(Arc::new(epoll_instance)) + }) +} + +/// Control interface for an epoll file descriptor +pub unsafe fn sys_epoll_ctl( + epfd: c_int, + op: c_int, + fd: c_int, + event: *mut ctypes::epoll_event, +) -> c_int { + debug!("sys_epoll_ctl <= epfd: {} op: {} fd: {}", epfd, op, fd); + syscall_body!(sys_epoll_ctl, { + let ret = unsafe { + EpollInstance::from_fd(epfd)?.control(op as usize, fd as usize, &(*event))? as c_int + }; + Ok(ret) + }) +} + +/// Waits for events on the epoll instance referred to by the file descriptor epfd. +pub unsafe fn sys_epoll_wait( + epfd: c_int, + events: *mut ctypes::epoll_event, + maxevents: c_int, + timeout: c_int, +) -> c_int { + debug!( + "sys_epoll_wait <= epfd: {}, maxevents: {}, timeout: {}", + epfd, maxevents, timeout + ); + + syscall_body!(sys_epoll_wait, { + if maxevents <= 0 { + return Err(LinuxError::EINVAL); + } + let events = unsafe { core::slice::from_raw_parts_mut(events, maxevents as usize) }; + let deadline = (!timeout.is_negative()) + .then(|| current_time() + Duration::from_millis(timeout as u64)); + let epoll_instance = EpollInstance::from_fd(epfd)?; + loop { + #[cfg(feature = "net")] + axnet::poll_interfaces(); + let events_num = epoll_instance.poll_all(events)?; + if events_num > 0 { + return Ok(events_num as c_int); + } + + if deadline.map_or(false, |ddl| current_time() >= ddl) { + debug!(" timeout!"); + return Ok(0); + } + crate::sys_sched_yield(); + } + }) +} diff --git a/api/arceos_posix_api/src/imp/io_mpx/mod.rs b/api/arceos_posix_api/src/imp/io_mpx/mod.rs new file mode 100644 index 000000000..397ed44f3 --- /dev/null +++ b/api/arceos_posix_api/src/imp/io_mpx/mod.rs @@ -0,0 +1,16 @@ +//! I/O multiplexing: +//! +//! * [`select`](select::sys_select) +//! * [`epoll_create`](epoll::sys_epoll_create) +//! * [`epoll_ctl`](epoll::sys_epoll_ctl) +//! * [`epoll_wait`](epoll::sys_epoll_wait) + +#[cfg(feature = "epoll")] +mod epoll; +#[cfg(feature = "select")] +mod select; + +#[cfg(feature = "epoll")] +pub use self::epoll::{sys_epoll_create, sys_epoll_ctl, sys_epoll_wait}; +#[cfg(feature = "select")] +pub use self::select::sys_select; diff --git a/api/arceos_posix_api/src/imp/io_mpx/select.rs b/api/arceos_posix_api/src/imp/io_mpx/select.rs new file mode 100644 index 000000000..6986598b5 --- /dev/null +++ b/api/arceos_posix_api/src/imp/io_mpx/select.rs @@ -0,0 +1,165 @@ +use core::ffi::c_int; + +use axerrno::{LinuxError, LinuxResult}; +use axhal::time::current_time; + +use crate::{ctypes, imp::fd_ops::get_file_like}; + +const FD_SETSIZE: usize = 1024; +const BITS_PER_USIZE: usize = usize::BITS as usize; +const FD_SETSIZE_USIZES: usize = FD_SETSIZE.div_ceil(BITS_PER_USIZE); + +struct FdSets { + nfds: usize, + bits: [usize; FD_SETSIZE_USIZES * 3], +} + +impl FdSets { + fn from( + nfds: usize, + read_fds: *const ctypes::fd_set, + write_fds: *const ctypes::fd_set, + except_fds: *const ctypes::fd_set, + ) -> Self { + let nfds = nfds.min(FD_SETSIZE); + let nfds_usizes = nfds.div_ceil(BITS_PER_USIZE); + let mut bits = core::mem::MaybeUninit::<[usize; FD_SETSIZE_USIZES * 3]>::uninit(); + let bits_ptr = unsafe { core::mem::transmute(bits.as_mut_ptr()) }; + + let copy_from_fd_set = |bits_ptr: *mut usize, fds: *const ctypes::fd_set| unsafe { + let dst = core::slice::from_raw_parts_mut(bits_ptr, nfds_usizes); + if fds.is_null() { + dst.fill(0); + } else { + let fds_ptr = (*fds).fds_bits.as_ptr() as *const usize; + let src = core::slice::from_raw_parts(fds_ptr, nfds_usizes); + dst.copy_from_slice(src); + } + }; + + let bits = unsafe { + copy_from_fd_set(bits_ptr, read_fds); + copy_from_fd_set(bits_ptr.add(FD_SETSIZE_USIZES), write_fds); + copy_from_fd_set(bits_ptr.add(FD_SETSIZE_USIZES * 2), except_fds); + bits.assume_init() + }; + Self { nfds, bits } + } + + fn poll_all( + &self, + res_read_fds: *mut ctypes::fd_set, + res_write_fds: *mut ctypes::fd_set, + res_except_fds: *mut ctypes::fd_set, + ) -> LinuxResult { + let mut read_bits_ptr = self.bits.as_ptr(); + let mut write_bits_ptr = unsafe { read_bits_ptr.add(FD_SETSIZE_USIZES) }; + let mut execpt_bits_ptr = unsafe { read_bits_ptr.add(FD_SETSIZE_USIZES * 2) }; + let mut i = 0; + let mut res_num = 0; + while i < self.nfds { + let read_bits = unsafe { *read_bits_ptr }; + let write_bits = unsafe { *write_bits_ptr }; + let except_bits = unsafe { *execpt_bits_ptr }; + unsafe { + read_bits_ptr = read_bits_ptr.add(1); + write_bits_ptr = write_bits_ptr.add(1); + execpt_bits_ptr = execpt_bits_ptr.add(1); + } + + let all_bits = read_bits | write_bits | except_bits; + if all_bits == 0 { + i += BITS_PER_USIZE; + continue; + } + let mut j = 0; + while j < BITS_PER_USIZE && i + j < self.nfds { + let bit = 1 << j; + if all_bits & bit == 0 { + j += 1; + continue; + } + let fd = i + j; + match get_file_like(fd as _)?.poll() { + Ok(state) => { + if state.readable && read_bits & bit != 0 { + unsafe { set_fd_set(res_read_fds, fd) }; + res_num += 1; + } + if state.writable && write_bits & bit != 0 { + unsafe { set_fd_set(res_write_fds, fd) }; + res_num += 1; + } + } + Err(e) => { + debug!(" except: {} {:?}", fd, e); + if except_bits & bit != 0 { + unsafe { set_fd_set(res_except_fds, fd) }; + res_num += 1; + } + } + } + j += 1; + } + i += BITS_PER_USIZE; + } + Ok(res_num) + } +} + +/// Monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation +pub unsafe fn sys_select( + nfds: c_int, + readfds: *mut ctypes::fd_set, + writefds: *mut ctypes::fd_set, + exceptfds: *mut ctypes::fd_set, + timeout: *mut ctypes::timeval, +) -> c_int { + debug!( + "sys_select <= {} {:#x} {:#x} {:#x}", + nfds, readfds as usize, writefds as usize, exceptfds as usize + ); + syscall_body!(sys_select, { + if nfds < 0 { + return Err(LinuxError::EINVAL); + } + let nfds = (nfds as usize).min(FD_SETSIZE); + let deadline = unsafe { timeout.as_ref().map(|t| current_time() + (*t).into()) }; + let fd_sets = FdSets::from(nfds, readfds, writefds, exceptfds); + + unsafe { + zero_fd_set(readfds, nfds); + zero_fd_set(writefds, nfds); + zero_fd_set(exceptfds, nfds); + } + + loop { + #[cfg(feature = "net")] + axnet::poll_interfaces(); + let res = fd_sets.poll_all(readfds, writefds, exceptfds)?; + if res > 0 { + return Ok(res); + } + + if deadline.map_or(false, |ddl| current_time() >= ddl) { + debug!(" timeout!"); + return Ok(0); + } + crate::sys_sched_yield(); + } + }) +} + +unsafe fn zero_fd_set(fds: *mut ctypes::fd_set, nfds: usize) { + if !fds.is_null() { + let nfds_usizes = nfds.div_ceil(BITS_PER_USIZE); + let dst = &mut (*fds).fds_bits[..nfds_usizes]; + dst.fill(0); + } +} + +unsafe fn set_fd_set(fds: *mut ctypes::fd_set, fd: usize) { + if !fds.is_null() { + (*fds).fds_bits[fd / BITS_PER_USIZE] |= 1 << (fd % BITS_PER_USIZE); + } +} diff --git a/api/arceos_posix_api/src/imp/mod.rs b/api/arceos_posix_api/src/imp/mod.rs new file mode 100644 index 000000000..603f934ba --- /dev/null +++ b/api/arceos_posix_api/src/imp/mod.rs @@ -0,0 +1,20 @@ +mod stdio; + +pub mod io; +pub mod resources; +pub mod sys; +pub mod task; +pub mod time; + +#[cfg(feature = "fd")] +pub mod fd_ops; +#[cfg(feature = "fs")] +pub mod fs; +#[cfg(any(feature = "select", feature = "epoll"))] +pub mod io_mpx; +#[cfg(feature = "net")] +pub mod net; +#[cfg(feature = "pipe")] +pub mod pipe; +#[cfg(feature = "multitask")] +pub mod pthread; diff --git a/api/arceos_posix_api/src/imp/net.rs b/api/arceos_posix_api/src/imp/net.rs new file mode 100644 index 000000000..861e4737b --- /dev/null +++ b/api/arceos_posix_api/src/imp/net.rs @@ -0,0 +1,581 @@ +use alloc::{sync::Arc, vec, vec::Vec}; +use core::ffi::{c_char, c_int, c_void}; +use core::mem::size_of; +use core::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; + +use axerrno::{LinuxError, LinuxResult}; +use axio::PollState; +use axnet::{TcpSocket, UdpSocket}; +use axsync::Mutex; + +use super::fd_ops::FileLike; +use crate::ctypes; +use crate::utils::char_ptr_to_str; + +pub enum Socket { + Udp(Mutex), + Tcp(Mutex), +} + +impl Socket { + fn add_to_fd_table(self) -> LinuxResult { + super::fd_ops::add_file_like(Arc::new(self)) + } + + fn from_fd(fd: c_int) -> LinuxResult> { + let f = super::fd_ops::get_file_like(fd)?; + f.into_any() + .downcast::() + .map_err(|_| LinuxError::EINVAL) + } + + fn send(&self, buf: &[u8]) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().send(buf)?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().send(buf)?), + } + } + + fn recv(&self, buf: &mut [u8]) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().recv_from(buf).map(|e| e.0)?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().recv(buf)?), + } + } + + pub fn poll(&self) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().poll()?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().poll()?), + } + } + + fn local_addr(&self) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().local_addr()?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().local_addr()?), + } + } + + fn peer_addr(&self) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().peer_addr()?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().peer_addr()?), + } + } + + fn bind(&self, addr: SocketAddr) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().bind(addr)?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().bind(addr)?), + } + } + + fn connect(&self, addr: SocketAddr) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => Ok(udpsocket.lock().connect(addr)?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().connect(addr)?), + } + } + + fn sendto(&self, buf: &[u8], addr: SocketAddr) -> LinuxResult { + match self { + // diff: must bind before sendto + Socket::Udp(udpsocket) => Ok(udpsocket.lock().send_to(buf, addr)?), + Socket::Tcp(_) => Err(LinuxError::EISCONN), + } + } + + fn recvfrom(&self, buf: &mut [u8]) -> LinuxResult<(usize, Option)> { + match self { + // diff: must bind before recvfrom + Socket::Udp(udpsocket) => Ok(udpsocket + .lock() + .recv_from(buf) + .map(|res| (res.0, Some(res.1)))?), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().recv(buf).map(|res| (res, None))?), + } + } + + fn listen(&self) -> LinuxResult { + match self { + Socket::Udp(_) => Err(LinuxError::EOPNOTSUPP), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().listen()?), + } + } + + fn accept(&self) -> LinuxResult { + match self { + Socket::Udp(_) => Err(LinuxError::EOPNOTSUPP), + Socket::Tcp(tcpsocket) => Ok(tcpsocket.lock().accept()?), + } + } + + fn shutdown(&self) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => { + let udpsocket = udpsocket.lock(); + udpsocket.peer_addr()?; + udpsocket.shutdown()?; + Ok(()) + } + + Socket::Tcp(tcpsocket) => { + let tcpsocket = tcpsocket.lock(); + tcpsocket.peer_addr()?; + tcpsocket.shutdown()?; + Ok(()) + } + } + } +} + +impl FileLike for Socket { + fn read(&self, buf: &mut [u8]) -> LinuxResult { + self.recv(buf) + } + + fn write(&self, buf: &[u8]) -> LinuxResult { + self.send(buf) + } + + fn stat(&self) -> LinuxResult { + // not really implemented + let st_mode = 0o140000 | 0o777u32; // S_IFSOCK | rwxrwxrwx + Ok(ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + st_uid: 1000, + st_gid: 1000, + st_blksize: 4096, + ..Default::default() + }) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn poll(&self) -> LinuxResult { + self.poll() + } + + fn set_nonblocking(&self, nonblock: bool) -> LinuxResult { + match self { + Socket::Udp(udpsocket) => udpsocket.lock().set_nonblocking(nonblock), + Socket::Tcp(tcpsocket) => tcpsocket.lock().set_nonblocking(nonblock), + } + Ok(()) + } +} + +impl From for ctypes::sockaddr_in { + fn from(addr: SocketAddrV4) -> ctypes::sockaddr_in { + ctypes::sockaddr_in { + sin_family: ctypes::AF_INET as u16, + sin_port: addr.port().to_be(), + sin_addr: ctypes::in_addr { + // `s_addr` is stored as BE on all machines and the array is in BE order. + // So the native endian conversion method is used so that it's never swapped. + s_addr: u32::from_ne_bytes(addr.ip().octets()), + }, + sin_zero: [0; 8], + } + } +} + +impl From for SocketAddrV4 { + fn from(addr: ctypes::sockaddr_in) -> SocketAddrV4 { + SocketAddrV4::new( + Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes()), + u16::from_be(addr.sin_port), + ) + } +} + +fn into_sockaddr(addr: SocketAddr) -> (ctypes::sockaddr, ctypes::socklen_t) { + debug!(" Sockaddr: {}", addr); + match addr { + SocketAddr::V4(addr) => ( + unsafe { *(&ctypes::sockaddr_in::from(addr) as *const _ as *const ctypes::sockaddr) }, + size_of::() as _, + ), + SocketAddr::V6(_) => panic!("IPv6 is not supported"), + } +} + +fn from_sockaddr( + addr: *const ctypes::sockaddr, + addrlen: ctypes::socklen_t, +) -> LinuxResult { + if addr.is_null() { + return Err(LinuxError::EFAULT); + } + if addrlen != size_of::() as _ { + return Err(LinuxError::EINVAL); + } + + let mid = unsafe { *(addr as *const ctypes::sockaddr_in) }; + if mid.sin_family != ctypes::AF_INET as u16 { + return Err(LinuxError::EINVAL); + } + + let res = SocketAddr::V4(mid.into()); + debug!(" load sockaddr:{:#x} => {:?}", addr as usize, res); + Ok(res) +} + +/// Create an socket for communication. +/// +/// Return the socket file descriptor. +pub fn sys_socket(domain: c_int, socktype: c_int, protocol: c_int) -> c_int { + debug!("sys_socket <= {} {} {}", domain, socktype, protocol); + let (domain, socktype, protocol) = (domain as u32, socktype as u32, protocol as u32); + syscall_body!(sys_socket, { + match (domain, socktype, protocol) { + (ctypes::AF_INET, ctypes::SOCK_STREAM, ctypes::IPPROTO_TCP) + | (ctypes::AF_INET, ctypes::SOCK_STREAM, 0) => { + Socket::Tcp(Mutex::new(TcpSocket::new())).add_to_fd_table() + } + (ctypes::AF_INET, ctypes::SOCK_DGRAM, ctypes::IPPROTO_UDP) + | (ctypes::AF_INET, ctypes::SOCK_DGRAM, 0) => { + Socket::Udp(Mutex::new(UdpSocket::new())).add_to_fd_table() + } + _ => Err(LinuxError::EINVAL), + } + }) +} + +/// Bind a address to a socket. +/// +/// Return 0 if success. +pub fn sys_bind( + socket_fd: c_int, + socket_addr: *const ctypes::sockaddr, + addrlen: ctypes::socklen_t, +) -> c_int { + debug!( + "sys_bind <= {} {:#x} {}", + socket_fd, socket_addr as usize, addrlen + ); + syscall_body!(sys_bind, { + let addr = from_sockaddr(socket_addr, addrlen)?; + Socket::from_fd(socket_fd)?.bind(addr)?; + Ok(0) + }) +} + +/// Connects the socket to the address specified. +/// +/// Return 0 if success. +pub fn sys_connect( + socket_fd: c_int, + socket_addr: *const ctypes::sockaddr, + addrlen: ctypes::socklen_t, +) -> c_int { + debug!( + "sys_connect <= {} {:#x} {}", + socket_fd, socket_addr as usize, addrlen + ); + syscall_body!(sys_connect, { + let addr = from_sockaddr(socket_addr, addrlen)?; + Socket::from_fd(socket_fd)?.connect(addr)?; + Ok(0) + }) +} + +/// Send a message on a socket to the address specified. +/// +/// Return the number of bytes sent if success. +pub fn sys_sendto( + socket_fd: c_int, + buf_ptr: *const c_void, + len: ctypes::size_t, + flag: c_int, // currently not used + socket_addr: *const ctypes::sockaddr, + addrlen: ctypes::socklen_t, +) -> ctypes::ssize_t { + debug!( + "sys_sendto <= {} {:#x} {} {} {:#x} {}", + socket_fd, buf_ptr as usize, len, flag, socket_addr as usize, addrlen + ); + syscall_body!(sys_sendto, { + if buf_ptr.is_null() { + return Err(LinuxError::EFAULT); + } + let addr = from_sockaddr(socket_addr, addrlen)?; + let buf = unsafe { core::slice::from_raw_parts(buf_ptr as *const u8, len) }; + Socket::from_fd(socket_fd)?.sendto(buf, addr) + }) +} + +/// Send a message on a socket to the address connected. +/// +/// Return the number of bytes sent if success. +pub fn sys_send( + socket_fd: c_int, + buf_ptr: *const c_void, + len: ctypes::size_t, + flag: c_int, // currently not used +) -> ctypes::ssize_t { + debug!( + "sys_sendto <= {} {:#x} {} {}", + socket_fd, buf_ptr as usize, len, flag + ); + syscall_body!(sys_send, { + if buf_ptr.is_null() { + return Err(LinuxError::EFAULT); + } + let buf = unsafe { core::slice::from_raw_parts(buf_ptr as *const u8, len) }; + Socket::from_fd(socket_fd)?.send(buf) + }) +} + +/// Receive a message on a socket and get its source address. +/// +/// Return the number of bytes received if success. +pub unsafe fn sys_recvfrom( + socket_fd: c_int, + buf_ptr: *mut c_void, + len: ctypes::size_t, + flag: c_int, // currently not used + socket_addr: *mut ctypes::sockaddr, + addrlen: *mut ctypes::socklen_t, +) -> ctypes::ssize_t { + debug!( + "sys_recvfrom <= {} {:#x} {} {} {:#x} {:#x}", + socket_fd, buf_ptr as usize, len, flag, socket_addr as usize, addrlen as usize + ); + syscall_body!(sys_recvfrom, { + if buf_ptr.is_null() || socket_addr.is_null() || addrlen.is_null() { + return Err(LinuxError::EFAULT); + } + let socket = Socket::from_fd(socket_fd)?; + let buf = unsafe { core::slice::from_raw_parts_mut(buf_ptr as *mut u8, len) }; + + let res = socket.recvfrom(buf)?; + if let Some(addr) = res.1 { + unsafe { + (*socket_addr, *addrlen) = into_sockaddr(addr); + } + } + Ok(res.0) + }) +} + +/// Receive a message on a socket. +/// +/// Return the number of bytes received if success. +pub fn sys_recv( + socket_fd: c_int, + buf_ptr: *mut c_void, + len: ctypes::size_t, + flag: c_int, // currently not used +) -> ctypes::ssize_t { + debug!( + "sys_recv <= {} {:#x} {} {}", + socket_fd, buf_ptr as usize, len, flag + ); + syscall_body!(sys_recv, { + if buf_ptr.is_null() { + return Err(LinuxError::EFAULT); + } + let buf = unsafe { core::slice::from_raw_parts_mut(buf_ptr as *mut u8, len) }; + Socket::from_fd(socket_fd)?.recv(buf) + }) +} + +/// Listen for connections on a socket +/// +/// Return 0 if success. +pub fn sys_listen( + socket_fd: c_int, + backlog: c_int, // currently not used +) -> c_int { + debug!("sys_listen <= {} {}", socket_fd, backlog); + syscall_body!(sys_listen, { + Socket::from_fd(socket_fd)?.listen()?; + Ok(0) + }) +} + +/// Accept for connections on a socket +/// +/// Return file descriptor for the accepted socket if success. +pub unsafe fn sys_accept( + socket_fd: c_int, + socket_addr: *mut ctypes::sockaddr, + socket_len: *mut ctypes::socklen_t, +) -> c_int { + debug!( + "sys_accept <= {} {:#x} {:#x}", + socket_fd, socket_addr as usize, socket_len as usize + ); + syscall_body!(sys_accept, { + if socket_addr.is_null() || socket_len.is_null() { + return Err(LinuxError::EFAULT); + } + let socket = Socket::from_fd(socket_fd)?; + let new_socket = socket.accept()?; + let addr = new_socket.peer_addr()?; + let new_fd = Socket::add_to_fd_table(Socket::Tcp(Mutex::new(new_socket)))?; + unsafe { + (*socket_addr, *socket_len) = into_sockaddr(addr); + } + Ok(new_fd) + }) +} + +/// Shut down a full-duplex connection. +/// +/// Return 0 if success. +pub fn sys_shutdown( + socket_fd: c_int, + flag: c_int, // currently not used +) -> c_int { + debug!("sys_shutdown <= {} {}", socket_fd, flag); + syscall_body!(sys_shutdown, { + Socket::from_fd(socket_fd)?.shutdown()?; + Ok(0) + }) +} + +/// Query addresses for a domain name. +/// +/// Only IPv4. Ports are always 0. Ignore servname and hint. +/// Results' ai_flags and ai_canonname are 0 or NULL. +/// +/// Return address number if success. +pub unsafe fn sys_getaddrinfo( + nodename: *const c_char, + servname: *const c_char, + _hints: *const ctypes::addrinfo, + res: *mut *mut ctypes::addrinfo, +) -> c_int { + let name = char_ptr_to_str(nodename); + let port = char_ptr_to_str(servname); + debug!("sys_getaddrinfo <= {:?} {:?}", name, port); + syscall_body!(sys_getaddrinfo, { + if nodename.is_null() && servname.is_null() { + return Ok(0); + } + if res.is_null() { + return Err(LinuxError::EFAULT); + } + + let port = port.map_or(0, |p| p.parse::().unwrap_or(0)); + let ip_addrs = if let Ok(domain) = name { + if let Ok(a) = domain.parse::() { + vec![a] + } else { + axnet::dns_query(domain)? + } + } else { + vec![Ipv4Addr::LOCALHOST.into()] + }; + + let len = ip_addrs.len().min(ctypes::MAXADDRS as usize); + if len == 0 { + return Ok(0); + } + + let mut out: Vec = Vec::with_capacity(len); + for (i, &ip) in ip_addrs.iter().enumerate().take(len) { + let buf = match ip { + IpAddr::V4(ip) => ctypes::aibuf { + ai: ctypes::addrinfo { + ai_family: ctypes::AF_INET as _, + // TODO: This is a hard-code part, only return TCP parameters + ai_socktype: ctypes::SOCK_STREAM as _, + ai_protocol: ctypes::IPPROTO_TCP as _, + ai_addrlen: size_of::() as _, + ai_addr: core::ptr::null_mut(), + ai_canonname: core::ptr::null_mut(), + ai_next: core::ptr::null_mut(), + ai_flags: 0, + }, + sa: ctypes::aibuf_sa { + sin: SocketAddrV4::new(ip, port).into(), + }, + slot: i as i16, + lock: [0], + ref_: 0, + }, + _ => panic!("IPv6 is not supported"), + }; + out.push(buf); + out[i].ai.ai_addr = + unsafe { core::ptr::addr_of_mut!(out[i].sa.sin) as *mut ctypes::sockaddr }; + if i > 0 { + out[i - 1].ai.ai_next = core::ptr::addr_of_mut!(out[i].ai); + } + } + + out[0].ref_ = len as i16; + unsafe { *res = core::ptr::addr_of_mut!(out[0].ai) }; + core::mem::forget(out); // drop in `sys_freeaddrinfo` + Ok(len) + }) +} + +/// Free queried `addrinfo` struct +pub unsafe fn sys_freeaddrinfo(res: *mut ctypes::addrinfo) { + if res.is_null() { + return; + } + let aibuf_ptr = res as *mut ctypes::aibuf; + let len = (*aibuf_ptr).ref_ as usize; + assert!((*aibuf_ptr).slot == 0); + assert!(len > 0); + let vec = Vec::from_raw_parts(aibuf_ptr, len, len); // TODO: lock + drop(vec); +} + +/// Get current address to which the socket sockfd is bound. +pub unsafe fn sys_getsockname( + sock_fd: c_int, + addr: *mut ctypes::sockaddr, + addrlen: *mut ctypes::socklen_t, +) -> c_int { + debug!( + "sys_getsockname <= {} {:#x} {:#x}", + sock_fd, addr as usize, addrlen as usize + ); + syscall_body!(sys_getsockname, { + if addr.is_null() || addrlen.is_null() { + return Err(LinuxError::EFAULT); + } + if unsafe { *addrlen } < size_of::() as u32 { + return Err(LinuxError::EINVAL); + } + unsafe { + (*addr, *addrlen) = into_sockaddr(Socket::from_fd(sock_fd)?.local_addr()?); + } + Ok(0) + }) +} + +/// Get peer address to which the socket sockfd is connected. +pub unsafe fn sys_getpeername( + sock_fd: c_int, + addr: *mut ctypes::sockaddr, + addrlen: *mut ctypes::socklen_t, +) -> c_int { + debug!( + "sys_getpeername <= {} {:#x} {:#x}", + sock_fd, addr as usize, addrlen as usize + ); + syscall_body!(sys_getpeername, { + if addr.is_null() || addrlen.is_null() { + return Err(LinuxError::EFAULT); + } + if unsafe { *addrlen } < size_of::() as u32 { + return Err(LinuxError::EINVAL); + } + unsafe { + (*addr, *addrlen) = into_sockaddr(Socket::from_fd(sock_fd)?.peer_addr()?); + } + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/imp/pipe.rs b/api/arceos_posix_api/src/imp/pipe.rs new file mode 100644 index 000000000..17a9b248b --- /dev/null +++ b/api/arceos_posix_api/src/imp/pipe.rs @@ -0,0 +1,214 @@ +use alloc::sync::Arc; +use core::ffi::c_int; + +use axerrno::{LinuxError, LinuxResult}; +use axio::PollState; +use axsync::Mutex; + +use super::fd_ops::{add_file_like, close_file_like, FileLike}; +use crate::ctypes; + +#[derive(Copy, Clone, PartialEq)] +enum RingBufferStatus { + Full, + Empty, + Normal, +} + +const RING_BUFFER_SIZE: usize = 256; + +pub struct PipeRingBuffer { + arr: [u8; RING_BUFFER_SIZE], + head: usize, + tail: usize, + status: RingBufferStatus, +} + +impl PipeRingBuffer { + pub const fn new() -> Self { + Self { + arr: [0; RING_BUFFER_SIZE], + head: 0, + tail: 0, + status: RingBufferStatus::Empty, + } + } + + pub fn write_byte(&mut self, byte: u8) { + self.status = RingBufferStatus::Normal; + self.arr[self.tail] = byte; + self.tail = (self.tail + 1) % RING_BUFFER_SIZE; + if self.tail == self.head { + self.status = RingBufferStatus::Full; + } + } + + pub fn read_byte(&mut self) -> u8 { + self.status = RingBufferStatus::Normal; + let c = self.arr[self.head]; + self.head = (self.head + 1) % RING_BUFFER_SIZE; + if self.head == self.tail { + self.status = RingBufferStatus::Empty; + } + c + } + + /// Get the length of remaining data in the buffer + pub const fn available_read(&self) -> usize { + if matches!(self.status, RingBufferStatus::Empty) { + 0 + } else if self.tail > self.head { + self.tail - self.head + } else { + self.tail + RING_BUFFER_SIZE - self.head + } + } + + /// Get the length of remaining space in the buffer + pub const fn available_write(&self) -> usize { + if matches!(self.status, RingBufferStatus::Full) { + 0 + } else { + RING_BUFFER_SIZE - self.available_read() + } + } +} + +pub struct Pipe { + readable: bool, + buffer: Arc>, +} + +impl Pipe { + pub fn new() -> (Pipe, Pipe) { + let buffer = Arc::new(Mutex::new(PipeRingBuffer::new())); + let read_end = Pipe { + readable: true, + buffer: buffer.clone(), + }; + let write_end = Pipe { + readable: false, + buffer, + }; + (read_end, write_end) + } + + pub const fn readable(&self) -> bool { + self.readable + } + + pub const fn writable(&self) -> bool { + !self.readable + } + + pub fn write_end_close(&self) -> bool { + Arc::strong_count(&self.buffer) == 1 + } +} + +impl FileLike for Pipe { + fn read(&self, buf: &mut [u8]) -> LinuxResult { + if !self.readable() { + return Err(LinuxError::EPERM); + } + let mut read_size = 0usize; + let max_len = buf.len(); + loop { + let mut ring_buffer = self.buffer.lock(); + let loop_read = ring_buffer.available_read(); + if loop_read == 0 { + if self.write_end_close() { + return Ok(read_size); + } + drop(ring_buffer); + // Data not ready, wait for write end + crate::sys_sched_yield(); // TODO: use synconize primitive + continue; + } + for _ in 0..loop_read { + if read_size == max_len { + return Ok(read_size); + } + buf[read_size] = ring_buffer.read_byte(); + read_size += 1; + } + } + } + + fn write(&self, buf: &[u8]) -> LinuxResult { + if !self.writable() { + return Err(LinuxError::EPERM); + } + let mut write_size = 0usize; + let max_len = buf.len(); + loop { + let mut ring_buffer = self.buffer.lock(); + let loop_write = ring_buffer.available_write(); + if loop_write == 0 { + drop(ring_buffer); + // Buffer is full, wait for read end to consume + crate::sys_sched_yield(); // TODO: use synconize primitive + continue; + } + for _ in 0..loop_write { + if write_size == max_len { + return Ok(write_size); + } + ring_buffer.write_byte(buf[write_size]); + write_size += 1; + } + } + } + + fn stat(&self) -> LinuxResult { + let st_mode = 0o10000 | 0o600u32; // S_IFIFO | rw------- + Ok(ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + st_uid: 1000, + st_gid: 1000, + st_blksize: 4096, + ..Default::default() + }) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn poll(&self) -> LinuxResult { + let buf = self.buffer.lock(); + Ok(PollState { + readable: self.readable() && buf.available_read() > 0, + writable: self.writable() && buf.available_write() > 0, + }) + } + + fn set_nonblocking(&self, _nonblocking: bool) -> LinuxResult { + Ok(()) + } +} + +/// Create a pipe +/// +/// Return 0 if succeed +pub fn sys_pipe(fds: &mut [c_int]) -> c_int { + debug!("sys_pipe <= {:#x}", fds.as_ptr() as usize); + syscall_body!(sys_pipe, { + if fds.len() != 2 { + return Err(LinuxError::EFAULT); + } + + let (read_end, write_end) = Pipe::new(); + let read_fd = add_file_like(Arc::new(read_end))?; + let write_fd = add_file_like(Arc::new(write_end)).inspect_err(|_| { + close_file_like(read_fd).ok(); + })?; + + fds[0] = read_fd as c_int; + fds[1] = write_fd as c_int; + + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/imp/pthread/mod.rs b/api/arceos_posix_api/src/imp/pthread/mod.rs new file mode 100644 index 000000000..bcf12fff8 --- /dev/null +++ b/api/arceos_posix_api/src/imp/pthread/mod.rs @@ -0,0 +1,154 @@ +use alloc::{boxed::Box, collections::BTreeMap, sync::Arc}; +use core::cell::UnsafeCell; +use core::ffi::{c_int, c_void}; + +use axerrno::{LinuxError, LinuxResult}; +use axtask::AxTaskRef; +use spin::RwLock; + +use crate::ctypes; + +pub mod mutex; + +lazy_static::lazy_static! { + static ref TID_TO_PTHREAD: RwLock>> = { + let mut map = BTreeMap::new(); + let main_task = axtask::current(); + let main_tid = main_task.id().as_u64(); + let main_thread = Pthread { + inner: main_task.as_task_ref().clone(), + retval: Arc::new(Packet { + result: UnsafeCell::new(core::ptr::null_mut()), + }), + }; + let ptr = Box::into_raw(Box::new(main_thread)) as *mut c_void; + map.insert(main_tid, ForceSendSync(ptr)); + RwLock::new(map) + }; +} + +struct Packet { + result: UnsafeCell, +} + +unsafe impl Send for Packet {} +unsafe impl Sync for Packet {} + +pub struct Pthread { + inner: AxTaskRef, + retval: Arc>, +} + +impl Pthread { + fn create( + _attr: *const ctypes::pthread_attr_t, + start_routine: extern "C" fn(arg: *mut c_void) -> *mut c_void, + arg: *mut c_void, + ) -> LinuxResult { + let arg_wrapper = ForceSendSync(arg); + + let my_packet: Arc> = Arc::new(Packet { + result: UnsafeCell::new(core::ptr::null_mut()), + }); + let their_packet = my_packet.clone(); + + let main = move || { + let arg = arg_wrapper; + let ret = start_routine(arg.0); + unsafe { *their_packet.result.get() = ret }; + drop(their_packet); + }; + + let task_inner = axtask::spawn(main); + let tid = task_inner.id().as_u64(); + let thread = Pthread { + inner: task_inner, + retval: my_packet, + }; + let ptr = Box::into_raw(Box::new(thread)) as *mut c_void; + TID_TO_PTHREAD.write().insert(tid, ForceSendSync(ptr)); + Ok(ptr) + } + + fn current_ptr() -> *mut Pthread { + let tid = axtask::current().id().as_u64(); + match TID_TO_PTHREAD.read().get(&tid) { + None => core::ptr::null_mut(), + Some(ptr) => ptr.0 as *mut Pthread, + } + } + + fn current() -> Option<&'static Pthread> { + unsafe { core::ptr::NonNull::new(Self::current_ptr()).map(|ptr| ptr.as_ref()) } + } + + fn exit_current(retval: *mut c_void) -> ! { + let thread = Self::current().expect("fail to get current thread"); + unsafe { *thread.retval.result.get() = retval }; + axtask::exit(0); + } + + fn join(ptr: ctypes::pthread_t) -> LinuxResult<*mut c_void> { + if core::ptr::eq(ptr, Self::current_ptr() as _) { + return Err(LinuxError::EDEADLK); + } + + let thread = unsafe { Box::from_raw(ptr as *mut Pthread) }; + thread.inner.join(); + let tid = thread.inner.id().as_u64(); + let retval = unsafe { *thread.retval.result.get() }; + TID_TO_PTHREAD.write().remove(&tid); + drop(thread); + Ok(retval) + } +} + +/// Returns the `pthread` struct of current thread. +pub fn sys_pthread_self() -> ctypes::pthread_t { + Pthread::current().expect("fail to get current thread") as *const Pthread as _ +} + +/// Create a new thread with the given entry point and argument. +/// +/// If successful, it stores the pointer to the newly created `struct __pthread` +/// in `res` and returns 0. +pub unsafe fn sys_pthread_create( + res: *mut ctypes::pthread_t, + attr: *const ctypes::pthread_attr_t, + start_routine: extern "C" fn(arg: *mut c_void) -> *mut c_void, + arg: *mut c_void, +) -> c_int { + debug!( + "sys_pthread_create <= {:#x}, {:#x}", + start_routine as usize, arg as usize + ); + syscall_body!(sys_pthread_create, { + let ptr = Pthread::create(attr, start_routine, arg)?; + unsafe { core::ptr::write(res, ptr) }; + Ok(0) + }) +} + +/// Exits the current thread. The value `retval` will be returned to the joiner. +pub fn sys_pthread_exit(retval: *mut c_void) -> ! { + debug!("sys_pthread_exit <= {:#x}", retval as usize); + Pthread::exit_current(retval); +} + +/// Waits for the given thread to exit, and stores the return value in `retval`. +pub unsafe fn sys_pthread_join(thread: ctypes::pthread_t, retval: *mut *mut c_void) -> c_int { + debug!("sys_pthread_join <= {:#x}", retval as usize); + syscall_body!(sys_pthread_join, { + let ret = Pthread::join(thread)?; + if !retval.is_null() { + unsafe { core::ptr::write(retval, ret) }; + } + Ok(0) + }) +} + +#[derive(Clone, Copy)] +struct ForceSendSync(T); + +unsafe impl Send for ForceSendSync {} +unsafe impl Sync for ForceSendSync {} diff --git a/api/arceos_posix_api/src/imp/pthread/mutex.rs b/api/arceos_posix_api/src/imp/pthread/mutex.rs new file mode 100644 index 000000000..f22a8f55d --- /dev/null +++ b/api/arceos_posix_api/src/imp/pthread/mutex.rs @@ -0,0 +1,70 @@ +use crate::{ctypes, utils::check_null_mut_ptr}; + +use axerrno::LinuxResult; +use axsync::Mutex; + +use core::ffi::c_int; +use core::mem::{size_of, ManuallyDrop}; + +static_assertions::const_assert_eq!( + size_of::(), + size_of::() +); + +#[repr(C)] +pub struct PthreadMutex(Mutex<()>); + +impl PthreadMutex { + const fn new() -> Self { + Self(Mutex::new(())) + } + + fn lock(&self) -> LinuxResult { + let _guard = ManuallyDrop::new(self.0.lock()); + Ok(()) + } + + fn unlock(&self) -> LinuxResult { + unsafe { self.0.force_unlock() }; + Ok(()) + } +} + +/// Initialize a mutex. +pub fn sys_pthread_mutex_init( + mutex: *mut ctypes::pthread_mutex_t, + _attr: *const ctypes::pthread_mutexattr_t, +) -> c_int { + debug!("sys_pthread_mutex_init <= {:#x}", mutex as usize); + syscall_body!(sys_pthread_mutex_init, { + check_null_mut_ptr(mutex)?; + unsafe { + mutex.cast::().write(PthreadMutex::new()); + } + Ok(0) + }) +} + +/// Lock the given mutex. +pub fn sys_pthread_mutex_lock(mutex: *mut ctypes::pthread_mutex_t) -> c_int { + debug!("sys_pthread_mutex_lock <= {:#x}", mutex as usize); + syscall_body!(sys_pthread_mutex_lock, { + check_null_mut_ptr(mutex)?; + unsafe { + (*mutex.cast::()).lock()?; + } + Ok(0) + }) +} + +/// Unlock the given mutex. +pub fn sys_pthread_mutex_unlock(mutex: *mut ctypes::pthread_mutex_t) -> c_int { + debug!("sys_pthread_mutex_unlock <= {:#x}", mutex as usize); + syscall_body!(sys_pthread_mutex_unlock, { + check_null_mut_ptr(mutex)?; + unsafe { + (*mutex.cast::()).unlock()?; + } + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/imp/resources.rs b/api/arceos_posix_api/src/imp/resources.rs new file mode 100644 index 000000000..16956ae83 --- /dev/null +++ b/api/arceos_posix_api/src/imp/resources.rs @@ -0,0 +1,51 @@ +use crate::ctypes; +use axerrno::LinuxError; +use core::ffi::c_int; + +/// Get resource limitations +/// +/// TODO: support more resource types +pub unsafe fn sys_getrlimit(resource: c_int, rlimits: *mut ctypes::rlimit) -> c_int { + debug!("sys_getrlimit <= {} {:#x}", resource, rlimits as usize); + syscall_body!(sys_getrlimit, { + match resource as u32 { + ctypes::RLIMIT_DATA => {} + ctypes::RLIMIT_STACK => {} + ctypes::RLIMIT_NOFILE => {} + _ => return Err(LinuxError::EINVAL), + } + if rlimits.is_null() { + return Ok(0); + } + match resource as u32 { + ctypes::RLIMIT_STACK => unsafe { + (*rlimits).rlim_cur = axconfig::TASK_STACK_SIZE as _; + (*rlimits).rlim_max = axconfig::TASK_STACK_SIZE as _; + }, + #[cfg(feature = "fd")] + ctypes::RLIMIT_NOFILE => unsafe { + (*rlimits).rlim_cur = super::fd_ops::AX_FILE_LIMIT as _; + (*rlimits).rlim_max = super::fd_ops::AX_FILE_LIMIT as _; + }, + _ => {} + } + Ok(0) + }) +} + +/// Set resource limitations +/// +/// TODO: support more resource types +pub unsafe fn sys_setrlimit(resource: c_int, rlimits: *mut crate::ctypes::rlimit) -> c_int { + debug!("sys_setrlimit <= {} {:#x}", resource, rlimits as usize); + syscall_body!(sys_setrlimit, { + match resource as u32 { + crate::ctypes::RLIMIT_DATA => {} + crate::ctypes::RLIMIT_STACK => {} + crate::ctypes::RLIMIT_NOFILE => {} + _ => return Err(LinuxError::EINVAL), + } + // Currently do not support set resources + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/imp/stdio.rs b/api/arceos_posix_api/src/imp/stdio.rs new file mode 100644 index 000000000..ded9c3333 --- /dev/null +++ b/api/arceos_posix_api/src/imp/stdio.rs @@ -0,0 +1,170 @@ +use axerrno::AxResult; +use axio::{prelude::*, BufReader}; +use axsync::Mutex; + +#[cfg(feature = "fd")] +use {alloc::sync::Arc, axerrno::LinuxError, axerrno::LinuxResult, axio::PollState}; + +fn console_read_bytes() -> Option { + axhal::console::getchar().map(|c| if c == b'\r' { b'\n' } else { c }) +} + +fn console_write_bytes(buf: &[u8]) -> AxResult { + axhal::console::write_bytes(buf); + Ok(buf.len()) +} + +struct StdinRaw; +struct StdoutRaw; + +impl Read for StdinRaw { + // Non-blocking read, returns number of bytes read. + fn read(&mut self, buf: &mut [u8]) -> AxResult { + let mut read_len = 0; + while read_len < buf.len() { + if let Some(c) = console_read_bytes() { + buf[read_len] = c; + read_len += 1; + } else { + break; + } + } + Ok(read_len) + } +} + +impl Write for StdoutRaw { + fn write(&mut self, buf: &[u8]) -> AxResult { + console_write_bytes(buf) + } + + fn flush(&mut self) -> AxResult { + Ok(()) + } +} + +pub struct Stdin { + inner: &'static Mutex>, +} + +impl Stdin { + // Block until at least one byte is read. + fn read_blocked(&self, buf: &mut [u8]) -> AxResult { + let read_len = self.inner.lock().read(buf)?; + if buf.is_empty() || read_len > 0 { + return Ok(read_len); + } + // try again until we get something + loop { + let read_len = self.inner.lock().read(buf)?; + if read_len > 0 { + return Ok(read_len); + } + crate::sys_sched_yield(); + } + } +} + +impl Read for Stdin { + fn read(&mut self, buf: &mut [u8]) -> AxResult { + self.read_blocked(buf) + } +} + +pub struct Stdout { + inner: &'static Mutex, +} + +impl Write for Stdout { + fn write(&mut self, buf: &[u8]) -> AxResult { + self.inner.lock().write(buf) + } + + fn flush(&mut self) -> AxResult { + self.inner.lock().flush() + } +} + +/// Constructs a new handle to the standard input of the current process. +pub fn stdin() -> Stdin { + static INSTANCE: Mutex> = Mutex::new(BufReader::new(StdinRaw)); + Stdin { inner: &INSTANCE } +} + +/// Constructs a new handle to the standard output of the current process. +pub fn stdout() -> Stdout { + static INSTANCE: Mutex = Mutex::new(StdoutRaw); + Stdout { inner: &INSTANCE } +} + +#[cfg(feature = "fd")] +impl super::fd_ops::FileLike for Stdin { + fn read(&self, buf: &mut [u8]) -> LinuxResult { + Ok(self.read_blocked(buf)?) + } + + fn write(&self, _buf: &[u8]) -> LinuxResult { + Err(LinuxError::EPERM) + } + + fn stat(&self) -> LinuxResult { + let st_mode = 0o20000 | 0o440u32; // S_IFCHR | r--r----- + Ok(crate::ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + ..Default::default() + }) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn poll(&self) -> LinuxResult { + Ok(PollState { + readable: true, + writable: true, + }) + } + + fn set_nonblocking(&self, _nonblocking: bool) -> LinuxResult { + Ok(()) + } +} + +#[cfg(feature = "fd")] +impl super::fd_ops::FileLike for Stdout { + fn read(&self, _buf: &mut [u8]) -> LinuxResult { + Err(LinuxError::EPERM) + } + + fn write(&self, buf: &[u8]) -> LinuxResult { + Ok(self.inner.lock().write(buf)?) + } + + fn stat(&self) -> LinuxResult { + let st_mode = 0o20000 | 0o220u32; // S_IFCHR | -w--w---- + Ok(crate::ctypes::stat { + st_ino: 1, + st_nlink: 1, + st_mode, + ..Default::default() + }) + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn poll(&self) -> LinuxResult { + Ok(PollState { + readable: true, + writable: true, + }) + } + + fn set_nonblocking(&self, _nonblocking: bool) -> LinuxResult { + Ok(()) + } +} diff --git a/api/arceos_posix_api/src/imp/sys.rs b/api/arceos_posix_api/src/imp/sys.rs new file mode 100644 index 000000000..0e7a43292 --- /dev/null +++ b/api/arceos_posix_api/src/imp/sys.rs @@ -0,0 +1,29 @@ +use core::ffi::{c_int, c_long}; + +use crate::ctypes; + +const PAGE_SIZE_4K: usize = 4096; + +/// Return system configuration infomation +/// +/// Notice: currently only support what unikraft covers +pub fn sys_sysconf(name: c_int) -> c_long { + debug!("sys_sysconf <= {}", name); + syscall_body!(sys_sysconf, { + match name as u32 { + // Page size + ctypes::_SC_PAGE_SIZE => Ok(PAGE_SIZE_4K), + // Total physical pages + ctypes::_SC_PHYS_PAGES => Ok(axconfig::PHYS_MEMORY_SIZE / PAGE_SIZE_4K), + // Number of processors in use + ctypes::_SC_NPROCESSORS_ONLN => Ok(axconfig::SMP), + // Avaliable physical pages + #[cfg(feature = "alloc")] + ctypes::_SC_AVPHYS_PAGES => Ok(axalloc::global_allocator().available_pages()), + // Maximum number of files per process + #[cfg(feature = "fd")] + ctypes::_SC_OPEN_MAX => Ok(super::fd_ops::AX_FILE_LIMIT), + _ => Ok(0), + } + }) +} diff --git a/api/arceos_posix_api/src/imp/task.rs b/api/arceos_posix_api/src/imp/task.rs new file mode 100644 index 000000000..5ba05feef --- /dev/null +++ b/api/arceos_posix_api/src/imp/task.rs @@ -0,0 +1,40 @@ +use core::ffi::c_int; + +/// Relinquish the CPU, and switches to another task. +/// +/// For single-threaded configuration (`multitask` feature is disabled), we just +/// relax the CPU and wait for incoming interrupts. +pub fn sys_sched_yield() -> c_int { + #[cfg(feature = "multitask")] + axtask::yield_now(); + #[cfg(not(feature = "multitask"))] + if cfg!(feature = "irq") { + axhal::arch::wait_for_irqs(); + } else { + core::hint::spin_loop(); + } + 0 +} + +/// Get current thread ID. +pub fn sys_getpid() -> c_int { + syscall_body!(sys_getpid, + #[cfg(feature = "multitask")] + { + Ok(axtask::current().id().as_u64() as c_int) + } + #[cfg(not(feature = "multitask"))] + { + Ok(2) // `main` task ID + } + ) +} + +/// Exit current task +pub fn sys_exit(exit_code: c_int) -> ! { + debug!("sys_exit <= {}", exit_code); + #[cfg(feature = "multitask")] + axtask::exit(exit_code); + #[cfg(not(feature = "multitask"))] + axhal::misc::terminate(); +} diff --git a/api/arceos_posix_api/src/imp/time.rs b/api/arceos_posix_api/src/imp/time.rs new file mode 100644 index 000000000..56fa3a1fe --- /dev/null +++ b/api/arceos_posix_api/src/imp/time.rs @@ -0,0 +1,84 @@ +use axerrno::LinuxError; +use core::ffi::{c_int, c_long}; +use core::time::Duration; + +use crate::ctypes; + +impl From for Duration { + fn from(ts: ctypes::timespec) -> Self { + Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32) + } +} + +impl From for Duration { + fn from(tv: ctypes::timeval) -> Self { + Duration::new(tv.tv_sec as u64, tv.tv_usec as u32 * 1000) + } +} + +impl From for ctypes::timespec { + fn from(d: Duration) -> Self { + ctypes::timespec { + tv_sec: d.as_secs() as c_long, + tv_nsec: d.subsec_nanos() as c_long, + } + } +} + +impl From for ctypes::timeval { + fn from(d: Duration) -> Self { + ctypes::timeval { + tv_sec: d.as_secs() as c_long, + tv_usec: d.subsec_micros() as c_long, + } + } +} + +/// Get clock time since booting +pub unsafe fn sys_clock_gettime(_clk: ctypes::clockid_t, ts: *mut ctypes::timespec) -> c_int { + syscall_body!(sys_clock_gettime, { + if ts.is_null() { + return Err(LinuxError::EFAULT); + } + let now = axhal::time::current_time().into(); + unsafe { *ts = now }; + debug!("sys_clock_gettime: {}.{:09}s", now.tv_sec, now.tv_nsec); + Ok(0) + }) +} + +/// Sleep some nanoseconds +/// +/// TODO: should be woken by signals, and set errno +pub unsafe fn sys_nanosleep(req: *const ctypes::timespec, rem: *mut ctypes::timespec) -> c_int { + syscall_body!(sys_nanosleep, { + unsafe { + if req.is_null() || (*req).tv_nsec < 0 || (*req).tv_nsec > 999999999 { + return Err(LinuxError::EINVAL); + } + } + + let dur = unsafe { + debug!("sys_nanosleep <= {}.{:09}s", (*req).tv_sec, (*req).tv_nsec); + Duration::from(*req) + }; + + let now = axhal::time::current_time(); + + #[cfg(feature = "multitask")] + axtask::sleep(dur); + #[cfg(not(feature = "multitask"))] + axhal::time::busy_wait(dur); + + let after = axhal::time::current_time(); + let actual = after - now; + + if let Some(diff) = dur.checked_sub(actual) { + if !rem.is_null() { + unsafe { (*rem) = diff.into() }; + } + return Err(LinuxError::EINTR); + } + Ok(0) + }) +} diff --git a/api/arceos_posix_api/src/lib.rs b/api/arceos_posix_api/src/lib.rs new file mode 100644 index 000000000..1e0e126e9 --- /dev/null +++ b/api/arceos_posix_api/src/lib.rs @@ -0,0 +1,62 @@ +//! POSIX-compatible APIs for [ArceOS] modules +//! +//! [ArceOS]: https://github.com/rcore-os/arceos + +#![cfg_attr(all(not(test), not(doc)), no_std)] +#![feature(ip_in_core)] +#![feature(result_option_inspect)] +#![feature(doc_cfg)] +#![feature(doc_auto_cfg)] +#![allow(clippy::missing_safety_doc)] + +#[macro_use] +extern crate axlog; +extern crate axruntime; + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[macro_use] +mod utils; + +mod imp; + +/// Platform-specific constants and parameters. +pub mod config { + pub use axconfig::*; +} + +/// POSIX C types. +#[rustfmt::skip] +#[path = "./ctypes_gen.rs"] +#[allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals, clippy::upper_case_acronyms, missing_docs)] +pub mod ctypes; + +pub use imp::io::{sys_read, sys_write, sys_writev}; +pub use imp::resources::{sys_getrlimit, sys_setrlimit}; +pub use imp::sys::sys_sysconf; +pub use imp::task::{sys_exit, sys_getpid, sys_sched_yield}; +pub use imp::time::{sys_clock_gettime, sys_nanosleep}; + +#[cfg(feature = "fd")] +pub use imp::fd_ops::{sys_close, sys_dup, sys_dup2, sys_fcntl}; +#[cfg(feature = "fs")] +pub use imp::fs::{sys_fstat, sys_getcwd, sys_lseek, sys_lstat, sys_open, sys_rename, sys_stat}; +#[cfg(feature = "select")] +pub use imp::io_mpx::sys_select; +#[cfg(feature = "epoll")] +pub use imp::io_mpx::{sys_epoll_create, sys_epoll_ctl, sys_epoll_wait}; +#[cfg(feature = "net")] +pub use imp::net::{ + sys_accept, sys_bind, sys_connect, sys_freeaddrinfo, sys_getaddrinfo, sys_getpeername, + sys_getsockname, sys_listen, sys_recv, sys_recvfrom, sys_send, sys_sendto, sys_shutdown, + sys_socket, +}; +#[cfg(feature = "pipe")] +pub use imp::pipe::sys_pipe; +#[cfg(feature = "multitask")] +pub use imp::pthread::mutex::{ + sys_pthread_mutex_init, sys_pthread_mutex_lock, sys_pthread_mutex_unlock, +}; +#[cfg(feature = "multitask")] +pub use imp::pthread::{sys_pthread_create, sys_pthread_exit, sys_pthread_join, sys_pthread_self}; diff --git a/api/arceos_posix_api/src/utils.rs b/api/arceos_posix_api/src/utils.rs new file mode 100644 index 000000000..664bf5231 --- /dev/null +++ b/api/arceos_posix_api/src/utils.rs @@ -0,0 +1,61 @@ +#![allow(dead_code)] +#![allow(unused_macros)] + +use axerrno::{LinuxError, LinuxResult}; +use core::ffi::{c_char, CStr}; + +pub fn char_ptr_to_str<'a>(str: *const c_char) -> LinuxResult<&'a str> { + if str.is_null() { + Err(LinuxError::EFAULT) + } else { + unsafe { CStr::from_ptr(str) } + .to_str() + .map_err(|_| LinuxError::EINVAL) + } +} + +pub fn check_null_ptr(ptr: *const T) -> LinuxResult { + if ptr.is_null() { + Err(LinuxError::EFAULT) + } else { + Ok(()) + } +} + +pub fn check_null_mut_ptr(ptr: *mut T) -> LinuxResult { + if ptr.is_null() { + Err(LinuxError::EFAULT) + } else { + Ok(()) + } +} + +macro_rules! syscall_body { + ($fn: ident, $($stmt: tt)*) => {{ + #[allow(clippy::redundant_closure_call)] + let res = (|| -> axerrno::LinuxResult<_> { $($stmt)* })(); + match res { + Ok(_) | Err(axerrno::LinuxError::EAGAIN) => debug!(concat!(stringify!($fn), " => {:?}"), res), + Err(_) => info!(concat!(stringify!($fn), " => {:?}"), res), + } + match res { + Ok(v) => v as _, + Err(e) => { + -e.code() as _ + } + } + }}; +} + +macro_rules! syscall_body_no_debug { + ($($stmt: tt)*) => {{ + #[allow(clippy::redundant_closure_call)] + let res = (|| -> axerrno::LinuxResult<_> { $($stmt)* })(); + match res { + Ok(v) => v as _, + Err(e) => { + -e.code() as _ + } + } + }}; +} diff --git a/api/axfeat/Cargo.toml b/api/axfeat/Cargo.toml new file mode 100644 index 000000000..2947a7ea1 --- /dev/null +++ b/api/axfeat/Cargo.toml @@ -0,0 +1,74 @@ +[package] +name = "axfeat" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Top-level feature selection for ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/api/axfeat" +documentation = "https://rcore-os.github.io/arceos/axfeat/index.html" + +[features] +default = [] + +# Multicore +smp = ["axhal/smp", "axruntime/smp", "spinlock/smp"] + +# Floating point/SIMD +fp_simd = ["axhal/fp_simd"] + +# Interrupts +irq = ["axhal/irq", "axruntime/irq", "axtask?/irq"] + +# Memory +alloc = ["axalloc", "axruntime/alloc"] +alloc-tlsf = ["axalloc/tlsf"] +alloc-slab = ["axalloc/slab"] +alloc-buddy = ["axalloc/buddy"] +paging = ["alloc", "axhal/paging", "axruntime/paging"] +tls = ["alloc", "axhal/tls", "axruntime/tls", "axtask?/tls"] + +# Multi-threading and scheduler +multitask = ["alloc", "axtask/multitask", "axsync/multitask", "axruntime/multitask"] +sched_fifo = ["axtask/sched_fifo"] +sched_rr = ["axtask/sched_rr", "irq"] +sched_cfs = ["axtask/sched_cfs", "irq"] + +# File system +fs = ["alloc", "paging", "axdriver/virtio-blk", "dep:axfs", "axruntime/fs"] # TODO: try to remove "paging" +myfs = ["axfs?/myfs"] + +# Networking +net = ["alloc", "paging", "axdriver/virtio-net", "dep:axnet", "axruntime/net"] + +# Display +display = ["alloc", "paging", "axdriver/virtio-gpu", "dep:axdisplay", "axruntime/display"] + +# Device drivers +bus-mmio = ["axdriver?/bus-mmio"] +bus-pci = ["axdriver?/bus-pci"] +driver-ramdisk = ["axdriver?/ramdisk", "axfs?/use-ramdisk"] +driver-ixgbe = ["axdriver?/ixgbe"] +driver-bcm2835-sdhci = ["axdriver?/bcm2835-sdhci"] + +# Logging +log-level-off = ["axlog/log-level-off"] +log-level-error = ["axlog/log-level-error"] +log-level-warn = ["axlog/log-level-warn"] +log-level-info = ["axlog/log-level-info"] +log-level-debug = ["axlog/log-level-debug"] +log-level-trace = ["axlog/log-level-trace"] + +[dependencies] +axruntime = { path = "../../modules/axruntime" } +axhal = { path = "../../modules/axhal" } +axlog = { path = "../../modules/axlog" } +axalloc = { path = "../../modules/axalloc", optional = true } +axdriver = { path = "../../modules/axdriver", optional = true } +axfs = { path = "../../modules/axfs", optional = true } +axnet = { path = "../../modules/axnet", optional = true } +axdisplay = { path = "../../modules/axdisplay", optional = true } +axsync = { path = "../../modules/axsync", optional = true } +axtask = { path = "../../modules/axtask", optional = true } +spinlock = { path = "../../crates/spinlock", optional = true } diff --git a/api/axfeat/src/lib.rs b/api/axfeat/src/lib.rs new file mode 100644 index 000000000..619285272 --- /dev/null +++ b/api/axfeat/src/lib.rs @@ -0,0 +1,40 @@ +//! Top-level feature selection for [ArceOS]. +//! +//! # Cargo Features +//! +//! - CPU +//! - `smp`: Enable SMP (symmetric multiprocessing) support. +//! - `fp_simd`: Enable floating point and SIMD support. +//! - Interrupts: +//! - `irq`: Enable interrupt handling support. +//! - Memory +//! - `alloc`: Enable dynamic memory allocation. +//! - `alloc-tlsf`: Use the TLSF allocator. +//! - `alloc-slab`: Use the slab allocator. +//! - `alloc-buddy`: Use the buddy system allocator. +//! - `paging`: Enable page table manipulation. +//! - `tls`: Enable thread-local storage. +//! - Task management +//! - `multitask`: Enable multi-threading support. +//! - `sched_fifo`: Use the FIFO cooperative scheduler. +//! - `sched_rr`: Use the Round-robin preemptive scheduler. +//! - `sched_cfs`: Use the Completely Fair Scheduler (CFS) preemptive scheduler. +//! - Upperlayer stacks (fs, net, display) +//! - `fs`: Enable file system support. +//! - `myfs`: Allow users to define their custom filesystems to override the default. +//! - `net`: Enable networking support. +//! - `display`: Enable graphics support. +//! - Device drivers +//! - `bus-mmio`: Use device tree to probe all MMIO devices. +//! - `bus-pci`: Use PCI bus to probe all PCI devices. +//! - `driver-ramdisk`: Use the RAM disk to emulate the block device. +//! - `driver-ixgbe`: Enable the Intel 82599 10Gbit NIC driver. +//! - `driver-bcm2835-sdhci`: Enable the BCM2835 SDHCI driver (Raspberry Pi SD card). +//! - Logging +//! - `log-level-off`: Disable all logging. +//! - `log-level-error`, `log-level-warn`, `log-level-info`, `log-level-debug`, +//! `log-level-trace`: Keep logging only at the specified level or higher. +//! +//! [ArceOS]: https://github.com/rcore-os/arceos + +#![no_std] diff --git a/apps/.gitignore b/apps/.gitignore new file mode 100644 index 000000000..7cf238fbc --- /dev/null +++ b/apps/.gitignore @@ -0,0 +1,3 @@ +*.o +*.elf +*.bin diff --git a/apps/c/helloworld/expect_info.out b/apps/c/helloworld/expect_info.out new file mode 100644 index 000000000..61423d90e --- /dev/null +++ b/apps/c/helloworld/expect_info.out @@ -0,0 +1,17 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize platform devices... +Primary CPU 0 init OK. +Hello, C app! +Shutting down... diff --git a/apps/c/helloworld/expect_info_smp4.out b/apps/c/helloworld/expect_info_smp4.out new file mode 100644 index 000000000..fcf956349 --- /dev/null +++ b/apps/c/helloworld/expect_info_smp4.out @@ -0,0 +1,23 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize platform devices... +CPU 0 init OK +CPU 1 started +CPU 2 started +CPU 3 started +CPU 1 init OK +CPU 2 init OK +CPU 3 init OK +Hello, C app! +Shutting down... diff --git a/apps/c/helloworld/main.c b/apps/c/helloworld/main.c new file mode 100644 index 000000000..0e5e7a80f --- /dev/null +++ b/apps/c/helloworld/main.c @@ -0,0 +1,7 @@ +#include + +int main() +{ + printf("Hello, %c app!\n", 'C'); + return 0; +} diff --git a/apps/c/helloworld/test_cmd b/apps/c/helloworld/test_cmd new file mode 100644 index 000000000..1e0f8a32c --- /dev/null +++ b/apps/c/helloworld/test_cmd @@ -0,0 +1,3 @@ +test_one "LOG=info" "expect_info.out" +test_one "SMP=4 LOG=info" "expect_info_smp4.out" +rm -f $APP/*.o diff --git a/apps/c/httpclient/axbuild.mk b/apps/c/httpclient/axbuild.mk new file mode 100644 index 000000000..633789c4b --- /dev/null +++ b/apps/c/httpclient/axbuild.mk @@ -0,0 +1 @@ +app-objs := httpclient.o diff --git a/apps/c/httpclient/expect_info.out b/apps/c/httpclient/expect_info.out new file mode 100644 index 000000000..815bff980 --- /dev/null +++ b/apps/c/httpclient/expect_info.out @@ -0,0 +1,38 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize platform devices... +Initialize device drivers... +registered a new Net device at .\+: "virtio-net" +Initialize network subsystem... + use NIC 0: "virtio-net" +created net interface "eth0": + ether: 52-54-00-12-34-56 + ip: 10.0.2.15/24 + gateway: 10.0.2.2 +Primary CPU 0 init OK. +Hello, ArceOS C HTTP client! +IP: [0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+ +HTTP/1.1 200 OK +Server: nginx +Date: +Content-Type: text/plain +Content-Length: +Connection: keep-alive +Access-Control-Allow-Origin: * +Cache-Control: no-cache, no-store, must-revalidate + +^[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+ +Shutting down... diff --git a/apps/c/httpclient/features.txt b/apps/c/httpclient/features.txt new file mode 100644 index 000000000..25ca64f38 --- /dev/null +++ b/apps/c/httpclient/features.txt @@ -0,0 +1,3 @@ +alloc +paging +net diff --git a/apps/c/httpclient/httpclient.c b/apps/c/httpclient/httpclient.c new file mode 100644 index 000000000..32ec77b58 --- /dev/null +++ b/apps/c/httpclient/httpclient.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include + +const char request[] = "\ +GET / HTTP/1.1\r\n\ +Host: ident.me\r\n\ +Accept: */*\r\n\ +\r\n"; + +int main() +{ + puts("Hello, ArceOS C HTTP client!"); + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock == -1) { + perror("socket() error"); + return -1; + } + struct addrinfo *res; + + if (getaddrinfo("ident.me", NULL, NULL, &res) != 0) { + perror("getaddrinfo() error"); + return -1; + } + char str[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &(((struct sockaddr_in *)(res->ai_addr))->sin_addr), str, + INET_ADDRSTRLEN) == NULL) { + perror("inet_ntop() error"); + return -1; + } + printf("IP: %s\n", str); + ((struct sockaddr_in *)(res->ai_addr))->sin_port = htons(80); + if (connect(sock, res->ai_addr, sizeof(*(res->ai_addr))) != 0) { + perror("connect() error"); + return -1; + } + char rebuf[2000] = {}; + if (send(sock, request, strlen(request), 0) == -1) { + perror("send() error"); + return -1; + } + ssize_t l = recv(sock, rebuf, 2000, 0); + if (l == -1) { + perror("recv() error"); + return -1; + } + rebuf[l] = '\0'; + printf("%s\n", rebuf); + + freeaddrinfo(res); + + return 0; +} diff --git a/apps/c/httpclient/test_cmd b/apps/c/httpclient/test_cmd new file mode 100644 index 000000000..1623cd74c --- /dev/null +++ b/apps/c/httpclient/test_cmd @@ -0,0 +1,2 @@ +test_one "LOG=info NET=y" "expect_info.out" +rm -f $APP/*.o diff --git a/apps/c/httpserver/axbuild.mk b/apps/c/httpserver/axbuild.mk new file mode 100644 index 000000000..c30772479 --- /dev/null +++ b/apps/c/httpserver/axbuild.mk @@ -0,0 +1 @@ +app-objs := httpserver.o diff --git a/apps/c/httpserver/features.txt b/apps/c/httpserver/features.txt new file mode 100644 index 000000000..25ca64f38 --- /dev/null +++ b/apps/c/httpserver/features.txt @@ -0,0 +1,3 @@ +alloc +paging +net diff --git a/apps/c/httpserver/httpserver.c b/apps/c/httpserver/httpserver.c new file mode 100644 index 000000000..4480785aa --- /dev/null +++ b/apps/c/httpserver/httpserver.c @@ -0,0 +1,90 @@ + +#include +#include +#include +#include +#include +#include + +const char header[] = "\ +HTTP/1.1 200 OK\r\n\ +Content-Type: text/html\r\n\ +Content-Length: %u\r\n\ +Connection: close\r\n\ +\r\n\ +"; + +const char content[] = "\n\ +\n\ + Hello, ArceOS\n\ +\n\ +\n\ +
\n\ +

Hello, ArceOS

\n\ +
\n\ +
\n\ +
\n\ + Powered by ArceOS example HTTP server v0.1.0\n\ +
\n\ +\n\ +\n\ +"; + +int main() +{ + puts("Hello, ArceOS C HTTP server!"); + struct sockaddr_in local, remote; + int addr_len = sizeof(remote); + local.sin_family = AF_INET; + if (inet_pton(AF_INET, "0.0.0.0", &(local.sin_addr)) != 1) { + perror("inet_pton() error"); + return -1; + } + local.sin_port = htons(5555); + int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock == -1) { + perror("socket() error"); + return -1; + } + if (bind(sock, (struct sockaddr *)&local, sizeof(local)) != 0) { + perror("bind() error"); + return -1; + } + if (listen(sock, 0) != 0) { + perror("listen() error"); + return -1; + } + puts("listen on: http://0.0.0.0:5555/"); + char buf[1024] = {}; + int client; + char response[1024] = {}; + snprintf(response, 1024, header, strlen(content)); + strcat(response, content); + for (;;) { + client = accept(sock, (struct sockaddr *)&remote, (socklen_t *)&addr_len); + if (client == -1) { + perror("accept() error"); + return -1; + } + printf("new client %d\n", client); + if (recv(client, buf, 1024, 0) == -1) { + perror("recv() error"); + return -1; + } + ssize_t l = send(client, response, strlen(response), 0); + if (l == -1) { + perror("send() error"); + return -1; + } + if (close(client) == -1) { + perror("close() error"); + return -1; + } + printf("client %d close: %ld bytes sent\n", client, l); + } + if (close(sock) == -1) { + perror("close() error"); + return -1; + } + return 0; +} diff --git a/apps/c/iperf/.gitignore b/apps/c/iperf/.gitignore new file mode 100644 index 000000000..64620fc22 --- /dev/null +++ b/apps/c/iperf/.gitignore @@ -0,0 +1 @@ +iperf-* diff --git a/apps/c/iperf/README.md b/apps/c/iperf/README.md new file mode 100644 index 000000000..ca6f1469e --- /dev/null +++ b/apps/c/iperf/README.md @@ -0,0 +1,54 @@ +# How to run iperf on ArceOS and benchmark network performance + +## Build & run + +Build and start the [`iperf3`](https://github.com/esnet/iperf) server on ArceOS: + +```bash +# in arceos root directory +make A=apps/c/iperf BLK=y NET=y ARCH= run +``` + +## Benchmark + +In another shell, run the `iperf3` client: + +* iperf on ArceOS as the receiver: + + ```bash + # TCP + iperf3 -c 127.0.0.1 -p 5555 + # UDP + iperf3 -uc 127.0.0.1 -p 5555 -b -l + ``` + + You need to set the `` (in bits/sec) to avoid sending packets too fast from the client when use UDP. + +* iperf on ArceOS as the sender: + + ```bash + # TCP + iperf3 -c 127.0.0.1 -p 5555 -R + # UDP + iperf3 -uc 127.0.0.1 -p 5555 -b 0 -l -R + ``` + +By default, the `` is 128 KB for TCP and 8 KB for UDP. Larger buffer length may improve the performance. You can change it by the `-l` option of `iperf3`. + +Note that if the `` is greater than `1472` (total packet length is exceeded the MTU of the NIC) when use UDP, packets fragmentation will occur. You should enable fragmentation features in [smoltcp](https://github.com/smoltcp-rs/smoltcp): + +```toml +# in arceos/modules/axnet/Cargo.toml +[dependencies.smoltcp] +git = "https://github.com/smoltcp-rs/smoltcp.git" +rev = "1f9b9f0" +default-features = false +features = [ + "medium-ethernet", + "proto-ipv4", + "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dns", + "fragmentation-buffer-size-65536", "proto-ipv4-fragmentation", + "reassembly-buffer-size-65536", "reassembly-buffer-count-32", + "assembler-max-segment-count-32", +] +``` diff --git a/apps/c/iperf/axbuild.mk b/apps/c/iperf/axbuild.mk new file mode 100644 index 000000000..7c4dcf7aa --- /dev/null +++ b/apps/c/iperf/axbuild.mk @@ -0,0 +1,32 @@ +IPERF_VERSION := 3.1.3 +APP_CFLAGS := -Ulinux + +iperf_pkg := iperf-$(IPERF_VERSION) +iperf_dir := $(APP)/$(iperf_pkg) +iperf_src := \ + cjson.c \ + iperf_api.c \ + iperf_error.c \ + iperf_client_api.c \ + iperf_locale.c \ + iperf_server_api.c \ + iperf_tcp.c \ + iperf_udp.c \ + iperf_sctp.c \ + iperf_util.c \ + net.c \ + tcp_info.c \ + tcp_window_size.c \ + timer.c \ + units.c \ + main_server.c + +app-objs := $(patsubst %.c,$(iperf_pkg)/src/%.o,$(iperf_src)) + +.PRECIOUS: $(APP)/%.c +$(APP)/%.c: + @echo "Download iperf source code" + wget https://downloads.es.net/pub/iperf/$(iperf_pkg).tar.gz -P $(APP) + tar -zxvf $(APP)/$(iperf_pkg).tar.gz -C $(APP) && rm -f $(APP)/$(iperf_pkg).tar.gz + cd $(iperf_dir) && git init && git add . + patch -p1 -N -d $(iperf_dir) --no-backup-if-mismatch -r - < $(APP)/iperf.patch diff --git a/apps/c/iperf/features.txt b/apps/c/iperf/features.txt new file mode 100644 index 000000000..bf42f8cfe --- /dev/null +++ b/apps/c/iperf/features.txt @@ -0,0 +1,6 @@ +alloc +paging +net +fs +select +fp_simd diff --git a/apps/c/iperf/iperf.patch b/apps/c/iperf/iperf.patch new file mode 100644 index 000000000..07994d1a7 --- /dev/null +++ b/apps/c/iperf/iperf.patch @@ -0,0 +1,664 @@ +diff --git a/src/cjson.c b/src/cjson.c +index 595d919..43264e6 100644 +--- a/src/cjson.c ++++ b/src/cjson.c +@@ -105,7 +105,7 @@ void cJSON_Delete(cJSON *c) + /* Parse the input text to generate a number, and populate the result into item. */ + static const char *parse_number(cJSON *item,const char *num) + { +- double n=0,sign=1,scale=0;int subscale=0,signsubscale=1; ++ double n=0,sign=1;int subscale=0,signsubscale=1,scale=0; + + if (*num=='-') sign=-1,num++; /* Has sign? */ + if (*num=='0') num++; /* is zero */ +@@ -116,8 +116,11 @@ static const char *parse_number(cJSON *item,const char *num) + while (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0'); /* Number? */ + } + +- n=sign*n*pow(10.0,(scale+subscale*signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */ +- ++ scale += subscale * signsubscale; ++ n = sign * n; ++ while (scale != 0) ++ { if (scale > 0) n *= 10.0,scale--; else n /= 10.0,scale++; ++ } + item->valuedouble=n; + item->valueint=(int64_t)n; + item->type=cJSON_Number; +diff --git a/src/iperf.h b/src/iperf.h +index 8ff25d7..4e96d9f 100755 +--- a/src/iperf.h ++++ b/src/iperf.h +@@ -37,6 +37,7 @@ + #include + #include + #include ++#include + + #if defined(HAVE_CPUSET_SETAFFINITY) + #include +diff --git a/src/iperf_api.c b/src/iperf_api.c +index 5b56af6..1e12a81 100755 +--- a/src/iperf_api.c ++++ b/src/iperf_api.c +@@ -32,7 +32,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -52,7 +51,6 @@ + #include + #include + #include +-#include + #include + #include + +@@ -95,7 +93,7 @@ static cJSON *JSON_read(int fd); + void + usage() + { +- fputs(usage_shortstr, stderr); ++ puts(usage_shortstr); + } + + +@@ -613,6 +611,7 @@ iperf_on_test_finish(struct iperf_test *test) + + /******************************************************************************/ + ++#if 0 + int + iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) + { +@@ -1035,6 +1034,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) + + return 0; + } ++#endif + + int + iperf_set_send_state(struct iperf_test *test, signed char state) +@@ -2639,8 +2639,7 @@ iperf_free_stream(struct iperf_stream *sp) + struct iperf_interval_results *irp, *nirp; + + /* XXX: need to free interval list too! */ +- munmap(sp->buffer, sp->test->settings->blksize); +- close(sp->buffer_fd); ++ free(sp->buffer); + if (sp->diskfile_fd >= 0) + close(sp->diskfile_fd); + for (irp = TAILQ_FIRST(&sp->result->interval_results); irp != NULL; irp = nirp) { +@@ -2691,35 +2690,10 @@ iperf_new_stream(struct iperf_test *test, int s) + TAILQ_INIT(&sp->result->interval_results); + + /* Create and randomize the buffer */ +- sp->buffer_fd = mkstemp(template); +- if (sp->buffer_fd == -1) { +- i_errno = IECREATESTREAM; +- free(sp->result); +- free(sp); +- return NULL; +- } +- if (unlink(template) < 0) { +- i_errno = IECREATESTREAM; +- free(sp->result); +- free(sp); +- return NULL; +- } +- if (ftruncate(sp->buffer_fd, test->settings->blksize) < 0) { +- i_errno = IECREATESTREAM; +- free(sp->result); +- free(sp); +- return NULL; +- } +- sp->buffer = (char *) mmap(NULL, test->settings->blksize, PROT_READ|PROT_WRITE, MAP_PRIVATE, sp->buffer_fd, 0); +- if (sp->buffer == MAP_FAILED) { +- i_errno = IECREATESTREAM; +- free(sp->result); +- free(sp); +- return NULL; +- } +- srandom(time(NULL)); ++ sp->buffer = (char *)malloc(test->settings->blksize); ++ srand(time(NULL)); + for (i = 0; i < test->settings->blksize; ++i) +- sp->buffer[i] = random(); ++ sp->buffer[i] = rand(); + + /* Set socket */ + sp->socket = s; +@@ -2731,7 +2705,7 @@ iperf_new_stream(struct iperf_test *test, int s) + sp->diskfile_fd = open(test->diskfile_name, test->sender ? O_RDONLY : (O_WRONLY|O_CREAT|O_TRUNC), S_IRUSR|S_IWUSR); + if (sp->diskfile_fd == -1) { + i_errno = IEFILE; +- munmap(sp->buffer, sp->test->settings->blksize); ++ free(sp->buffer); + free(sp->result); + free(sp); + return NULL; +@@ -2745,8 +2719,7 @@ iperf_new_stream(struct iperf_test *test, int s) + + /* Initialize stream */ + if (iperf_init_stream(sp, test) < 0) { +- close(sp->buffer_fd); +- munmap(sp->buffer, sp->test->settings->blksize); ++ free(sp->buffer); + free(sp->result); + free(sp); + return NULL; +@@ -2774,25 +2747,6 @@ iperf_init_stream(struct iperf_stream *sp, struct iperf_test *test) + return -1; + } + +- /* Set IP TOS */ +- if ((opt = test->settings->tos)) { +- if (getsockdomain(sp->socket) == AF_INET6) { +-#ifdef IPV6_TCLASS +- if (setsockopt(sp->socket, IPPROTO_IPV6, IPV6_TCLASS, &opt, sizeof(opt)) < 0) { +- i_errno = IESETCOS; +- return -1; +- } +-#else +- i_errno = IESETCOS; +- return -1; +-#endif +- } else { +- if (setsockopt(sp->socket, IPPROTO_IP, IP_TOS, &opt, sizeof(opt)) < 0) { +- i_errno = IESETTOS; +- return -1; +- } +- } +- } + + return 0; + } +diff --git a/src/iperf_api.h b/src/iperf_api.h +index 0f153fe..f2ff9bc 100755 +--- a/src/iperf_api.h ++++ b/src/iperf_api.h +@@ -29,6 +29,7 @@ + + #include + #include ++#include + + struct iperf_test; + struct iperf_stream_result; +diff --git a/src/iperf_client_api.c b/src/iperf_client_api.c +index f19f6f1..ff0a4c8 100644 +--- a/src/iperf_client_api.c ++++ b/src/iperf_client_api.c +@@ -33,7 +33,6 @@ + #include + #include + #include +-#include + #include + + #include "iperf.h" +diff --git a/src/iperf_config.h b/src/iperf_config.h +new file mode 100644 +index 0000000..979b858 +--- /dev/null ++++ b/src/iperf_config.h +@@ -0,0 +1,98 @@ ++/* src/iperf_config.h. Generated from iperf_config.h.in by configure. */ ++/* src/iperf_config.h.in. Generated from configure.ac by autoheader. */ ++ ++/* Define to 1 if you have the `cpuset_setaffinity' function. */ ++/* #undef HAVE_CPUSET_SETAFFINITY */ ++ ++/* Have CPU affinity support. */ ++// #define HAVE_CPU_AFFINITY 1 ++ ++/* Define to 1 if you have the header file. */ ++// #define HAVE_DLFCN_H 1 ++ ++/* Have IPv6 flowlabel support. */ ++// #define HAVE_FLOWLABEL 1 ++ ++/* Define to 1 if you have the header file. */ ++// #define HAVE_INTTYPES_H 1 ++ ++/* Define to 1 if you have the header file. */ ++// #define HAVE_MEMORY_H 1 ++ ++/* Define to 1 if you have the header file. */ ++/* #undef HAVE_NETINET_SCTP_H */ ++ ++/* Define to 1 if you have the `sched_setaffinity' function. */ ++// #define HAVE_SCHED_SETAFFINITY 1 ++ ++/* Have SCTP support. */ ++/* #undef HAVE_SCTP */ ++ ++/* Define to 1 if you have the `sendfile' function. */ ++// #define HAVE_SENDFILE 1 ++ ++/* Have SO_MAX_PACING_RATE sockopt. */ ++// #define HAVE_SO_MAX_PACING_RATE 1 ++ ++/* Define to 1 if you have the header file. */ ++#define HAVE_STDINT_H 1 ++ ++/* Define to 1 if you have the header file. */ ++#define HAVE_STDLIB_H 1 ++ ++/* Define to 1 if you have the header file. */ ++// #define HAVE_STRINGS_H 1 ++ ++/* Define to 1 if you have the header file. */ ++#define HAVE_STRING_H 1 ++ ++/* Define to 1 if the system has the type `struct sctp_assoc_value'. */ ++/* #undef HAVE_STRUCT_SCTP_ASSOC_VALUE */ ++ ++/* Define to 1 if you have the header file. */ ++#define HAVE_SYS_SOCKET_H 1 ++ ++/* Define to 1 if you have the header file. */ ++#define HAVE_SYS_STAT_H 1 ++ ++/* Define to 1 if you have the header file. */ ++#define HAVE_SYS_TYPES_H 1 ++ ++/* Have TCP_CONGESTION sockopt. */ ++#define HAVE_TCP_CONGESTION 1 ++ ++/* Define to 1 if you have the header file. */ ++#define HAVE_UNISTD_H 1 ++ ++// /* Define to the sub-directory where libtool stores uninstalled libraries. */ ++// #define LT_OBJDIR ".libs/" ++ ++/* Name of package */ ++#define PACKAGE "iperf" ++ ++/* Define to the address where bug reports for this package should be sent. */ ++#define PACKAGE_BUGREPORT "https://github.com/esnet/iperf" ++ ++/* Define to the full name of this package. */ ++#define PACKAGE_NAME "iperf" ++ ++/* Define to the full name and version of this package. */ ++#define PACKAGE_STRING "iperf 3.1.3" ++ ++/* Define to the one symbol short name of this package. */ ++#define PACKAGE_TARNAME "iperf" ++ ++/* Define to the home page for this package. */ ++#define PACKAGE_URL "http://software.es.net/iperf/" ++ ++/* Define to the version of this package. */ ++#define PACKAGE_VERSION "3.1.3" ++ ++/* Define to 1 if you have the ANSI C header files. */ ++#define STDC_HEADERS 1 ++ ++/* Version number of package */ ++#define VERSION "3.1.3" ++ ++/* Define to empty if `const' does not conform to ANSI C. */ ++/* #undef const */ +diff --git a/src/iperf_server_api.c b/src/iperf_server_api.c +index 227ec78..f0a4a30 100644 +--- a/src/iperf_server_api.c ++++ b/src/iperf_server_api.c +@@ -30,7 +30,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -47,7 +46,6 @@ + #include + #include + #include +-#include + #include + + #include "iperf.h" +diff --git a/src/iperf_util.c b/src/iperf_util.c +index 73dc362..4bfe6f9 100644 +--- a/src/iperf_util.c ++++ b/src/iperf_util.c +@@ -40,7 +40,6 @@ + #include + #include + #include +-#include + #include + #include + +@@ -59,17 +58,17 @@ void + make_cookie(char *cookie) + { + static int randomized = 0; +- char hostname[500]; ++ char hostname[500] = "arceos-iperf"; + struct timeval tv; + char temp[1000]; + + if ( ! randomized ) +- srandom((int) time(0) ^ getpid()); ++ srand((int) time(0) ^ getpid()); + + /* Generate a string based on hostname, time, randomness, and filler. */ +- (void) gethostname(hostname, sizeof(hostname)); ++ // (void) gethostname(hostname, sizeof(hostname)); + (void) gettimeofday(&tv, 0); +- (void) snprintf(temp, sizeof(temp), "%s.%ld.%06ld.%08lx%08lx.%s", hostname, (unsigned long int) tv.tv_sec, (unsigned long int) tv.tv_usec, (unsigned long int) random(), (unsigned long int) random(), "1234567890123456789012345678901234567890"); ++ (void) snprintf(temp, sizeof(temp), "%s.%ld.%06ld.%08lx%08lx.%s", hostname, (unsigned long int) tv.tv_sec, (unsigned long int) tv.tv_usec, (unsigned long int) rand(), (unsigned long int) rand(), "1234567890123456789012345678901234567890"); + + /* Now truncate it to 36 bytes and terminate. */ + memcpy(cookie, temp, 36); +@@ -178,50 +177,25 @@ delay(int us) + void + cpu_util(double pcpu[3]) + { +- static struct timeval last; +- static clock_t clast; +- static struct rusage rlast; +- struct timeval temp; +- clock_t ctemp; +- struct rusage rtemp; +- double timediff; +- double userdiff; +- double systemdiff; +- + if (pcpu == NULL) { +- gettimeofday(&last, NULL); +- clast = clock(); +- getrusage(RUSAGE_SELF, &rlast); + return; + } + +- gettimeofday(&temp, NULL); +- ctemp = clock(); +- getrusage(RUSAGE_SELF, &rtemp); +- +- timediff = ((temp.tv_sec * 1000000.0 + temp.tv_usec) - +- (last.tv_sec * 1000000.0 + last.tv_usec)); +- userdiff = ((rtemp.ru_utime.tv_sec * 1000000.0 + rtemp.ru_utime.tv_usec) - +- (rlast.ru_utime.tv_sec * 1000000.0 + rlast.ru_utime.tv_usec)); +- systemdiff = ((rtemp.ru_stime.tv_sec * 1000000.0 + rtemp.ru_stime.tv_usec) - +- (rlast.ru_stime.tv_sec * 1000000.0 + rlast.ru_stime.tv_usec)); +- +- pcpu[0] = (((ctemp - clast) * 1000000.0 / CLOCKS_PER_SEC) / timediff) * 100; +- pcpu[1] = (userdiff / timediff) * 100; +- pcpu[2] = (systemdiff / timediff) * 100; ++ pcpu[0] =0; ++ pcpu[1] =0; ++ pcpu[2] =0; + } + + const char * + get_system_info(void) + { + static char buf[1024]; +- struct utsname uts; ++ // struct utsname uts; + + memset(buf, 0, 1024); +- uname(&uts); ++ // uname(&uts); + +- snprintf(buf, sizeof(buf), "%s %s %s %s %s", uts.sysname, uts.nodename, +- uts.release, uts.version, uts.machine); ++ snprintf(buf, sizeof(buf), "%s %s %s %s %s", "arceos", "iperf", "0", "0","null"); + + return buf; + } +diff --git a/src/main_server.c b/src/main_server.c +new file mode 100644 +index 0000000..c9964ba +--- /dev/null ++++ b/src/main_server.c +@@ -0,0 +1,42 @@ ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "iperf_api.h" ++ ++int main() { ++ int port; ++ struct iperf_test *test; ++ int consecutive_errors; ++ ++ port = 5555; ++ ++ test = iperf_new_test(); ++ if (test == NULL) { ++ printf("failed to create test\n"); ++ exit(-1); ++ } ++ iperf_defaults(test); ++ iperf_set_test_role(test, 's'); ++ iperf_set_test_server_port(test, port); ++ iperf_set_test_bind_address(test, "0.0.0.0"); ++ consecutive_errors = 0; ++ for (;;) { ++ if (iperf_run_server(test) < 0) { ++ printf("error - %s\n\n", iperf_strerror(i_errno)); ++ ++consecutive_errors; ++ if (consecutive_errors >= 5) { ++ printf("too many errors, exiting\n"); ++ break; ++ } ++ } else ++ consecutive_errors = 0; ++ iperf_reset_test(test); ++ } ++ ++ iperf_free_test(test); ++ return 0; ++} +diff --git a/src/net.c b/src/net.c +index aa4a15c..80271e2 100644 +--- a/src/net.c ++++ b/src/net.c +@@ -31,13 +31,13 @@ + #include + #include + #include +-#include ++#include + #include + #include + #include + #include + #include +-#include ++#include + + #ifdef HAVE_SENDFILE + #ifdef linux +diff --git a/src/tcp_info.c b/src/tcp_info.c +index 76b117a..950ab41 100644 +--- a/src/tcp_info.c ++++ b/src/tcp_info.c +@@ -45,7 +45,7 @@ + + #include + #include +-#include ++// #include + #include + #include + #include +diff --git a/src/units.c b/src/units.c +index ed1ea60..3d590db 100644 +--- a/src/units.c ++++ b/src/units.c +@@ -78,116 +78,6 @@ extern "C" + const long MEGA_RATE_UNIT = 1000 * 1000; + const long GIGA_RATE_UNIT = 1000 * 1000 * 1000; + +-/* ------------------------------------------------------------------- +- * unit_atof +- * +- * Given a string of form #x where # is a number and x is a format +- * character listed below, this returns the interpreted integer. +- * Gg, Mm, Kk are giga, mega, kilo respectively +- * ------------------------------------------------------------------- */ +- +- double unit_atof(const char *s) +- { +- double n; +- char suffix = '\0'; +- +- assert(s != NULL); +- +- /* scan the number and any suffices */ +- sscanf(s, "%lf%c", &n, &suffix); +- +- /* convert according to [Gg Mm Kk] */ +- switch (suffix) +- { +- case 'g': case 'G': +- n *= GIGA_UNIT; +- break; +- case 'm': case 'M': +- n *= MEGA_UNIT; +- break; +- case 'k': case 'K': +- n *= KILO_UNIT; +- break; +- default: +- break; +- } +- return n; +- } /* end unit_atof */ +- +- +-/* ------------------------------------------------------------------- +- * unit_atof_rate +- * +- * Similar to unit_atof, but uses 10-based rather than 2-based +- * suffixes. +- * ------------------------------------------------------------------- */ +- +- double unit_atof_rate(const char *s) +- { +- double n; +- char suffix = '\0'; +- +- assert(s != NULL); +- +- /* scan the number and any suffices */ +- sscanf(s, "%lf%c", &n, &suffix); +- +- /* convert according to [Gg Mm Kk] */ +- switch (suffix) +- { +- case 'g': case 'G': +- n *= GIGA_RATE_UNIT; +- break; +- case 'm': case 'M': +- n *= MEGA_RATE_UNIT; +- break; +- case 'k': case 'K': +- n *= KILO_RATE_UNIT; +- break; +- default: +- break; +- } +- return n; +- } /* end unit_atof_rate */ +- +- +- +-/* ------------------------------------------------------------------- +- * unit_atoi +- * +- * Given a string of form #x where # is a number and x is a format +- * character listed below, this returns the interpreted integer. +- * Gg, Mm, Kk are giga, mega, kilo respectively +- * ------------------------------------------------------------------- */ +- +- iperf_size_t unit_atoi(const char *s) +- { +- double n; +- char suffix = '\0'; +- +- assert(s != NULL); +- +- /* scan the number and any suffices */ +- sscanf(s, "%lf%c", &n, &suffix); +- +- /* convert according to [Gg Mm Kk] */ +- switch (suffix) +- { +- case 'g': case 'G': +- n *= GIGA_UNIT; +- break; +- case 'm': case 'M': +- n *= MEGA_UNIT; +- break; +- case 'k': case 'K': +- n *= KILO_UNIT; +- break; +- default: +- break; +- } +- return (iperf_size_t) n; +- } /* end unit_atof */ +- + /* ------------------------------------------------------------------- + * constants for byte_printf + * ------------------------------------------------------------------- */ +diff --git a/src/units.h b/src/units.h +index 6ab9216..437c89d 100644 +--- a/src/units.h ++++ b/src/units.h +@@ -28,7 +28,4 @@ enum { + UNIT_LEN = 32 + }; + +-double unit_atof( const char *s ); +-double unit_atof_rate( const char *s ); +-iperf_size_t unit_atoi( const char *s ); + void unit_snprintf( char *s, int inLen, double inNum, char inFormat ); +diff --git a/src/version.h b/src/version.h +new file mode 100644 +index 0000000..db8f001 +--- /dev/null ++++ b/src/version.h +@@ -0,0 +1,27 @@ ++/* ++ * iperf, Copyright (c) 2014, The Regents of the University of ++ * California, through Lawrence Berkeley National Laboratory (subject ++ * to receipt of any required approvals from the U.S. Dept. of ++ * Energy). All rights reserved. ++ * ++ * If you have questions about your rights to use or distribute this ++ * software, please contact Berkeley Lab's Technology Transfer ++ * Department at TTD@lbl.gov. ++ * ++ * NOTICE. This software is owned by the U.S. Department of Energy. ++ * As such, the U.S. Government has been granted for itself and others ++ * acting on its behalf a paid-up, nonexclusive, irrevocable, ++ * worldwide license in the Software to reproduce, prepare derivative ++ * works, and perform publicly and display publicly. Beginning five ++ * (5) years after the date permission to assert copyright is obtained ++ * from the U.S. Department of Energy, and subject to any subsequent ++ * five (5) year renewals, the U.S. Government is granted for itself ++ * and others acting on its behalf a paid-up, nonexclusive, ++ * irrevocable, worldwide license in the Software to reproduce, ++ * prepare derivative works, distribute copies to the public, perform ++ * publicly and display publicly, and to permit others to do so. ++ * ++ * This code is distributed under a BSD style license, see the LICENSE ++ * file for complete information. ++ */ ++#define IPERF_VERSION "3.1.3" diff --git a/apps/c/memtest/axbuild.mk b/apps/c/memtest/axbuild.mk new file mode 100644 index 000000000..54ab99fc5 --- /dev/null +++ b/apps/c/memtest/axbuild.mk @@ -0,0 +1 @@ +app-objs := memtest.o diff --git a/apps/c/memtest/expect_trace.out b/apps/c/memtest/expect_trace.out new file mode 100644 index 000000000..04aaf13aa --- /dev/null +++ b/apps/c/memtest/expect_trace.out @@ -0,0 +1,43 @@ +smp = 1 +build_mode = release +log_level = trace + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... + use TLSF allocator. +initialize global allocator at: \[0x[0-9a-f]\+, 0x[0-9a-f]\+) +Initialize kernel page table... +Initialize platform devices... +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | EXECUTE) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | WRITE) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | WRITE) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | WRITE) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | WRITE) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | WRITE | DEVICE) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | WRITE) +set page table root: PA:0x[0-9a-f]\+ => PA:0x[0-9a-f]\+ +Primary CPU 0 init OK. +Running memory tests... +top of heap=0x[0-9a-f]\{16\} +72(+8)Byte allocated: p=0x[0-9a-f]\{16\} +allocate 8(+8)Byte for 9 times: +allocated addr=0x[0-9a-f]\{16\} +allocated addr=0x[0-9a-f]\{16\} +allocated addr=0x[0-9a-f]\{16\} +allocated addr=0x[0-9a-f]\{16\} +allocated addr=0x[0-9a-f]\{16\} +allocated addr=0x[0-9a-f]\{16\} +allocated addr=0x[0-9a-f]\{16\} +allocated addr=0x[0-9a-f]\{16\} +allocated addr=0x[0-9a-f]\{16\} +Memory tests run OK! +Shutting down... diff --git a/apps/c/memtest/features.txt b/apps/c/memtest/features.txt new file mode 100644 index 000000000..1b71ca850 --- /dev/null +++ b/apps/c/memtest/features.txt @@ -0,0 +1,2 @@ +alloc +paging diff --git a/apps/c/memtest/memtest.c b/apps/c/memtest/memtest.c new file mode 100644 index 000000000..78cef5ca2 --- /dev/null +++ b/apps/c/memtest/memtest.c @@ -0,0 +1,27 @@ +#include +#include +#include + +int main() +{ + puts("Running memory tests..."); + uintptr_t *brk = (uintptr_t *)malloc(0); + printf("top of heap=%p\n", brk); + + int n = 9; + int i = 0; + uintptr_t **p = (uintptr_t **)malloc(n * sizeof(uint64_t)); + printf("%d(+8)Byte allocated: p=%p\n", n * sizeof(uint64_t), p, p[1]); + printf("allocate %d(+8)Byte for %d times:\n", sizeof(uint64_t), n); + for (i = 0; i < n; i++) { + p[i] = (uintptr_t *)malloc(sizeof(uint64_t)); + *p[i] = 233; + printf("allocated addr=%p\n", p[i]); + } + for (i = 0; i < n; i++) { + free(p[i]); + } + free(p); + puts("Memory tests run OK!"); + return 0; +} diff --git a/apps/c/memtest/test_cmd b/apps/c/memtest/test_cmd new file mode 100644 index 000000000..55a07d6a8 --- /dev/null +++ b/apps/c/memtest/test_cmd @@ -0,0 +1,2 @@ +test_one "LOG=trace" "expect_trace.out" +rm -f $APP/*.o diff --git a/apps/c/pthread/basic/expect_info_smp4_fifo.out b/apps/c/pthread/basic/expect_info_smp4_fifo.out new file mode 100644 index 000000000..2e337c9d1 --- /dev/null +++ b/apps/c/pthread/basic/expect_info_smp4_fifo.out @@ -0,0 +1,26 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize platform devices... +Initialize scheduling... + use FIFO scheduler. +Pass NULL argument +Recieve: Main thread pass message +test_create_join: Child thread return message +A message before call pthread_exit +test_create_exit: Exit message +test_mutex: data = 100 +(C)Pthread basic tests run OK! +Shutting down... diff --git a/apps/c/pthread/basic/features.txt b/apps/c/pthread/basic/features.txt new file mode 100644 index 000000000..6ca9b0c5f --- /dev/null +++ b/apps/c/pthread/basic/features.txt @@ -0,0 +1,3 @@ +alloc +paging +multitask diff --git a/apps/c/pthread/basic/main.c b/apps/c/pthread/basic/main.c new file mode 100644 index 000000000..cf697e903 --- /dev/null +++ b/apps/c/pthread/basic/main.c @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include + +void *ThreadFunc1(void *arg) +{ + if (arg == NULL) { + puts("Pass NULL argument"); + return NULL; + } else { + char buf[64]; + sprintf(buf, "Recieve: %s", (char *)arg); + puts(buf); + return "Child thread return message"; + } +} + +void *ThreadFunc2(void *arg) +{ + puts("A message before call pthread_exit"); + pthread_exit("Exit message"); + puts("This message should not be printed"); +} + +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + +void *ThreadFunc3(void *arg) +{ + pthread_mutex_lock(&lock); + + int value = *(int *)arg; + + // long operation + for (int i = 0; i < 100000; i++) getpid(); + + *(int *)arg = value + 1; + + pthread_mutex_unlock(&lock); + return 0; +} + +void test_create_join() +{ + int res; + char *s = "Main thread pass message"; + void *thread_result; + pthread_t t1, t2; + res = pthread_create(&t1, NULL, ThreadFunc1, NULL); + if (res != 0) { + puts("fail to create thread1"); + return; + } + + res = pthread_join(t1, NULL); + if (res != 0) { + puts("First pthread join fail"); + } + + res = pthread_create(&t2, NULL, ThreadFunc1, (void *)s); + if (res != 0) { + puts("fail to create thread2"); + return; + } + + res = pthread_join(t2, &thread_result); + if (res != 0) { + puts("Second pthread join fail"); + } + + printf("test_create_join: %s\n", (char *)thread_result); +} + +void test_create_exit() +{ + int res; + void *thread_result; + pthread_t t1; + + res = pthread_create(&t1, NULL, ThreadFunc2, NULL); + if (res != 0) { + puts("pthread create fail"); + return; + } + + res = pthread_join(t1, &thread_result); + if (res != 0) { + puts("pthread join fail"); + } + + printf("test_create_exit: %s\n", (char *)thread_result); +} + +void test_mutex() +{ + const int NUM_THREADS = 100; + int data = 0; + pthread_t t[NUM_THREADS]; + + for (int i = 0; i < NUM_THREADS; i++) { + int res = pthread_create(&t[i], NULL, ThreadFunc3, &data); + if (res != 0) { + puts("pthread create fail"); + return; + } + } + + for (int i = 0; i < NUM_THREADS; i++) { + int res = pthread_join(t[i], NULL); + if (res != 0) { + puts("pthread join fail"); + } + } + + printf("test_mutex: data = %d\n", data); + assert(data == NUM_THREADS); +} + +int main() +{ + pthread_t main_thread = pthread_self(); + assert(main_thread != 0); + + test_create_join(); + test_create_exit(); + test_mutex(); + puts("(C)Pthread basic tests run OK!"); + + return 0; +} diff --git a/apps/c/pthread/basic/test_cmd b/apps/c/pthread/basic/test_cmd new file mode 100644 index 000000000..fa3dfed71 --- /dev/null +++ b/apps/c/pthread/basic/test_cmd @@ -0,0 +1,2 @@ +test_one "SMP=4 LOG=info" "expect_info_smp4_fifo.out" +rm -f $APP/*.o diff --git a/apps/c/pthread/parallel/expect_info_smp4_fifo.out b/apps/c/pthread/parallel/expect_info_smp4_fifo.out new file mode 100644 index 000000000..cb930640c --- /dev/null +++ b/apps/c/pthread/parallel/expect_info_smp4_fifo.out @@ -0,0 +1,60 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize platform devices... +Initialize scheduling... + use FIFO scheduler +CPU 1 started +CPU 1 init OK +CPU 2 started +CPU 2 init OK +CPU 3 started +CPU 3 init OK +CPU 0 init OK +part 0: \[0, 125000) +part 1: \[125000, 250000) +part 2: \[250000, 375000) +part 3: \[375000, 500000) +part 0 finished +part 4: \[500000, 625000) +part 1 finished +part 5: \[625000, 750000) +part 2 finished +part 6: \[750000, 875000) +part 3 finished +part 7: \[875000, 1000000) +part 4 finished +part 8: \[1000000, 1125000) +part 5 finished +part 9: \[1125000, 1250000) +part 6 finished +part 10: \[1250000, 1375000) +part 7 finished +part 11: \[1375000, 1500000) +part 8 finished +part 12: \[1500000, 1625000) +part 9 finished +part 13: \[1625000, 1750000) +part 10 finished +part 14: \[1750000, 1875000) +part 11 finished +part 15: \[1875000, 2000000) +part 12 finished +part 13 finished +part 14 finished +part 15 finished +actual sum = 61783189038 +(C)Pthread parallel run OK! +Shutting down... diff --git a/apps/c/pthread/parallel/expect_info_smp4_rr.out b/apps/c/pthread/parallel/expect_info_smp4_rr.out new file mode 100644 index 000000000..5c3fc839b --- /dev/null +++ b/apps/c/pthread/parallel/expect_info_smp4_rr.out @@ -0,0 +1,61 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize platform devices... +Initialize scheduling... + use Round-robin scheduler +CPU 1 started +CPU 1 init OK +CPU 2 started +CPU 2 init OK +CPU 3 started +CPU 3 init OK +Initialize interrupt handlers... +CPU 0 init OK +part 0: \[0, 125000) +part 1: \[125000, 250000) +part 2: \[250000, 375000) +part 3: \[375000, 500000) +part 0 finished +part 4: \[500000, 625000) +part 1 finished +part 5: \[625000, 750000) +part 2 finished +part 6: \[750000, 875000) +part 3 finished +part 7: \[875000, 1000000) +part 4 finished +part 8: \[1000000, 1125000) +part 5 finished +part 9: \[1125000, 1250000) +part 6 finished +part 10: \[1250000, 1375000) +part 7 finished +part 11: \[1375000, 1500000) +part 8 finished +part 12: \[1500000, 1625000) +part 9 finished +part 13: \[1625000, 1750000) +part 10 finished +part 14: \[1750000, 1875000) +part 11 finished +part 15: \[1875000, 2000000) +part 12 finished +part 13 finished +part 14 finished +part 15 finished +actual sum = 61783189038 +(C)Pthread parallel run OK! +Shutting down... diff --git a/apps/c/pthread/parallel/features.txt b/apps/c/pthread/parallel/features.txt new file mode 100644 index 000000000..6ca9b0c5f --- /dev/null +++ b/apps/c/pthread/parallel/features.txt @@ -0,0 +1,3 @@ +alloc +paging +multitask diff --git a/apps/c/pthread/parallel/main.c b/apps/c/pthread/parallel/main.c new file mode 100644 index 000000000..d1dc9a2e1 --- /dev/null +++ b/apps/c/pthread/parallel/main.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include + +#define NUM_DATA 2000000 +#define NUM_TASKS 16 + +uint64_t array[NUM_DATA] = {0}; + +uint64_t my_sqrt(uint64_t n) +{ + uint64_t x = n; + while (1) { + if (x * x <= n && (x + 1) * (x + 1) > n) + return x; + + x = (x + n / x) / 2; + } +} + +void *ThreadFunc(void *arg) +{ + int id = *(int *)arg; + int left = (NUM_DATA / NUM_TASKS) * id; + int right = + (left + (NUM_DATA / NUM_TASKS)) < NUM_DATA ? (left + (NUM_DATA / NUM_TASKS)) : NUM_DATA; + + char buf[512]; + sprintf(buf, "part %d: [%d, %d)", id, left, right); + puts(buf); + + uint64_t *partial_sum = (uint64_t *)calloc(1, sizeof(uint64_t)); + for (int i = left; i < right; i++) *partial_sum += my_sqrt(array[i]); + + sprintf(buf, "part %d finished", id); + puts(buf); + return (void *)(partial_sum); +} + +int main() +{ + for (int i = 0; i < NUM_DATA; i++) array[i] = rand(); + + uint64_t expect = 0; + for (int i = 0; i < NUM_DATA; i++) expect += my_sqrt(array[i]); + + int thread_part[NUM_TASKS]; + for (int i = 0; i < NUM_TASKS; i++) thread_part[i] = i; + + pthread_t tasks[NUM_TASKS]; + for (int i = 0; i < NUM_TASKS; i++) { + pthread_t t; + pthread_create(&t, NULL, ThreadFunc, (void *)(&thread_part[i])); + tasks[i] = t; + } + + uint64_t *partial_sum; + uint64_t actual = 0; + for (int i = 0; i < NUM_TASKS; i++) { + pthread_join(tasks[i], (void *)(&partial_sum)); + actual += *partial_sum; + } + + char buf[64]; + sprintf(buf, "actual sum = %lld", actual); + puts(buf); + + if (actual == expect) + puts("(C)Pthread parallel run OK!"); + else { + puts("(C)Pthread parallel run FAIL!"); + } + return 0; +} diff --git a/apps/c/pthread/parallel/test_cmd b/apps/c/pthread/parallel/test_cmd new file mode 100644 index 000000000..b0d44ae37 --- /dev/null +++ b/apps/c/pthread/parallel/test_cmd @@ -0,0 +1,3 @@ +test_one "SMP=4 LOG=info" "expect_info_smp4_fifo.out" +test_one "SMP=4 LOG=info FEATURES=sched_rr" "expect_info_smp4_rr.out" +rm -f $APP/*.o diff --git a/apps/c/pthread/pipe/expect_info_smp4_fifo.out b/apps/c/pthread/pipe/expect_info_smp4_fifo.out new file mode 100644 index 000000000..a1e328dd2 --- /dev/null +++ b/apps/c/pthread/pipe/expect_info_smp4_fifo.out @@ -0,0 +1,30 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize platform devices... +Initialize scheduling... + use FIFO scheduler. +Child thread send message(1) +Child thread send message(2) +Main thread recieve (1): I am child(1)! +Child thread send message(3) +Main thread recieve (2): I am child(2)! +Child thread send message(4) +Main thread recieve (3): I am child(3)! +Child thread send message(5) +Main thread recieve (4): I am child(4)! +Main thread recieve (5): I am child(5)! +(C)Pipe tests run OK +Shutting down... diff --git a/apps/c/pthread/pipe/features.txt b/apps/c/pthread/pipe/features.txt new file mode 100644 index 000000000..be558434d --- /dev/null +++ b/apps/c/pthread/pipe/features.txt @@ -0,0 +1,4 @@ +paging +alloc +multitask +pipe diff --git a/apps/c/pthread/pipe/main.c b/apps/c/pthread/pipe/main.c new file mode 100644 index 000000000..3f7bedd4b --- /dev/null +++ b/apps/c/pthread/pipe/main.c @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +const int ROUND = 5; + +void *ChildFunc(void *arg) +{ + int *fd = (int *)arg; + int i = 0; + char buf[32]; + while (i++ < ROUND) { + sprintf(buf, "Child thread send message(%d)", i); + puts(buf); + sprintf(buf, "I am child(%d)!", i); + write(fd[1], buf, strlen(buf) + 1); + sleep(1); + } + close(fd[1]); + return NULL; +} + +void main() +{ + int fd[2]; + int ret = pipe(fd); + if (ret != 0) { + puts("Fail to create pipe"); + return; + } + + pthread_t t1; + pthread_create(&t1, NULL, ChildFunc, (void *)fd); + + char msg[100]; + int j = 0; + while (j++ < ROUND) { + read(fd[0], msg, 15); + char buf[64]; + sprintf(buf, "Main thread recieve (%d): %s", j, msg); + puts(buf); + } + + puts("(C)Pipe tests run OK"); + return; +} diff --git a/apps/c/pthread/pipe/test_cmd b/apps/c/pthread/pipe/test_cmd new file mode 100644 index 000000000..fa3dfed71 --- /dev/null +++ b/apps/c/pthread/pipe/test_cmd @@ -0,0 +1,2 @@ +test_one "SMP=4 LOG=info" "expect_info_smp4_fifo.out" +rm -f $APP/*.o diff --git a/apps/c/pthread/sleep/expect_info_smp4_fifo.out b/apps/c/pthread/sleep/expect_info_smp4_fifo.out new file mode 100644 index 000000000..35227b22d --- /dev/null +++ b/apps/c/pthread/sleep/expect_info_smp4_fifo.out @@ -0,0 +1,83 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize platform devices... +Initialize scheduling... + use FIFO scheduler. +Initialize interrupt handlers... +Hello, main task! +main task sleep for 1\.[0-9]\+s + tick 0 +task 8 sleep 1 seconds (0) ... +task 9 sleep 2 seconds (0) ... +task 10 sleep 3 seconds (0) ... +task 11 sleep 4 seconds (0) ... +task 12 sleep 5 seconds (0) ... + tick 1 +task 8 actually sleep 1\.[0-9]\+ seconds (0) ... +task 8 sleep 1 seconds (1) ... + tick 2 + tick 3 +task 9 actually sleep 2\.[0-9]\+ seconds (0) ... +task 9 sleep 2 seconds (1) ... +task 8 actually sleep 1\.[0-9]\+ seconds (1) ... +task 8 sleep 1 seconds (2) ... + tick 4 + tick 5 +task 10 actually sleep 3\.[0-9]\+ seconds (0) ... +task 10 sleep 3 seconds (1) ... +task 8 actually sleep 1\.[0-9]\+ seconds (2) ... + tick 6 + tick 7 +task 11 actually sleep 4\.[0-9]\+ seconds (0) ... +task 11 sleep 4 seconds (1) ... +task 9 actually sleep 2\.[0-9]\+ seconds (1) ... +task 9 sleep 2 seconds (2) ... + tick 8 + tick 9 +task 12 actually sleep 5\.[0-9]\+ seconds (0) ... +task 12 sleep 5 seconds (1) ... + tick 10 + tick 11 +task 10 actually sleep 3\.[0-9]\+ seconds (1) ... +task 10 sleep 3 seconds (2) ... +task 9 actually sleep 2\.[0-9]\+ seconds (2) ... + tick 12 + tick 13 + tick 14 + tick 15 +task 11 actually sleep 4\.[0-9]\+ seconds (1) ... +task 11 sleep 4 seconds (2) ... + tick 16 + tick 17 +task 10 actually sleep 3\.[0-9]\+ seconds (2) ... + tick 18 + tick 19 +task 12 actually sleep 5\.[0-9]\+ seconds (1) ... +task 12 sleep 5 seconds (2) ... + tick 20 + tick 21 + tick 22 + tick 23 +task 11 actually sleep 4\.[0-9]\+ seconds (2) ... + tick 24 + tick 25 + tick 26 + tick 27 + tick 28 + tick 29 +task 12 actually sleep 5\.[0-9]\+ seconds (2) ... +(C)Sleep tests run OK! +Shutting down... diff --git a/apps/c/pthread/sleep/features.txt b/apps/c/pthread/sleep/features.txt new file mode 100644 index 000000000..efe7876d3 --- /dev/null +++ b/apps/c/pthread/sleep/features.txt @@ -0,0 +1,4 @@ +paging +alloc +multitask +irq diff --git a/apps/c/pthread/sleep/main.c b/apps/c/pthread/sleep/main.c new file mode 100644 index 000000000..2781e0dc3 --- /dev/null +++ b/apps/c/pthread/sleep/main.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include + +const int NUM_TASKS = 5; + +int parse_time(char *buf, int sec, int usec) +{ + int n = 0; + n += sprintf(buf, "%d.", sec); + n += sprintf(buf + n, "%d", usec / 100000 % 10); + n += sprintf(buf + n, "%d", usec / 10000 % 10); + n += sprintf(buf + n, "%d", usec / 1000 % 10); + n += sprintf(buf + n, "%d", usec / 100 % 10); + n += sprintf(buf + n, "%d", usec / 10 % 10); + n += sprintf(buf + n, "%d", usec % 10); + return n; +} + +void *tickfunc(void *arg) +{ + char buf[32]; + for (int i = 0; i < 30; i++) { + sprintf(buf, " tick %d", i); + puts(buf); + usleep(500000); + } + return NULL; +} + +void *tickfunc2(void *arg) +{ + pid_t task_id = getpid(); + char buf0[128]; + char buf1[128]; + + for (int j = 0; j < 3; j++) { + int sleep_sec = *(int *)arg + 1; + sprintf(buf0, "task %d sleep %d seconds (%d) ...", task_id, sleep_sec, j); + puts(buf0); + struct timespec before, later; + clock_gettime(0, &before); + sleep(sleep_sec); + clock_gettime(0, &later); + long sec = later.tv_sec - before.tv_sec; + long nsec = later.tv_nsec - before.tv_nsec; + if (nsec < 0) { + sec -= 1; + nsec += 1000000000; + } + int n = sprintf(buf1, "task %d actually sleep ", task_id); + n += parse_time(buf1 + n, (int)sec, (int)nsec / 1000); + sprintf(buf1 + n, " seconds (%d) ...", j); + puts(buf1); + } + return NULL; +} + +void main() +{ + puts("Hello, main task!"); + struct timespec before, later; + clock_gettime(0, &before); + sleep(1); + clock_gettime(0, &later); + long sec = later.tv_sec - before.tv_sec; + long nsec = later.tv_nsec - before.tv_nsec; + if (nsec < 0) { + sec -= 1; + nsec += 1000000000; + } + char buf[128] = {0}; + int n = sprintf(buf, "main task sleep for "); + n += parse_time(buf + n, (int)sec, (int)nsec / 1000); + sprintf(buf + n, "s"); + puts(buf); + + pthread_t t1; + pthread_create(&t1, NULL, tickfunc, NULL); + + pthread_t tasks[NUM_TASKS + 1]; + int sleep_times[NUM_TASKS]; + + for (int i = 0; i < NUM_TASKS; i++) { + sleep_times[i] = i; + } + + tasks[NUM_TASKS] = t1; + + for (int i = 0; i < NUM_TASKS; i++) { + pthread_t t; + pthread_create(&t, NULL, tickfunc2, (void *)&sleep_times[i]); + tasks[i] = t; + } + + for (int i = 0; i < NUM_TASKS + 1; i++) { + pthread_join(tasks[i], NULL); + } + + puts("(C)Sleep tests run OK!"); +} diff --git a/apps/c/pthread/sleep/test_cmd b/apps/c/pthread/sleep/test_cmd new file mode 100644 index 000000000..fa3dfed71 --- /dev/null +++ b/apps/c/pthread/sleep/test_cmd @@ -0,0 +1,2 @@ +test_one "SMP=4 LOG=info" "expect_info_smp4_fifo.out" +rm -f $APP/*.o diff --git a/apps/c/redis/.gitignore b/apps/c/redis/.gitignore new file mode 100644 index 000000000..7905e7add --- /dev/null +++ b/apps/c/redis/.gitignore @@ -0,0 +1 @@ +redis-* diff --git a/apps/c/redis/Makefile b/apps/c/redis/Makefile new file mode 100644 index 000000000..59d5315b8 --- /dev/null +++ b/apps/c/redis/Makefile @@ -0,0 +1,45 @@ +# Build for linux + +ARCH ?= x86_64 + +CC := $(ARCH)-linux-musl-gcc + +CFLAGS := +LDFLAGS := -static -no-pie + +redis-version := 7.0.12 +redis-dir := $(CURDIR)/redis-$(redis-version) +redis-objs := $(redis-dir)/src/redis-server.o +redis-bin := redis-server + +redis-build-args := \ + CC=$(CC) \ + CFLAGS="$(CFLAGS)" \ + USE_JEMALLOC=no \ + -j + +ifneq ($(V),) + redis-build-args += V=$(V) +endif + +all: build + +$(redis-dir): + @echo "Download redis source code" + wget https://github.com/redis/redis/archive/$(redis-version).tar.gz -P $(APP) + tar -zxvf $(APP)/$(redis-version).tar.gz -C $(APP) && rm -f $(APP)/$(redis-version).tar.gz + cd $(redis-dir) && git init && git add . + patch -p1 -N -d $(redis-dir) --no-backup-if-mismatch -r - < $(APP)/redis.patch + +build: $(redis-dir) + cd $(redis-dir) && $(MAKE) $(redis-build-args) + $(CC) $(LDFLAGS) -o $(redis-bin) $(redis-objs) + +run: + ./$(redis-bin) + +clean: + $(MAKE) -C $(redis-dir) distclean + rm -f $(redis-bin) + +.PHONY: all build clean diff --git a/apps/c/redis/README.md b/apps/c/redis/README.md new file mode 100644 index 000000000..f5c24416c --- /dev/null +++ b/apps/c/redis/README.md @@ -0,0 +1,333 @@ + +# How to run? +- Run: + - `make A=apps/c/redis/ LOG=error NET=y BLK=y ARCH=aarch64 SMP=4 run`(for aarch64) + - `make A=apps/c/redis/ LOG=error NET=y BLK=y ARCH=x86_64 SMP=4 run`(for x86_64) + +# How to test? +- Use `redis-cli -p 5555` to connect to redis-server, and enjoy ArceOS-Redis world! +- Use `redis-benchmark -p 5555` and other optional parameters to run the benchmark. + - Like: `redis-benchmark -p 5555 -n 5 -q -c 10`, this command issues 5 requests for each commands (like `set`, `get`, etc.), with 10 concurrency. + - `LRANGE_xxx` test may fail because of running out of memory(Follow notes 4, 5). + +# Notes +- Only test "aarch64". +- Use `SMP=xxx`. +- Must run `make clean`, this may be changed later. +- Enlarge memory size in `qemu.mk` and `qemu-virt-aarch64.toml` to 2G. +- Extend fd number to 4096, and comment out corresponding checks in `flatten_objects`. + +# Some test result +## AARCH64 +### SET and GET: +- Here test `SET` and `GET` throughput.(30 conns, 100k requests like `unikraft`) +- command: + - `redis-benchmark -p 5555 -n 100000 -q -t set -c 30` + - `redis-benchmark -p 5555 -n 100000 -q -t get -c 30` +- 0630 + +| Operation | Op number | Concurrency | Round | Result(request per seconds) | +|-|-|-|-|-| +| SET | 100K | 30 | 1 | 11921.79 | +| | | | 2 | 11873.66 | +| | | | 3 | 11499.54 | +| | | | 4 | 12001.92 | +| | | | 5 | 11419.44 | +| GET | 100K | 30 | 1 | 13002.21 | +| | | | 2 | 12642.23 | +| | | | 3 | 13674.28 | +| | | | 4 | 12987.01 | +| | | | 5 | 12853.47 | + +- 0710(Extend disk size) + +| Operation | Op number | Concurrency | Round | Result(request per seconds) | +|-|-|-|-|-| +| SET | 100K | 30 | 1 | 12740.48 | +| | | | 2 | 13150.97 | +| | | | 3 | 13147.52 | +| | | | 4 | 12898.23 | +| | | | 5 | 12918.23 | +| GET | 100K | 30 | 1 | 13253.81 | +| | | | 2 | 14332.81 | +| | | | 3 | 14600.67 | +| | | | 4 | 13974.29 | +| | | | 5 | 14005.60 | + +- 0714(Update net implementation, maximum: 2.9w(set)) + +| Operation | Op number | Concurrency | Round | Result(request per seconds) | +|-|-|-|-|-| +| SET | 100K | 30 | 1 | 25713.55 | +| | | | 2 | 25246.15 | +| | | | 3 | 24968.79 | +| | | | 4 | 25018.76 | +| | | | 5 | 25348.54 | +| GET | 100K | 30 | 1 | 27917.37 | +| | | | 2 | 28360.75 | +| | | | 3 | 27525.46 | +| | | | 4 | 27901.79 | +| | | | 5 | 27495.19 | + +### Other tests, one round +- Do other tests. +- Use `config set save ""` to avoid rapidly saving. +- Use `-c 30` + +- 0704 + +| Operation | Op number | Concurrency | Round | Result(request per seconds) | +|-|-|-|-|-| +| PING_INLINE | 100K | 30 | 1 | 12147.72 | +| INCR | 100K | 30 | 1 | 13097.58 | +| LPUSH | 100K | 30 | 1 | 12955.05 | +| RPUSH | 100K | 30 | 1 | 11339.15 | +| LPOP | 100K | 30 | 1 | 12611.93 | +| RPOP | 100K | 30 | 1 | 13106.16 | +| SADD | 100K | 30 | 1 | 12773.02 | +| HSET | 100K | 30 | 1 | 11531.37 | +| SPOP | 100K | 30 | 1 | 12918.23 | +| ZADD | 100K | 30 | 1 | 10462.44 | +| ZPOPMIN | 100K | 30 | 1 | 12817.23 | +| LRANGE_100 | 100K | 30 | 1 | 6462.45 | +| LRANGE_300 | 100K | 30 | 1 | 3318.84 | +| LRANGE_500 | 100K | 30 | 1 | 2522.13 | +| LRANGE_600 | 100K | 30 | 1 | 1877.30 | +| MSET | 100K | 30 | 1 | 8929.37 | + +- 0714 +``` +PING_INLINE: 28768.70 requests per second +PING_BULK: 31347.96 requests per second +SET: 23185.72 requests per second +GET: 25700.33 requests per second +INCR: 25746.65 requests per second +LPUSH: 20416.50 requests per second +RPUSH: 20868.12 requests per second +LPOP: 20370.75 requests per second +RPOP: 19956.10 requests per second +SADD: 25361.40 requests per second +HSET: 21431.63 requests per second +SPOP: 25438.82 requests per second +ZADD: 23820.87 requests per second +ZPOPMIN: 26954.18 requests per second +LPUSH (needed to benchmark LRANGE): 26385.22 requests per second +LRANGE_100 (first 100 elements): 23912.00 requests per second +LRANGE_300 (first 300 elements): 22665.46 requests per second +LRANGE_500 (first 450 elements): 23369.95 requests per second +LRANGE_600 (first 600 elements): 22256.84 requests per second +MSET (10 keys): 18460.40 requests per second +``` + +## X86_64 +### SET and GET +- command: + - `redis-benchmark -p 5555 -n 100000 -q -t set -c 30` + - `redis-benchmark -p 5555 -n 100000 -q -t get -c 30` + +- 0710 + +| Operation | Op number | Concurrency | Round | Result(request per seconds) | +|-|-|-|-|-| +| SET | 100K | 30 | 1 | 30931.02 | +| | | | 2 | 32258.07 | +| | | | 3 | 30571.69 | +| | | | 4 | 33344.45 | +| | | | 5 | 31655.59 | +| GET | 100K | 30 | 1 | 33523.30 | +| | | | 2 | 33134.53 | +| | | | 3 | 30450.67 | +| | | | 4 | 33178.50 | +| | | | 5 | 32268.47 | + +- 0714 + +| Operation | Op number | Concurrency | Round | Result(request per seconds) | +|-|-|-|-|-| +| SET | 100K | 30 | 1 | 105263.16 | +| | | | 2 | 105263.16 | +| | | | 3 | 103950.10 | +| | | | 4 | 107758.62 | +| | | | 5 | 105820.11 | +| GET | 100K | 30 | 1 | 103199.18 | +| | | | 2 | 104058.27 | +| | | | 3 | 99502.48 | +| | | | 4 | 106951.88 | +| | | | 5 | 105263.16 | + +### Other tests + +- 0710 + +| Operation | Op number | Concurrency | Round | Result(request per seconds) | +|-|-|-|-|-| +| PING_INLINE | 100K | 30 | 1 | 32992.41 | +| INCR | 100K | 30 | 1 | 32467.53 | +| LPUSH | 100K | 30 | 1 | 29815.14 | +| RPUSH | 100K | 30 | 1 | 30864.20 | +| LPOP | 100K | 30 | 1 | 34094.78 | +| RPOP | 100K | 30 | 1 | 31133.25 | +| SADD | 100K | 30 | 1 | 32948.93 | +| HSET | 100K | 30 | 1 | 31036.62 | +| SPOP | 100K | 30 | 1 | 32916.39 | +| ZADD | 100K | 30 | 1 | 30693.68 | +| ZPOPMIN | 100K | 30 | 1 | 31525.85 | +| LRANGE_100 | 100K | 30 | 1 | 22925.26 | +| LRANGE_300 | 100K | 30 | 1 | 7404.12 | +| LRANGE_500 | 100K | 30 | 1 | 9320.53 | +| LRANGE_600 | 100K | 30 | 1 | 7760.96 | +| MSET | 100K | 30 | 1 | 31269.54 | + +- 0714 + +``` +PING_INLINE: 111607.14 requests per second +PING_BULK: 102880.66 requests per second +SET: 80971.66 requests per second +GET: 103519.66 requests per second +INCR: 98425.20 requests per second +LPUSH: 70274.07 requests per second +RPUSH: 108108.11 requests per second +LPOP: 53561.86 requests per second +RPOP: 100200.40 requests per second +SADD: 62150.41 requests per second +HSET: 99009.90 requests per second +SPOP: 104712.05 requests per second +ZADD: 105263.16 requests per second +ZPOPMIN: 110497.24 requests per second +LPUSH (needed to benchmark LRANGE): 74682.60 requests per second +LRANGE_100 (first 100 elements): 62305.30 requests per second +LRANGE_300 (first 300 elements): 8822.23 requests per second +LRANGE_500 (first 450 elements): 22446.69 requests per second +LRANGE_600 (first 600 elements): 17280.11 requests per second +MSET (10 keys): 92081.03 requests per second +``` + +- Comparing to local Redis server +``` +PING_INLINE: 176056.33 requests per second +PING_BULK: 173611.12 requests per second +SET: 175131.36 requests per second +GET: 174825.17 requests per second +INCR: 177304.97 requests per second +LPUSH: 176678.45 requests per second +RPUSH: 176056.33 requests per second +LPOP: 178253.12 requests per second +RPOP: 176678.45 requests per second +SADD: 175746.92 requests per second +HSET: 176991.16 requests per second +SPOP: 176991.16 requests per second +ZADD: 177619.89 requests per second +ZPOPMIN: 176056.33 requests per second +LPUSH (needed to benchmark LRANGE): 178253.12 requests per second +LRANGE_100 (first 100 elements): 113895.21 requests per second +LRANGE_300 (first 300 elements): 50942.43 requests per second +LRANGE_500 (first 450 elements): 35186.49 requests per second +LRANGE_600 (first 600 elements): 28320.59 requests per second +MSET (10 keys): 183150.19 requests per second +``` + +## Unikraft +### AARCH64: +- `redis-benchmark -h 172.44.0.2 -q -n 100000 -c 30` +- Separately: + ``` + SET: 13814.06 requests per second + GET: 15297.54 requests per second + ``` +- The whole benchmark: + ``` + PING_INLINE: 14369.88 requests per second + PING_BULK: 13335.11 requests per second + SET: 13650.01 requests per second + GET: 12103.61 requests per second + INCR: 13395.85 requests per second + LPUSH: 10279.61 requests per second + RPUSH: 12536.04 requests per second + LPOP: 9541.07 requests per second + RPOP: 12540.76 requests per second + SADD: 11880.72 requests per second + HSET: 12318.30 requests per second + SPOP: 12235.41 requests per second + ZADD: 12130.03 requests per second + ZPOPMIN: 12223.45 requests per second + LPUSH (needed to benchmark LRANGE): 11125.95 requests per second + LRANGE_100 (first 100 elements): 6791.17 requests per second + LRANGE_300 (first 300 elements): 3772.30 requests per second + LRANGE_500 (first 450 elements): 2779.71 requests per second + LRANGE_600 (first 600 elements): 2230.80 requests per second + MSET (10 keys): 9215.74 requests per second + ``` +### X86_64 + +# Run ArceOS-Redis On PC + +## Notification + +- Comment out `spt_init()`(Already patched). +- It will be nicer to comment out `pthread_cond_wait` as well. + +## Compile and Run + +- `make A=apps/c/redis LOG=error PLATFORM=x86_64-pc-oslab SMP=4 FEATURES=driver-ixgbe,driver-ramdisk IP=10.2.2.2 GW=10.2.2.1` +- Copy `redis_x86_64-pc-oslab.elf` to `/boot`, then reboot. +- Enter `grub` then boot the PC by ArceOS Redis. +- Connect to ArceOS-Redis server by: + - `redis-cli -p 5555 -h 10.2.2.2` + - `redis-benchmark -p 5555 -h 10.2.2.2` + +## Test Result + +### Qemu + +- 0801 + +``` +PING_INLINE: 54171.18 requests per second +PING_BULK: 69156.30 requests per second +SET: 70274.07 requests per second +GET: 71479.62 requests per second +INCR: 65876.16 requests per second +LPUSH: 53850.30 requests per second +RPUSH: 53908.36 requests per second +LPOP: 67704.80 requests per second +RPOP: 69832.40 requests per second +SADD: 69444.45 requests per second +HSET: 69541.03 requests per second +SPOP: 69492.70 requests per second +ZADD: 53879.31 requests per second +ZPOPMIN: 53908.36 requests per second +LPUSH (needed to benchmark LRANGE): 37216.23 requests per second +LRANGE_100 (first 100 elements): 48123.20 requests per second +LRANGE_300 (first 300 elements): 7577.48 requests per second +LRANGE_500 (first 450 elements): 15288.18 requests per second +LRANGE_600 (first 600 elements): 12371.64 requests per second +MSET (10 keys): 54318.30 requests per second +``` + +### Real Machine + +- 0801 + +``` +PING_INLINE: 71377.59 requests per second +PING_BULK: 74571.22 requests per second +SET: 75642.96 requests per second +GET: 75872.54 requests per second +INCR: 76394.20 requests per second +LPUSH: 75815.01 requests per second +RPUSH: 75930.14 requests per second +LPOP: 75528.70 requests per second +RPOP: 75585.79 requests per second +SADD: 75815.01 requests per second +HSET: 75757.57 requests per second +SPOP: 75930.14 requests per second +ZADD: 75757.57 requests per second +ZPOPMIN: 75757.57 requests per second +LPUSH (needed to benchmark LRANGE): 75471.70 requests per second +LRANGE_100 (first 100 elements): 52438.39 requests per second +LRANGE_300 (first 300 elements): 677.74 requests per second +LRANGE_500 (first 450 elements): 16485.33 requests per second +LRANGE_600 (first 600 elements): 13159.63 requests per second +MSET (10 keys): 72780.20 requests per second +``` diff --git a/apps/c/redis/axbuild.mk b/apps/c/redis/axbuild.mk new file mode 100644 index 000000000..a6d99fdd9 --- /dev/null +++ b/apps/c/redis/axbuild.mk @@ -0,0 +1,34 @@ +redis-version := 7.0.12 +redis-dir := $(APP)/redis-$(redis-version) +redis-objs := redis-$(redis-version)/src/redis-server.o + +app-objs := $(redis-objs) + +CFLAGS += -Wno-format + +redis-build-args := \ + CC=$(CC) \ + CFLAGS="$(CFLAGS)" \ + USE_JEMALLOC=no \ + -j + +ifneq ($(V),) + redis-build-args += V=$(V) +endif + +$(redis-dir): + @echo "Download redis source code" + wget https://github.com/redis/redis/archive/$(redis-version).tar.gz -P $(APP) + tar -zxvf $(APP)/$(redis-version).tar.gz -C $(APP) && rm -f $(APP)/$(redis-version).tar.gz + cd $(redis-dir) && git init && git add . + patch -p1 -N -d $(redis-dir) --no-backup-if-mismatch -r - < $(APP)/redis.patch + +$(APP)/$(redis-objs): build_redis + +build_redis: $(redis-dir) + cd $(redis-dir) && $(MAKE) $(redis-build-args) + +clean_c:: + $(MAKE) -C $(redis-dir) distclean + +.PHONY: build_redis clean_c diff --git a/apps/c/redis/features.txt b/apps/c/redis/features.txt new file mode 100644 index 000000000..8422cb83c --- /dev/null +++ b/apps/c/redis/features.txt @@ -0,0 +1,9 @@ +alloc +paging +fp_simd +irq +multitask +fs +net +pipe +epoll diff --git a/apps/c/redis/redis.patch b/apps/c/redis/redis.patch new file mode 100644 index 000000000..6f4e9418b --- /dev/null +++ b/apps/c/redis/redis.patch @@ -0,0 +1,102 @@ +diff --git a/src/Makefile b/src/Makefile +index e4f7d90..1b41e94 100644 +--- a/src/Makefile ++++ b/src/Makefile +@@ -16,7 +16,7 @@ release_hdr := $(shell sh -c './mkreleasehdr.sh') + uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') + uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not') + OPTIMIZATION?=-O2 +-DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram ++DEPENDENCY_TARGETS=hiredis lua hdr_histogram + NODEPS:=clean distclean + + # Default settings +@@ -37,14 +37,7 @@ OPT=$(OPTIMIZATION) + # Detect if the compiler supports C11 _Atomic. + # NUMBER_SIGN_CHAR is a workaround to support both GNU Make 4.3 and older versions. + NUMBER_SIGN_CHAR := \# +-C11_ATOMIC := $(shell sh -c 'echo "$(NUMBER_SIGN_CHAR)include " > foo.c; \ +- $(CC) -std=c11 -c foo.c -o foo.o > /dev/null 2>&1; \ +- if [ -f foo.o ]; then echo "yes"; rm foo.o; fi; rm foo.c') +-ifeq ($(C11_ATOMIC),yes) +- STD+=-std=c11 +-else +- STD+=-std=c99 +-endif ++STD+=-std=c99 + + PREFIX?=/usr/local + INSTALL_BIN=$(PREFIX)/bin +@@ -316,6 +309,7 @@ endif + + REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX) + REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX) ++REDIS_SERVER_LIB=redis-server.o + REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o tracking.o connection.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o + REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX) + REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o redisassert.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o +@@ -323,9 +317,9 @@ REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX) + REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o redisassert.o release.o crcspeed.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o + REDIS_CHECK_RDB_NAME=redis-check-rdb$(PROG_SUFFIX) + REDIS_CHECK_AOF_NAME=redis-check-aof$(PROG_SUFFIX) +-ALL_SOURCES=$(sort $(patsubst %.o,%.c,$(REDIS_SERVER_OBJ) $(REDIS_CLI_OBJ) $(REDIS_BENCHMARK_OBJ))) ++ALL_SOURCES=$(sort $(patsubst %.o,%.c,$(REDIS_SERVER_OBJ))) + +-all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) ++all: $(REDIS_SERVER_LIB) + @echo "" + @echo "Hint: It's a good idea to run 'make test' ;)" + @echo "" +@@ -373,6 +367,10 @@ endif + $(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ) + $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a $(FINAL_LIBS) + ++# redis-server.o ++$(REDIS_SERVER_LIB): $(REDIS_SERVER_OBJ) ++ $(REDIS_LD) -r -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ++ + # redis-sentinel + $(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME) + $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) +@@ -410,7 +408,7 @@ commands.c: commands/*.json ../utils/generate-command-code.py + endif + + clean: +- rm -rf $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep ++ rm -rf $(REDIS_SERVER_NAME) $(REDIS_SERVER_LIB) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_RDB_NAME) $(REDIS_CHECK_AOF_NAME) *.o *.gcda *.gcno *.gcov redis.info lcov-html Makefile.dep + rm -f $(DEP) + + .PHONY: clean +diff --git a/src/server.c b/src/server.c +index b170cbb..f5dfde0 100644 +--- a/src/server.c ++++ b/src/server.c +@@ -6854,6 +6854,19 @@ redisTestProc *getTestProcByName(const char *name) { + #endif + + int main(int argc, char **argv) { ++ const char *cmdline = "./redis-server " ++ "--bind 0.0.0.0 " ++ "--port 5555 " ++ "--save \"\" " ++ "--appendonly no " ++ "--protected-mode no " ++#if defined (__arm64__) ++ "--ignore-warnings ARM64-COW-BUG " ++#endif ++ ; ++ printf("Run Redis with: %s\n", cmdline); ++ argv = sdssplitargs(cmdline, &argc); ++ + struct timeval tv; + int j; + char config_from_stdin = 0; +@@ -6900,7 +6913,7 @@ int main(int argc, char **argv) { + + /* We need to initialize our libraries, and the server configuration. */ + #ifdef INIT_SETPROCTITLE_REPLACEMENT +- spt_init(argc, argv); ++ // spt_init(argc, argv); // environment variables is not supported + #endif + setlocale(LC_COLLATE,""); + tzset(); /* Populates 'timezone' global. */ diff --git a/apps/c/sqlite3/.gitignore b/apps/c/sqlite3/.gitignore new file mode 100644 index 000000000..c69a055ba --- /dev/null +++ b/apps/c/sqlite3/.gitignore @@ -0,0 +1 @@ +sqlite-* diff --git a/apps/c/sqlite3/Makefile b/apps/c/sqlite3/Makefile new file mode 100644 index 000000000..1404e3de8 --- /dev/null +++ b/apps/c/sqlite3/Makefile @@ -0,0 +1,13 @@ +AX_ROOT := /home/oslab/OS/arceos + +all: + $(MAKE) -C $(AX_ROOT) A=$(PWD) + +run: + $(MAKE) -C $(AX_ROOT) A=$(PWD) run + +clean: + rm -rf *.o + $(MAKE) -C $(AX_ROOT) A=$(PWD) clean + +.PHONY: all run clean diff --git a/apps/c/sqlite3/axbuild.mk b/apps/c/sqlite3/axbuild.mk new file mode 100644 index 000000000..8894ebc20 --- /dev/null +++ b/apps/c/sqlite3/axbuild.mk @@ -0,0 +1,14 @@ +sqlite3_pkg := sqlite-amalgamation-3410100 +sqlite3_dir := $(APP)/$(sqlite3_pkg) +APP_CFLAGS := -I$(sqlite3_dir) -w \ + -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_FLOATING_POINT -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_DEBUG + +app-objs := main.o $(sqlite3_pkg)/sqlite3.o + +$(APP)/main.o: $(sqlite3_dir)/sqlite3.c + +# Download sqlite source code +$(sqlite3_dir)/sqlite3.c: + echo "Download sqlite source code" + wget https://sqlite.org/2023/$(sqlite3_pkg).zip -P $(APP) + unzip $(APP)/$(sqlite3_pkg).zip -d $(APP) && rm -f $(APP)/$(sqlite3_pkg).zip diff --git a/apps/c/sqlite3/expect_info.out b/apps/c/sqlite3/expect_info.out new file mode 100644 index 000000000..66815dbf7 --- /dev/null +++ b/apps/c/sqlite3/expect_info.out @@ -0,0 +1,80 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize platform devices... +Initialize device drivers... +registered a new Block device at .\+: "virtio-blk" +Initialize filesystems... + use block device 0: "virtio-blk" +Primary CPU 0 init OK. +sqlite version: 3.41.1 +sqlite open memory status 0 +======== init user table ======== +sqlite exec: + create table user(id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT,password TEXT) +======== insert user 1, 2, 3 into user table ======== +sqlite exec: + insert into user (username, password) VALUES ('memory_1', 'password1'), ('memory_2', 'password2'), ('memory_3', 'password3') +======== select all ======== +sqlite query: + select \* from user +id = 1 +username = memory_1 +password = password1 + +id = 2 +username = memory_2 +password = password2 + +id = 3 +username = memory_3 +password = password3 + +======== select id = 2 ======== +sqlite query: + select \* from user where id = 2 +id = 2 +username = memory_2 +password = password2 + +sqlite open /file.sqlite status 0 +======== init user table ======== +sqlite exec: + create table user(id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT,password TEXT) +======== insert user 1, 2, 3 into user table ======== +sqlite exec: + insert into user (username, password) VALUES ('file_1', 'password1'), ('file_2', 'password2'), ('file_3', 'password3') +======== select all ======== +sqlite query: + select \* from user +id = 1 +username = file_1 +password = password1 + +id = 2 +username = file_2 +password = password2 + +id = 3 +username = file_3 +password = password3 + +======== select id = 2 ======== +sqlite query: + select \* from user where id = 2 +id = 2 +username = file_2 +password = password2 +Shutting down... diff --git a/apps/c/sqlite3/expect_info_again.out b/apps/c/sqlite3/expect_info_again.out new file mode 100644 index 000000000..7532d63ef --- /dev/null +++ b/apps/c/sqlite3/expect_info_again.out @@ -0,0 +1,92 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize platform devices... +Initialize device drivers... +registered a new Block device at .\+: "virtio-blk" +Initialize filesystems... + use block device 0: "virtio-blk" +Primary CPU 0 init OK. +sqlite version: 3.41.1 +sqlite open memory status 0 +======== init user table ======== +sqlite exec: + create table user(id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT,password TEXT) +======== insert user 1, 2, 3 into user table ======== +sqlite exec: + insert into user (username, password) VALUES ('memory_1', 'password1'), ('memory_2', 'password2'), ('memory_3', 'password3') +======== select all ======== +sqlite query: + select \* from user +id = 1 +username = memory_1 +password = password1 + +id = 2 +username = memory_2 +password = password2 + +id = 3 +username = memory_3 +password = password3 + +======== select id = 2 ======== +sqlite query: + select \* from user where id = 2 +id = 2 +username = memory_2 +password = password2 + +sqlite open /file.sqlite status 0 +======== init user table ======== +sqlite exec: + create table user(id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT,password TEXT) +======== insert user 1, 2, 3 into user table ======== +sqlite exec: + insert into user (username, password) VALUES ('file_1', 'password1'), ('file_2', 'password2'), ('file_3', 'password3') +======== select all ======== +sqlite query: + select \* from user +id = 1 +username = file_1 +password = password1 + +id = 2 +username = file_2 +password = password2 + +id = 3 +username = file_3 +password = password3 + +id = 4 +username = file_1 +password = password1 + +id = 5 +username = file_2 +password = password2 + +id = 6 +username = file_3 +password = password3 + +======== select id = 2 ======== +sqlite query: + select \* from user where id = 2 +id = 2 +username = file_2 +password = password2 +Shutting down... diff --git a/apps/c/sqlite3/expect_info_ramdisk.out b/apps/c/sqlite3/expect_info_ramdisk.out new file mode 100644 index 000000000..02b206367 --- /dev/null +++ b/apps/c/sqlite3/expect_info_ramdisk.out @@ -0,0 +1,81 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize platform devices... +Initialize device drivers... +device model: static +registered a new Block device: "ramdisk" +Initialize filesystems... + use block device 0: "ramdisk" +Primary CPU 0 init OK. +sqlite version: 3.41.1 +sqlite open memory status 0 +======== init user table ======== +sqlite exec: + create table user(id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT,password TEXT) +======== insert user 1, 2, 3 into user table ======== +sqlite exec: + insert into user (username, password) VALUES ('memory_1', 'password1'), ('memory_2', 'password2'), ('memory_3', 'password3') +======== select all ======== +sqlite query: + select \* from user +id = 1 +username = memory_1 +password = password1 + +id = 2 +username = memory_2 +password = password2 + +id = 3 +username = memory_3 +password = password3 + +======== select id = 2 ======== +sqlite query: + select \* from user where id = 2 +id = 2 +username = memory_2 +password = password2 + +sqlite open /file.sqlite status 0 +======== init user table ======== +sqlite exec: + create table user(id INTEGER PRIMARY KEY AUTOINCREMENT,username TEXT,password TEXT) +======== insert user 1, 2, 3 into user table ======== +sqlite exec: + insert into user (username, password) VALUES ('file_1', 'password1'), ('file_2', 'password2'), ('file_3', 'password3') +======== select all ======== +sqlite query: + select \* from user +id = 1 +username = file_1 +password = password1 + +id = 2 +username = file_2 +password = password2 + +id = 3 +username = file_3 +password = password3 + +======== select id = 2 ======== +sqlite query: + select \* from user where id = 2 +id = 2 +username = file_2 +password = password2 +Shutting down... diff --git a/apps/c/sqlite3/features.txt b/apps/c/sqlite3/features.txt new file mode 100644 index 000000000..e1814d00a --- /dev/null +++ b/apps/c/sqlite3/features.txt @@ -0,0 +1,4 @@ +fp_simd +alloc +paging +fs diff --git a/apps/c/sqlite3/main.c b/apps/c/sqlite3/main.c new file mode 100644 index 000000000..49a9768e5 --- /dev/null +++ b/apps/c/sqlite3/main.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include + +int callback(void *NotUsed, int argc, char **argv, char **azColName) +{ + NotUsed = NULL; + + for (int i = 0; i < argc; ++i) { + printf("%s = %s\n", azColName[i], (argv[i] ? argv[i] : "NULL")); + } + + printf("\n"); + + return 0; +} + +void exec(sqlite3 *db, char *sql) +{ + printf("sqlite exec:\n %s\n", sql); + char *errmsg = NULL; + int rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg); + if (rc != SQLITE_OK) { + printf("sqlite exec error: %s\n", errmsg); + } +} + +void query(sqlite3 *db, char *sql) +{ + printf("sqlite query:\n %s\n", sql); + char *errmsg = NULL; + int rc = sqlite3_exec(db, sql, callback, NULL, &errmsg); + + if (rc != SQLITE_OK) { + printf("%s\n", errmsg); + } +} + +void query_test(sqlite3 *db, const char *args) +{ + puts("======== init user table ========"); + exec(db, "create table user(" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "username TEXT," + "password TEXT" + ")"); + + puts("======== insert user 1, 2, 3 into user table ========"); + + char cmd[256] = {0}; + sprintf(cmd, + "insert into user (username, password) VALUES ('%s_1', 'password1'), ('%s_2', " + "'password2'), ('%s_3', 'password3')", + args, args, args); + exec(db, cmd); + + puts("======== select all ========"); + query(db, "select * from user"); + + puts("======== select id = 2 ========"); + query(db, "select * from user where id = 2"); +} + +void memory() +{ + sqlite3 *db; + int ret = sqlite3_open(":memory:", &db); + printf("sqlite open memory status %d \n", ret); + + query_test(db, "memory"); +} + +void file() +{ + sqlite3 *db; + int ret = sqlite3_open("file.sqlite", &db); + printf("sqlite open /file.sqlite status %d \n", ret); + + if (ret != 0) { + printf("sqlite open error"); + return; + } + + query_test(db, "file"); + sqlite3_close(db); +} + +int main() +{ + printf("sqlite version: %s\n", sqlite3_libversion()); + + memory(); + file(); + return 0; +} diff --git a/apps/c/sqlite3/test_cmd b/apps/c/sqlite3/test_cmd new file mode 100644 index 000000000..449cfe1a6 --- /dev/null +++ b/apps/c/sqlite3/test_cmd @@ -0,0 +1,4 @@ +test_one "LOG=info BLK=y" "expect_info.out" +test_one "LOG=info BLK=y" "expect_info_again.out" +test_one "LOG=info BLK=y FEATURES=driver-ramdisk" "expect_info_ramdisk.out" +rm -f $APP/*.o diff --git a/apps/c/udpserver/axbuild.mk b/apps/c/udpserver/axbuild.mk new file mode 100644 index 000000000..38ca5213a --- /dev/null +++ b/apps/c/udpserver/axbuild.mk @@ -0,0 +1 @@ +app-objs := udpserver.o diff --git a/apps/c/udpserver/features.txt b/apps/c/udpserver/features.txt new file mode 100644 index 000000000..25ca64f38 --- /dev/null +++ b/apps/c/udpserver/features.txt @@ -0,0 +1,3 @@ +alloc +paging +net diff --git a/apps/c/udpserver/udpserver.c b/apps/c/udpserver/udpserver.c new file mode 100644 index 000000000..cff15139c --- /dev/null +++ b/apps/c/udpserver/udpserver.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include + +const char res_suffix[11] = "_response\n"; + +int main() +{ + puts("Hello, ArceOS C UDP server!"); + struct sockaddr_in local, remote; + int addr_len = sizeof(remote); + local.sin_family = AF_INET; + if (inet_pton(AF_INET, "0.0.0.0", &(local.sin_addr)) != 1) { + perror("inet_pton() error"); + return -1; + } + local.sin_port = htons(5555); + int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == -1) { + perror("socket() error"); + return -1; + } + if (bind(sock, (struct sockaddr *)&local, sizeof(local)) != 0) { + perror("bind() error"); + return -1; + } + puts("listen on: 0.0.0.0:5555"); + char buf[1024] = {}; + for (;;) { + ssize_t l = + recvfrom(sock, buf, 1024, 0, (struct sockaddr *)&remote, (socklen_t *)&addr_len); + if (l == -1) { + perror("recvfrom() error"); + return -1; + } + uint8_t *addr = (uint8_t *)&(remote.sin_addr); + printf("recv: %d Bytes from %d.%d.%d.%d:%d\n", l, addr[0], addr[1], addr[2], addr[3], + ntohs(remote.sin_port)); + buf[l] = '\0'; + printf("%s\n", buf); + if (l > 1024 - 10) { + puts("received message too long"); + return 0; + } + strncpy(buf + l - 1, res_suffix, 10); + if (sendto(sock, buf, l + 10, 0, (struct sockaddr *)&remote, addr_len) == -1) { + perror("sendto() error"); + return -1; + } + } + return 0; +} diff --git a/apps/display/Cargo.toml b/apps/display/Cargo.toml new file mode 100644 index 000000000..4cfc2ba43 --- /dev/null +++ b/apps/display/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "arceos-display" +version = "0.1.0" +edition = "2021" +authors = ["Shiping Yuan "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axstd = { path = "../../ulib/axstd", features = ["display"], optional = true } +embedded-graphics = "0.8" diff --git a/apps/display/src/display.rs b/apps/display/src/display.rs new file mode 100644 index 000000000..01601f198 --- /dev/null +++ b/apps/display/src/display.rs @@ -0,0 +1,51 @@ +use embedded_graphics::pixelcolor::Rgb888; +use embedded_graphics::prelude::{RgbColor, Size}; +use embedded_graphics::{draw_target::DrawTarget, prelude::OriginDimensions}; + +use std::os::arceos::api::display as api; + +pub struct Display { + size: Size, + fb: &'static mut [u8], +} + +impl Display { + pub fn new() -> Self { + let info = api::ax_framebuffer_info(); + let fb = + unsafe { core::slice::from_raw_parts_mut(info.fb_base_vaddr as *mut u8, info.fb_size) }; + let size = Size::new(info.width, info.height); + Self { size, fb } + } + + pub fn flush(&self) { + api::ax_framebuffer_flush(); + } +} + +impl OriginDimensions for Display { + fn size(&self) -> Size { + self.size + } +} + +impl DrawTarget for Display { + type Color = Rgb888; + type Error = core::convert::Infallible; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + pixels.into_iter().for_each(|px| { + let idx = (px.0.y * self.size.width as i32 + px.0.x) as usize * 4; + if idx + 2 >= self.fb.len() { + return; + } + self.fb[idx] = px.1.b(); + self.fb[idx + 1] = px.1.g(); + self.fb[idx + 2] = px.1.r(); + }); + Ok(()) + } +} diff --git a/apps/display/src/main.rs b/apps/display/src/main.rs new file mode 100644 index 000000000..8605e818f --- /dev/null +++ b/apps/display/src/main.rs @@ -0,0 +1,86 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[cfg(feature = "axstd")] +extern crate axstd as std; + +mod display; + +use self::display::Display; +use embedded_graphics::{ + mono_font::{ascii::FONT_10X20, MonoTextStyle}, + pixelcolor::Rgb888, + prelude::*, + primitives::{Circle, PrimitiveStyle, Rectangle, Triangle}, + text::{Alignment, Text}, +}; + +const INIT_X: i32 = 80; +const INIT_Y: i32 = 400; +const RECT_SIZE: u32 = 150; + +pub struct DrawingBoard { + disp: Display, + latest_pos: Point, +} + +impl Default for DrawingBoard { + fn default() -> Self { + Self::new() + } +} + +impl DrawingBoard { + pub fn new() -> Self { + Self { + disp: Display::new(), + latest_pos: Point::new(INIT_X, INIT_Y), + } + } + + fn paint(&mut self) { + Rectangle::with_center(self.latest_pos, Size::new(RECT_SIZE, RECT_SIZE)) + .into_styled(PrimitiveStyle::with_stroke(Rgb888::RED, 10)) + .draw(&mut self.disp) + .ok(); + Circle::new(self.latest_pos + Point::new(-70, -300), 150) + .into_styled(PrimitiveStyle::with_fill(Rgb888::BLUE)) + .draw(&mut self.disp) + .ok(); + Triangle::new( + self.latest_pos + Point::new(0, 150), + self.latest_pos + Point::new(80, 200), + self.latest_pos + Point::new(-120, 300), + ) + .into_styled(PrimitiveStyle::with_stroke(Rgb888::GREEN, 10)) + .draw(&mut self.disp) + .ok(); + let text = "ArceOS"; + Text::with_alignment( + text, + self.latest_pos + Point::new(0, 300), + MonoTextStyle::new(&FONT_10X20, Rgb888::YELLOW), + Alignment::Center, + ) + .draw(&mut self.disp) + .ok(); + } +} + +fn test_gpu() { + let mut board = DrawingBoard::new(); + board.disp.clear(Rgb888::BLACK).unwrap(); + for _ in 0..5 { + board.latest_pos.x += RECT_SIZE as i32 + 20; + board.paint(); + board.disp.flush(); + } +} + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() -> ! { + test_gpu(); + loop { + core::hint::spin_loop(); + } +} diff --git a/apps/exception/Cargo.toml b/apps/exception/Cargo.toml new file mode 100644 index 000000000..1be50f0d1 --- /dev/null +++ b/apps/exception/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "arceos-exception" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axstd = { path = "../../ulib/axstd", optional = true } diff --git a/apps/exception/expect_debug_aarch64.out b/apps/exception/expect_debug_aarch64.out new file mode 100644 index 000000000..4652c0c04 --- /dev/null +++ b/apps/exception/expect_debug_aarch64.out @@ -0,0 +1,19 @@ +smp = 1 +build_mode = release +log_level = debug + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize platform devices... +Primary CPU 0 init OK. +Running exception tests... +BRK #0x0 @ 0x[0-9a-f]\{16\} +Exception tests run OK! +Shutting down... diff --git a/apps/exception/expect_debug_riscv64.out b/apps/exception/expect_debug_riscv64.out new file mode 100644 index 000000000..ea1508c5d --- /dev/null +++ b/apps/exception/expect_debug_riscv64.out @@ -0,0 +1,19 @@ +smp = 1 +build_mode = release +log_level = debug + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize platform devices... +Primary CPU 0 init OK. +Running exception tests... +Exception(Breakpoint) @ 0x[0-9a-f]\{16\} +Exception tests run OK! +Shutting down... diff --git a/apps/exception/expect_debug_x86_64.out b/apps/exception/expect_debug_x86_64.out new file mode 100644 index 000000000..0cdbde466 --- /dev/null +++ b/apps/exception/expect_debug_x86_64.out @@ -0,0 +1,19 @@ +smp = 1 +build_mode = release +log_level = debug + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize platform devices... +Primary CPU 0 init OK. +Running exception tests... +#BP @ 0x[0-9a-f]\{16\} +Exception tests run OK! +Shutting down... diff --git a/apps/exception/src/main.rs b/apps/exception/src/main.rs new file mode 100644 index 000000000..e196cabd0 --- /dev/null +++ b/apps/exception/src/main.rs @@ -0,0 +1,26 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[cfg(feature = "axstd")] +extern crate axstd as std; + +use std::arch::asm; +use std::println; + +fn raise_break_exception() { + unsafe { + #[cfg(target_arch = "x86_64")] + asm!("int3"); + #[cfg(target_arch = "aarch64")] + asm!("brk #0"); + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + asm!("ebreak"); + } +} + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + println!("Running exception tests..."); + raise_break_exception(); + println!("Exception tests run OK!"); +} diff --git a/apps/exception/test_cmd b/apps/exception/test_cmd new file mode 100644 index 000000000..746e8ac8a --- /dev/null +++ b/apps/exception/test_cmd @@ -0,0 +1 @@ +test_one "LOG=debug" "expect_debug_${ARCH}.out" diff --git a/apps/fs/shell/Cargo.toml b/apps/fs/shell/Cargo.toml new file mode 100644 index 000000000..bd99c1d11 --- /dev/null +++ b/apps/fs/shell/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "arceos-shell" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +use-ramfs = ["axstd/myfs", "dep:axfs_vfs", "dep:axfs_ramfs", "dep:crate_interface"] +default = [] + +[dependencies] +axfs_vfs = { path = "../../../crates/axfs_vfs", optional = true } +axfs_ramfs = { path = "../../../crates/axfs_ramfs", optional = true } +crate_interface = { path = "../../../crates/crate_interface", optional = true } +axstd = { path = "../../../ulib/axstd", features = ["alloc", "fs"], optional = true } diff --git a/apps/fs/shell/src/cmd.rs b/apps/fs/shell/src/cmd.rs new file mode 100644 index 000000000..23f087fe3 --- /dev/null +++ b/apps/fs/shell/src/cmd.rs @@ -0,0 +1,293 @@ +use std::fs::{self, File, FileType}; +use std::io::{self, prelude::*}; +use std::{string::String, vec::Vec}; + +#[cfg(all(not(feature = "axstd"), unix))] +use std::os::unix::fs::{FileTypeExt, PermissionsExt}; + +macro_rules! print_err { + ($cmd: literal, $msg: expr) => { + println!("{}: {}", $cmd, $msg); + }; + ($cmd: literal, $arg: expr, $err: expr) => { + println!("{}: {}: {}", $cmd, $arg, $err); + }; +} + +type CmdHandler = fn(&str); + +const CMD_TABLE: &[(&str, CmdHandler)] = &[ + ("cat", do_cat), + ("cd", do_cd), + ("echo", do_echo), + ("exit", do_exit), + ("help", do_help), + ("ls", do_ls), + ("mkdir", do_mkdir), + ("pwd", do_pwd), + ("rm", do_rm), + ("uname", do_uname), +]; + +fn file_type_to_char(ty: FileType) -> char { + if ty.is_char_device() { + 'c' + } else if ty.is_block_device() { + 'b' + } else if ty.is_socket() { + 's' + } else if ty.is_fifo() { + 'p' + } else if ty.is_symlink() { + 'l' + } else if ty.is_dir() { + 'd' + } else if ty.is_file() { + '-' + } else { + '?' + } +} + +#[rustfmt::skip] +const fn file_perm_to_rwx(mode: u32) -> [u8; 9] { + let mut perm = [b'-'; 9]; + macro_rules! set { + ($bit:literal, $rwx:literal) => { + if mode & (1 << $bit) != 0 { + perm[8 - $bit] = $rwx + } + }; + } + + set!(2, b'r'); set!(1, b'w'); set!(0, b'x'); + set!(5, b'r'); set!(4, b'w'); set!(3, b'x'); + set!(8, b'r'); set!(7, b'w'); set!(6, b'x'); + perm +} + +fn do_ls(args: &str) { + let current_dir = std::env::current_dir().unwrap(); + let args = if args.is_empty() { + path_to_str!(current_dir) + } else { + args + }; + let name_count = args.split_whitespace().count(); + + fn show_entry_info(path: &str, entry: &str) -> io::Result<()> { + let metadata = fs::metadata(path)?; + let size = metadata.len(); + let file_type = metadata.file_type(); + let file_type_char = file_type_to_char(file_type); + let rwx = file_perm_to_rwx(metadata.permissions().mode()); + let rwx = unsafe { core::str::from_utf8_unchecked(&rwx) }; + println!("{}{} {:>8} {}", file_type_char, rwx, size, entry); + Ok(()) + } + + fn list_one(name: &str, print_name: bool) -> io::Result<()> { + let is_dir = fs::metadata(name)?.is_dir(); + if !is_dir { + return show_entry_info(name, name); + } + + if print_name { + println!("{}:", name); + } + let mut entries = fs::read_dir(name)? + .filter_map(|e| e.ok()) + .map(|e| e.file_name()) + .collect::>(); + entries.sort(); + + for entry in entries { + let entry = path_to_str!(entry); + let path = String::from(name) + "/" + entry; + if let Err(e) = show_entry_info(&path, entry) { + print_err!("ls", path, e); + } + } + Ok(()) + } + + for (i, name) in args.split_whitespace().enumerate() { + if i > 0 { + println!(); + } + if let Err(e) = list_one(name, name_count > 1) { + print_err!("ls", name, e); + } + } +} + +fn do_cat(args: &str) { + if args.is_empty() { + print_err!("cat", "no file specified"); + return; + } + + fn cat_one(fname: &str) -> io::Result<()> { + let mut buf = [0; 1024]; + let mut file = File::open(fname)?; + loop { + let n = file.read(&mut buf)?; + if n > 0 { + io::stdout().write_all(&buf[..n])?; + } else { + return Ok(()); + } + } + } + + for fname in args.split_whitespace() { + if let Err(e) = cat_one(fname) { + print_err!("cat", fname, e); + } + } +} + +fn do_echo(args: &str) { + fn echo_file(fname: &str, text_list: &[&str]) -> io::Result<()> { + let mut file = File::create(fname)?; + for text in text_list { + file.write_all(text.as_bytes())?; + } + Ok(()) + } + + if let Some(pos) = args.rfind('>') { + let text_before = args[..pos].trim(); + let (fname, text_after) = split_whitespace(&args[pos + 1..]); + if fname.is_empty() { + print_err!("echo", "no file specified"); + return; + }; + + let text_list = [ + text_before, + if !text_after.is_empty() { " " } else { "" }, + text_after, + "\n", + ]; + if let Err(e) = echo_file(fname, &text_list) { + print_err!("echo", fname, e); + } + } else { + println!("{}", args) + } +} + +fn do_mkdir(args: &str) { + if args.is_empty() { + print_err!("mkdir", "missing operand"); + return; + } + + fn mkdir_one(path: &str) -> io::Result<()> { + fs::create_dir(path) + } + + for path in args.split_whitespace() { + if let Err(e) = mkdir_one(path) { + print_err!("mkdir", format_args!("cannot create directory '{path}'"), e); + } + } +} + +fn do_rm(args: &str) { + if args.is_empty() { + print_err!("rm", "missing operand"); + return; + } + let mut rm_dir = false; + for arg in args.split_whitespace() { + if arg == "-d" { + rm_dir = true; + } + } + + fn rm_one(path: &str, rm_dir: bool) -> io::Result<()> { + if rm_dir && fs::metadata(path)?.is_dir() { + fs::remove_dir(path) + } else { + fs::remove_file(path) + } + } + + for path in args.split_whitespace() { + if path == "-d" { + continue; + } + if let Err(e) = rm_one(path, rm_dir) { + print_err!("rm", format_args!("cannot remove '{path}'"), e); + } + } +} + +fn do_cd(mut args: &str) { + if args.is_empty() { + args = "/"; + } + if !args.contains(char::is_whitespace) { + if let Err(e) = std::env::set_current_dir(args) { + print_err!("cd", args, e); + } + } else { + print_err!("cd", "too many arguments"); + } +} + +fn do_pwd(_args: &str) { + let pwd = std::env::current_dir().unwrap(); + println!("{}", path_to_str!(pwd)); +} + +fn do_uname(_args: &str) { + let arch = option_env!("AX_ARCH").unwrap_or(""); + let platform = option_env!("AX_PLATFORM").unwrap_or(""); + let smp = match option_env!("AX_SMP") { + None | Some("1") => "", + _ => " SMP", + }; + let version = option_env!("CARGO_PKG_VERSION").unwrap_or("0.1.0"); + println!( + "ArceOS {ver}{smp} {arch} {plat}", + ver = version, + smp = smp, + arch = arch, + plat = platform, + ); +} + +fn do_help(_args: &str) { + println!("Available commands:"); + for (name, _) in CMD_TABLE { + println!(" {}", name); + } +} + +fn do_exit(_args: &str) { + println!("Bye~"); + std::process::exit(0); +} + +pub fn run_cmd(line: &[u8]) { + let line_str = unsafe { core::str::from_utf8_unchecked(line) }; + let (cmd, args) = split_whitespace(line_str); + if !cmd.is_empty() { + for (name, func) in CMD_TABLE { + if cmd == *name { + func(args); + return; + } + } + println!("{}: command not found", cmd); + } +} + +fn split_whitespace(str: &str) -> (&str, &str) { + let str = str.trim(); + str.find(char::is_whitespace) + .map_or((str, ""), |n| (&str[..n], str[n + 1..].trim())) +} diff --git a/apps/fs/shell/src/main.rs b/apps/fs/shell/src/main.rs new file mode 100644 index 000000000..1e2875aad --- /dev/null +++ b/apps/fs/shell/src/main.rs @@ -0,0 +1,85 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[macro_use] +#[cfg(feature = "axstd")] +extern crate axstd as std; + +macro_rules! path_to_str { + ($path:expr) => {{ + #[cfg(not(feature = "axstd"))] + { + $path.to_str().unwrap() // Path/OsString -> &str + } + #[cfg(feature = "axstd")] + { + $path.as_str() // String -> &str + } + }}; +} + +mod cmd; + +#[cfg(feature = "use-ramfs")] +mod ramfs; + +use std::io::prelude::*; + +const LF: u8 = b'\n'; +const CR: u8 = b'\r'; +const DL: u8 = b'\x7f'; +const BS: u8 = b'\x08'; +const SPACE: u8 = b' '; + +const MAX_CMD_LEN: usize = 256; + +fn print_prompt() { + print!( + "arceos:{}$ ", + path_to_str!(std::env::current_dir().unwrap()) + ); + std::io::stdout().flush().unwrap(); +} + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + let mut stdin = std::io::stdin(); + let mut stdout = std::io::stdout(); + + let mut buf = [0; MAX_CMD_LEN]; + let mut cursor = 0; + cmd::run_cmd("help".as_bytes()); + print_prompt(); + + loop { + if stdin.read(&mut buf[cursor..cursor + 1]).ok() != Some(1) { + continue; + } + if buf[cursor] == b'\x1b' { + buf[cursor] = b'^'; + } + match buf[cursor] { + CR | LF => { + println!(); + if cursor > 0 { + cmd::run_cmd(&buf[..cursor]); + cursor = 0; + } + print_prompt(); + } + BS | DL => { + if cursor > 0 { + stdout.write_all(&[BS, SPACE, BS]).unwrap(); + cursor -= 1; + } + } + 0..=31 => {} + c => { + if cursor < MAX_CMD_LEN - 1 { + stdout.write_all(&[c]).unwrap(); + cursor += 1; + } + } + } + } +} diff --git a/apps/fs/shell/src/ramfs.rs b/apps/fs/shell/src/ramfs.rs new file mode 100644 index 000000000..450cafaeb --- /dev/null +++ b/apps/fs/shell/src/ramfs.rs @@ -0,0 +1,15 @@ +extern crate alloc; + +use alloc::sync::Arc; +use axfs_ramfs::RamFileSystem; +use axfs_vfs::VfsOps; +use std::os::arceos::api::fs::{AxDisk, MyFileSystemIf}; + +struct MyFileSystemIfImpl; + +#[crate_interface::impl_interface] +impl MyFileSystemIf for MyFileSystemIfImpl { + fn new_myfs(_disk: AxDisk) -> Arc { + Arc::new(RamFileSystem::new()) + } +} diff --git a/apps/helloworld/Cargo.toml b/apps/helloworld/Cargo.toml new file mode 100644 index 000000000..9b759ec55 --- /dev/null +++ b/apps/helloworld/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "arceos-helloworld" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axstd = { path = "../../ulib/axstd", optional = true } diff --git a/apps/helloworld/expect_info.out b/apps/helloworld/expect_info.out new file mode 100644 index 000000000..870e9049c --- /dev/null +++ b/apps/helloworld/expect_info.out @@ -0,0 +1,17 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize platform devices... +Primary CPU 0 init OK. +Hello, world! +Shutting down... diff --git a/apps/helloworld/expect_info_smp4.out b/apps/helloworld/expect_info_smp4.out new file mode 100644 index 000000000..2fc566f33 --- /dev/null +++ b/apps/helloworld/expect_info_smp4.out @@ -0,0 +1,23 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize platform devices... +CPU 0 init OK +CPU 1 started +CPU 2 started +CPU 3 started +CPU 1 init OK +CPU 2 init OK +CPU 3 init OK +Hello, world! +Shutting down... diff --git a/apps/helloworld/src/main.rs b/apps/helloworld/src/main.rs new file mode 100644 index 000000000..97161c866 --- /dev/null +++ b/apps/helloworld/src/main.rs @@ -0,0 +1,10 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[cfg(feature = "axstd")] +use axstd::println; + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + println!("Hello, world!"); +} diff --git a/apps/helloworld/test_cmd b/apps/helloworld/test_cmd new file mode 100644 index 000000000..0fdc836a0 --- /dev/null +++ b/apps/helloworld/test_cmd @@ -0,0 +1,2 @@ +test_one "LOG=info" "expect_info.out" +test_one "SMP=4 LOG=info" "expect_info_smp4.out" diff --git a/apps/memtest/Cargo.toml b/apps/memtest/Cargo.toml new file mode 100644 index 000000000..592b6c4f1 --- /dev/null +++ b/apps/memtest/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "arceos-memtest" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = { version = "0.8", default-features = false, features = ["small_rng"] } +axstd = { path = "../../ulib/axstd", features = ["alloc"], optional = true } diff --git a/apps/memtest/expect_trace.out b/apps/memtest/expect_trace.out new file mode 100644 index 000000000..f77894b7b --- /dev/null +++ b/apps/memtest/expect_trace.out @@ -0,0 +1,34 @@ +smp = 1 +build_mode = release +log_level = trace + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... + use TLSF allocator. +initialize global allocator at: \[0x[0-9a-f]\+, 0x[0-9a-f]\+) +Initialize kernel page table... +Initialize platform devices... +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | EXECUTE) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | WRITE) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | WRITE) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | WRITE) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | WRITE) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | WRITE | DEVICE) +map_region(PA:0x[0-9a-f]\+): \[VA:0x[0-9a-f]\+, VA:0x[0-9a-f]\+) -> \[PA:0x[0-9a-f]\+, PA:0x[0-9a-f]\+) MappingFlags(READ | WRITE) +set page table root: PA:0x[0-9a-f]\+ => PA:0x[0-9a-f]\+ +Primary CPU 0 init OK. +Running memory tests... +expand heap memory: +test_vec() OK! +test_btree_map() OK! +Memory tests run OK! +Shutting down... diff --git a/apps/memtest/src/main.rs b/apps/memtest/src/main.rs new file mode 100644 index 000000000..e23e95e72 --- /dev/null +++ b/apps/memtest/src/main.rs @@ -0,0 +1,50 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[macro_use] +#[cfg(feature = "axstd")] +extern crate axstd as std; + +use rand::{rngs::SmallRng, RngCore, SeedableRng}; +use std::collections::BTreeMap; +use std::vec::Vec; + +fn test_vec(rng: &mut impl RngCore) { + const N: usize = 3_000_000; + let mut v = Vec::with_capacity(N); + for _ in 0..N { + v.push(rng.next_u32()); + } + v.sort(); + for i in 0..N - 1 { + assert!(v[i] <= v[i + 1]); + } + println!("test_vec() OK!"); +} + +fn test_btree_map(rng: &mut impl RngCore) { + const N: usize = 50_000; + let mut m = BTreeMap::new(); + for _ in 0..N { + let value = rng.next_u32(); + let key = format!("key_{value}"); + m.insert(key, value); + } + for (k, v) in m.iter() { + if let Some(k) = k.strip_prefix("key_") { + assert_eq!(k.parse::().unwrap(), *v); + } + } + println!("test_btree_map() OK!"); +} + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + println!("Running memory tests..."); + + let mut rng = SmallRng::seed_from_u64(0xdead_beef); + test_vec(&mut rng); + test_btree_map(&mut rng); + + println!("Memory tests run OK!"); +} diff --git a/apps/memtest/test_cmd b/apps/memtest/test_cmd new file mode 100644 index 000000000..c4a60e5ff --- /dev/null +++ b/apps/memtest/test_cmd @@ -0,0 +1 @@ +test_one "LOG=trace FEATURES=paging" "expect_trace.out" diff --git a/apps/net/bwbench/Cargo.toml b/apps/net/bwbench/Cargo.toml new file mode 100644 index 000000000..4cc245487 --- /dev/null +++ b/apps/net/bwbench/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "arceos-bwbench" +version = "0.1.0" +edition = "2021" +authors = ["ChengXiang Qi "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axstd = { path = "../../../ulib/axstd", features = ["net"] } +axnet = { path = "../../../modules/axnet" } diff --git a/apps/net/bwbench/src/main.rs b/apps/net/bwbench/src/main.rs new file mode 100644 index 000000000..a69cdb000 --- /dev/null +++ b/apps/net/bwbench/src/main.rs @@ -0,0 +1,11 @@ +#![no_std] +#![no_main] + +extern crate axstd; + +#[no_mangle] +fn main() { + axstd::println!("Benchmarking bandwidth..."); + axnet::bench_transmit(); + // axnet::bench_receive(); +} diff --git a/apps/net/echoserver/Cargo.toml b/apps/net/echoserver/Cargo.toml new file mode 100644 index 000000000..7698bf0c1 --- /dev/null +++ b/apps/net/echoserver/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "arceos-echoserver" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axstd = { path = "../../../ulib/axstd", features = ["alloc", "multitask", "net"], optional = true } diff --git a/apps/net/echoserver/src/main.rs b/apps/net/echoserver/src/main.rs new file mode 100644 index 000000000..bff296b3d --- /dev/null +++ b/apps/net/echoserver/src/main.rs @@ -0,0 +1,62 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[macro_use] +#[cfg(feature = "axstd")] +extern crate axstd as std; + +use std::io::{self, prelude::*}; +use std::net::{TcpListener, TcpStream}; +use std::thread; +use std::vec::Vec; + +const LOCAL_IP: &str = "0.0.0.0"; +const LOCAL_PORT: u16 = 5555; + +fn reverse(buf: &[u8]) -> Vec { + let mut lines = buf + .split(|&b| b == b'\n') + .map(Vec::from) + .collect::>(); + for line in lines.iter_mut() { + line.reverse(); + } + lines.join(&b'\n') +} + +fn echo_server(mut stream: TcpStream) -> io::Result<()> { + let mut buf = [0u8; 1024]; + loop { + let n = stream.read(&mut buf)?; + if n == 0 { + return Ok(()); + } + stream.write_all(reverse(&buf[..n]).as_slice())?; + } +} + +fn accept_loop() -> io::Result<()> { + let listener = TcpListener::bind((LOCAL_IP, LOCAL_PORT))?; + println!("listen on: {}", listener.local_addr().unwrap()); + + let mut i = 0; + loop { + match listener.accept() { + Ok((stream, addr)) => { + println!("new client {}: {}", i, addr); + thread::spawn(move || match echo_server(stream) { + Err(e) => println!("client connection error: {:?}", e), + Ok(()) => println!("client {} closed successfully", i), + }); + } + Err(e) => return Err(e), + } + i += 1; + } +} + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + println!("Hello, echo server!"); + accept_loop().expect("test echo server failed"); +} diff --git a/apps/net/httpclient/Cargo.toml b/apps/net/httpclient/Cargo.toml new file mode 100644 index 000000000..8efffa032 --- /dev/null +++ b/apps/net/httpclient/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "arceos-httpclient" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia ", "Dashuai Wu "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axstd = { path = "../../../ulib/axstd", features = ["net"], optional = true } + +[features] +default = [] +dns = ["axstd?/dns"] diff --git a/apps/net/httpclient/expect_info.out b/apps/net/httpclient/expect_info.out new file mode 100644 index 000000000..01e05eedf --- /dev/null +++ b/apps/net/httpclient/expect_info.out @@ -0,0 +1,38 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize platform devices... +Initialize device drivers... +registered a new Net device at .\+: "virtio-net" +Initialize network subsystem... + use NIC 0: "virtio-net" +created net interface "eth0": + ether: 52-54-00-12-34-56 + ip: 10.0.2.15/24 + gateway: 10.0.2.2 +Primary CPU 0 init OK. +Hello, simple http client! +dest: [0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+:80 ([0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+:80) +HTTP/1.1 200 OK +Server: nginx +Date: +Content-Type: text/plain +Content-Length: +Connection: keep-alive +Access-Control-Allow-Origin: * +Cache-Control: no-cache, no-store, must-revalidate + +^[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+ +Shutting down... diff --git a/apps/net/httpclient/expect_info_dns.out b/apps/net/httpclient/expect_info_dns.out new file mode 100644 index 000000000..e848229c7 --- /dev/null +++ b/apps/net/httpclient/expect_info_dns.out @@ -0,0 +1,38 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize kernel page table... +Initialize platform devices... +Initialize device drivers... +registered a new Net device at .\+: "virtio-net" +Initialize network subsystem... + use NIC 0: "virtio-net" +created net interface "eth0": + ether: 52-54-00-12-34-56 + ip: 10.0.2.15/24 + gateway: 10.0.2.2 +Primary CPU 0 init OK. +Hello, simple http client! +dest: ident.me:80 ([0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+:80) +HTTP/1.1 200 OK +Server: nginx +Date: +Content-Type: text/plain +Content-Length: +Connection: keep-alive +Access-Control-Allow-Origin: * +Cache-Control: no-cache, no-store, must-revalidate + +^[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+ +Shutting down... diff --git a/apps/net/httpclient/src/main.rs b/apps/net/httpclient/src/main.rs new file mode 100644 index 000000000..6c81a5da0 --- /dev/null +++ b/apps/net/httpclient/src/main.rs @@ -0,0 +1,40 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[macro_use] +#[cfg(feature = "axstd")] +extern crate axstd as std; + +use std::io::{self, prelude::*}; +use std::net::{TcpStream, ToSocketAddrs}; + +#[cfg(feature = "dns")] +const DEST: &str = "ident.me:80"; +#[cfg(not(feature = "dns"))] +const DEST: &str = "49.12.234.183:80"; + +const REQUEST: &str = "\ +GET / HTTP/1.1\r\n\ +Host: ident.me\r\n\ +Accept: */*\r\n\ +\r\n"; + +fn client() -> io::Result<()> { + for addr in DEST.to_socket_addrs()? { + println!("dest: {} ({})", DEST, addr); + } + + let mut stream = TcpStream::connect(DEST)?; + stream.write_all(REQUEST.as_bytes())?; + let mut buf = [0; 2048]; + let n = stream.read(&mut buf)?; + let response = core::str::from_utf8(&buf[..n]).unwrap(); + println!("{}", response); // longer response need to handle tcp package problems. + Ok(()) +} + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + println!("Hello, simple http client!"); + client().expect("test http client failed"); +} diff --git a/apps/net/httpclient/test_cmd b/apps/net/httpclient/test_cmd new file mode 100644 index 000000000..f56d6d43d --- /dev/null +++ b/apps/net/httpclient/test_cmd @@ -0,0 +1,2 @@ +test_one "LOG=info NET=y" "expect_info.out" +test_one "LOG=info NET=y APP_FEATURES=dns" "expect_info_dns.out" diff --git a/apps/net/httpserver/Cargo.toml b/apps/net/httpserver/Cargo.toml new file mode 100644 index 000000000..ba59e9820 --- /dev/null +++ b/apps/net/httpserver/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "arceos-httpserver" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axstd = { path = "../../../ulib/axstd", features = ["alloc", "multitask", "net"], optional = true } diff --git a/apps/net/httpserver/src/main.rs b/apps/net/httpserver/src/main.rs new file mode 100644 index 000000000..a5943a909 --- /dev/null +++ b/apps/net/httpserver/src/main.rs @@ -0,0 +1,96 @@ +//! Simple HTTP server. +//! +//! Benchmark with [Apache HTTP server benchmarking tool](https://httpd.apache.org/docs/2.4/programs/ab.html): +//! +//! ``` +//! ab -n 5000 -c 20 http://X.X.X.X:5555/ +//! ``` + +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[macro_use] +#[cfg(feature = "axstd")] +extern crate axstd as std; + +use std::io::{self, prelude::*}; +use std::net::{TcpListener, TcpStream}; +use std::thread; + +const LOCAL_IP: &str = "0.0.0.0"; +const LOCAL_PORT: u16 = 5555; + +macro_rules! header { + () => { + "\ +HTTP/1.1 200 OK\r\n\ +Content-Type: text/html\r\n\ +Content-Length: {}\r\n\ +Connection: close\r\n\ +\r\n\ +{}" + }; +} + +const CONTENT: &str = r#" + + Hello, ArceOS + + +
+

Hello, ArceOS

+
+
+
+ Powered by ArceOS example HTTP server v0.1.0 +
+ + +"#; + +macro_rules! info { + ($($arg:tt)*) => { + match option_env!("LOG") { + Some("info") | Some("debug") | Some("trace") => { + print!("[INFO] {}\n", format_args!($($arg)*)); + } + _ => {} + } + }; +} + +fn http_server(mut stream: TcpStream) -> io::Result<()> { + let mut buf = [0u8; 4096]; + let _len = stream.read(&mut buf)?; + + let response = format!(header!(), CONTENT.len(), CONTENT); + stream.write_all(response.as_bytes())?; + + Ok(()) +} + +fn accept_loop() -> io::Result<()> { + let listener = TcpListener::bind((LOCAL_IP, LOCAL_PORT))?; + println!("listen on: http://{}/", listener.local_addr().unwrap()); + + let mut i = 0; + loop { + match listener.accept() { + Ok((stream, addr)) => { + info!("new client {}: {}", i, addr); + thread::spawn(move || match http_server(stream) { + Err(e) => info!("client connection error: {:?}", e), + Ok(()) => info!("client {} closed successfully", i), + }); + } + Err(e) => return Err(e), + } + i += 1; + } +} + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + println!("Hello, ArceOS HTTP server!"); + accept_loop().expect("test HTTP server failed"); +} diff --git a/apps/net/udpserver/Cargo.toml b/apps/net/udpserver/Cargo.toml new file mode 100644 index 000000000..0c6b86a4a --- /dev/null +++ b/apps/net/udpserver/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "arceos-udpserver" +version = "0.1.0" +edition = "2021" +authors = ["Dashuai Wu "] + +[dependencies] +axstd = { path = "../../../ulib/axstd", features = ["net"], optional = true } diff --git a/apps/net/udpserver/src/main.rs b/apps/net/udpserver/src/main.rs new file mode 100644 index 000000000..54c2f9637 --- /dev/null +++ b/apps/net/udpserver/src/main.rs @@ -0,0 +1,38 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[macro_use] +#[cfg(feature = "axstd")] +extern crate axstd as std; + +use std::io; +use std::net::{ToSocketAddrs, UdpSocket}; + +const LOCAL_IP: &str = "0.0.0.0"; +const LOCAL_PORT: u16 = 5555; + +fn receive_loop() -> io::Result<()> { + let addr = (LOCAL_IP, LOCAL_PORT).to_socket_addrs()?.next().unwrap(); + let socket = UdpSocket::bind(addr)?; + println!("listen on: {}", socket.local_addr().unwrap()); + let mut buf = [0u8; 1024]; + loop { + match socket.recv_from(&mut buf) { + Ok((size, addr)) => { + println!("recv: {}Bytes from {}", size, addr); + let mid = core::str::from_utf8(&buf).unwrap(); + println!("{}", mid); + let mid = ["response_", mid].join(""); + socket.send_to(mid.as_bytes(), addr)?; + buf = [0u8; 1024]; + } + Err(e) => return Err(e), + }; + } +} + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + println!("Hello, simple udp client!"); + receive_loop().expect("test udp client failed"); +} diff --git a/apps/task/parallel/Cargo.toml b/apps/task/parallel/Cargo.toml new file mode 100644 index 000000000..ea9340992 --- /dev/null +++ b/apps/task/parallel/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "arceos-parallel" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = { version = "0.8", default-features = false, features = ["small_rng"] } +axstd = { path = "../../../ulib/axstd", features = ["alloc", "multitask", "irq"], optional = true } diff --git a/apps/task/parallel/expect_info_smp1_fifo.out b/apps/task/parallel/expect_info_smp1_fifo.out new file mode 100644 index 000000000..6e8097768 --- /dev/null +++ b/apps/task/parallel/expect_info_smp1_fifo.out @@ -0,0 +1,39 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize platform devices... +Initialize scheduling... + use FIFO scheduler. +Initialize interrupt handlers... +Primary CPU 0 init OK. +part 0: ThreadId(4) \[0, 125000) +part 1: ThreadId(5) \[125000, 250000) +part 2: ThreadId(6) \[250000, 375000) +part 3: ThreadId(7) \[375000, 500000) +part 4: ThreadId(8) \[500000, 625000) +part 5: ThreadId(9) \[625000, 750000) +part 6: ThreadId(10) \[750000, 875000) +part 7: ThreadId(11) \[875000, 1000000) +part 8: ThreadId(12) \[1000000, 1125000) +part 9: ThreadId(13) \[1125000, 1250000) +part 10: ThreadId(14) \[1250000, 1375000) +part 11: ThreadId(15) \[1375000, 1500000) +part 12: ThreadId(16) \[1500000, 1625000) +part 13: ThreadId(17) \[1625000, 1750000) +part 14: ThreadId(18) \[1750000, 1875000) +part 15: ThreadId(19) \[1875000, 2000000) +part 15: ThreadId(19) finished +sum = 87362923216 +Parallel summation tests run OK! +Shutting down... diff --git a/apps/task/parallel/expect_info_smp4_cfs.out b/apps/task/parallel/expect_info_smp4_cfs.out new file mode 100644 index 000000000..7f91a26d0 --- /dev/null +++ b/apps/task/parallel/expect_info_smp4_cfs.out @@ -0,0 +1,60 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: +.text (READ | EXECUTE | RESERVED) +.rodata (READ | RESERVED) +.data .tdata .tbss .percpu (READ | WRITE | RESERVED) +.percpu (READ | WRITE | RESERVED) +boot stack (READ | WRITE | RESERVED) +.bss (READ | WRITE | RESERVED) +free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize platform devices... +Initialize scheduling... + use Completely Fair scheduler. +Initialize interrupt handlers... +CPU 0 init OK +CPU 1 started +CPU 2 started +CPU 3 started +CPU 1 init OK +CPU 2 init OK +CPU 3 init OK +part 0: ThreadId([0-9]\+) \[0, 125000) +part 1: ThreadId([0-9]\+) \[125000, 250000) +part 2: ThreadId([0-9]\+) \[250000, 375000) +part 3: ThreadId([0-9]\+) \[375000, 500000) +part 4: ThreadId([0-9]\+) \[500000, 625000) +part 5: ThreadId([0-9]\+) \[625000, 750000) +part 6: ThreadId([0-9]\+) \[750000, 875000) +part 7: ThreadId([0-9]\+) \[875000, 1000000) +part 8: ThreadId([0-9]\+) \[1000000, 1125000) +part 9: ThreadId([0-9]\+) \[1125000, 1250000) +part 10: ThreadId([0-9]\+) \[1250000, 1375000) +part 11: ThreadId([0-9]\+) \[1375000, 1500000) +part 12: ThreadId([0-9]\+) \[1500000, 1625000) +part 13: ThreadId([0-9]\+) \[1625000, 1750000) +part 14: ThreadId([0-9]\+) \[1750000, 1875000) +part 15: ThreadId([0-9]\+) \[1875000, 2000000) +part 15: ThreadId([0-9]\+) finished +part 0: ThreadId([0-9]\+) finished +part 1: ThreadId([0-9]\+) finished +part 2: ThreadId([0-9]\+) finished +part 3: ThreadId([0-9]\+) finished +part 4: ThreadId([0-9]\+) finished +part 5: ThreadId([0-9]\+) finished +part 6: ThreadId([0-9]\+) finished +part 7: ThreadId([0-9]\+) finished +part 8: ThreadId([0-9]\+) finished +part 9: ThreadId([0-9]\+) finished +part 10: ThreadId([0-9]\+) finished +part 11: ThreadId([0-9]\+) finished +part 12: ThreadId([0-9]\+) finished +part 13: ThreadId([0-9]\+) finished +part 14: ThreadId([0-9]\+) finished +sum = 87362923216 +Parallel summation tests run OK! +Shutting down... diff --git a/apps/task/parallel/expect_info_smp4_rr.out b/apps/task/parallel/expect_info_smp4_rr.out new file mode 100644 index 000000000..771d4bb5d --- /dev/null +++ b/apps/task/parallel/expect_info_smp4_rr.out @@ -0,0 +1,60 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize platform devices... +Initialize scheduling... + use Round-robin scheduler. +Initialize interrupt handlers... +CPU 0 init OK +CPU 1 started +CPU 2 started +CPU 3 started +CPU 1 init OK +CPU 2 init OK +CPU 3 init OK +part 0: ThreadId([0-9]\+) \[0, 125000) +part 1: ThreadId([0-9]\+) \[125000, 250000) +part 2: ThreadId([0-9]\+) \[250000, 375000) +part 3: ThreadId([0-9]\+) \[375000, 500000) +part 4: ThreadId([0-9]\+) \[500000, 625000) +part 5: ThreadId([0-9]\+) \[625000, 750000) +part 6: ThreadId([0-9]\+) \[750000, 875000) +part 7: ThreadId([0-9]\+) \[875000, 1000000) +part 8: ThreadId([0-9]\+) \[1000000, 1125000) +part 9: ThreadId([0-9]\+) \[1125000, 1250000) +part 10: ThreadId([0-9]\+) \[1250000, 1375000) +part 11: ThreadId([0-9]\+) \[1375000, 1500000) +part 12: ThreadId([0-9]\+) \[1500000, 1625000) +part 13: ThreadId([0-9]\+) \[1625000, 1750000) +part 14: ThreadId([0-9]\+) \[1750000, 1875000) +part 15: ThreadId([0-9]\+) \[1875000, 2000000) +part 15: ThreadId([0-9]\+) finished +part 0: ThreadId([0-9]\+) finished +part 1: ThreadId([0-9]\+) finished +part 2: ThreadId([0-9]\+) finished +part 3: ThreadId([0-9]\+) finished +part 4: ThreadId([0-9]\+) finished +part 5: ThreadId([0-9]\+) finished +part 6: ThreadId([0-9]\+) finished +part 7: ThreadId([0-9]\+) finished +part 8: ThreadId([0-9]\+) finished +part 9: ThreadId([0-9]\+) finished +part 10: ThreadId([0-9]\+) finished +part 11: ThreadId([0-9]\+) finished +part 12: ThreadId([0-9]\+) finished +part 13: ThreadId([0-9]\+) finished +part 14: ThreadId([0-9]\+) finished +sum = 87362923216 +Parallel summation tests run OK! +Shutting down... diff --git a/apps/task/parallel/src/main.rs b/apps/task/parallel/src/main.rs new file mode 100644 index 000000000..2fe51c40c --- /dev/null +++ b/apps/task/parallel/src/main.rs @@ -0,0 +1,98 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[macro_use] +#[cfg(feature = "axstd")] +extern crate axstd as std; + +use rand::{rngs::SmallRng, RngCore, SeedableRng}; +use std::thread; +use std::{sync::Arc, vec::Vec}; + +#[cfg(feature = "axstd")] +use std::os::arceos::api::task::{self as api, AxWaitQueueHandle}; + +const NUM_DATA: usize = 2_000_000; +const NUM_TASKS: usize = 16; + +#[cfg(feature = "axstd")] +fn barrier() { + use std::sync::atomic::{AtomicUsize, Ordering}; + static BARRIER_WQ: AxWaitQueueHandle = AxWaitQueueHandle::new(); + static BARRIER_COUNT: AtomicUsize = AtomicUsize::new(0); + + BARRIER_COUNT.fetch_add(1, Ordering::Relaxed); + api::ax_wait_queue_wait( + &BARRIER_WQ, + || BARRIER_COUNT.load(Ordering::Relaxed) == NUM_TASKS, + None, + ); + api::ax_wait_queue_wake(&BARRIER_WQ, u32::MAX); // wakeup all +} + +#[cfg(not(feature = "axstd"))] +fn barrier() { + use std::sync::{Barrier, OnceLock}; + static BARRIER: OnceLock = OnceLock::new(); + BARRIER.get_or_init(|| Barrier::new(NUM_TASKS)).wait(); +} + +fn sqrt(n: &u64) -> u64 { + let mut x = *n; + loop { + if x * x <= *n && (x + 1) * (x + 1) > *n { + return x; + } + x = (x + *n / x) / 2; + } +} + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + let mut rng = SmallRng::seed_from_u64(0xdead_beef); + let vec = Arc::new( + (0..NUM_DATA) + .map(|_| rng.next_u32() as u64) + .collect::>(), + ); + let expect: u64 = vec.iter().map(sqrt).sum(); + + #[cfg(feature = "axstd")] + { + // equals to sleep(500ms) + let timeout = api::ax_wait_queue_wait( + &AxWaitQueueHandle::new(), + || false, + Some(std::time::Duration::from_millis(500)), + ); + assert!(timeout); + } + + let mut tasks = Vec::with_capacity(NUM_TASKS); + for i in 0..NUM_TASKS { + let vec = vec.clone(); + tasks.push(thread::spawn(move || { + let left = i * (NUM_DATA / NUM_TASKS); + let right = (left + (NUM_DATA / NUM_TASKS)).min(NUM_DATA); + println!( + "part {}: {:?} [{}, {})", + i, + thread::current().id(), + left, + right + ); + + let partial_sum: u64 = vec[left..right].iter().map(sqrt).sum(); + barrier(); + + println!("part {}: {:?} finished", i, thread::current().id()); + partial_sum + })); + } + + let actual = tasks.into_iter().map(|t| t.join().unwrap()).sum(); + println!("sum = {}", actual); + assert_eq!(expect, actual); + + println!("Parallel summation tests run OK!"); +} diff --git a/apps/task/parallel/test_cmd b/apps/task/parallel/test_cmd new file mode 100644 index 000000000..046ef5771 --- /dev/null +++ b/apps/task/parallel/test_cmd @@ -0,0 +1,3 @@ +test_one "LOG=info" "expect_info_smp1_fifo.out" +test_one "SMP=4 LOG=info FEATURES=sched_rr" "expect_info_smp4_rr.out" +test_one "SMP=4 LOG=info FEATURES=sched_cfs" "expect_info_smp4_cfs.out" diff --git a/apps/task/priority/Cargo.toml b/apps/task/priority/Cargo.toml new file mode 100644 index 000000000..5c4061a09 --- /dev/null +++ b/apps/task/priority/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "arceos-priority" +version = "0.1.0" +edition = "2021" +authors = ["Haoxing Ye "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +sched_rr = ["axstd?/sched_rr"] +sched_cfs = ["axstd?/sched_cfs"] + +[dependencies] +axstd = { path = "../../../ulib/axstd", features = ["alloc", "multitask"], optional = true } diff --git a/apps/task/priority/expect_info_smp1_cfs.out b/apps/task/priority/expect_info_smp1_cfs.out new file mode 100644 index 000000000..90ab65485 --- /dev/null +++ b/apps/task/priority/expect_info_smp1_cfs.out @@ -0,0 +1,37 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize scheduling... + use Completely Fair scheduler. +Initialize interrupt handlers... +Primary CPU 0 init OK. +part 0: ThreadId(4) \[0, 40) +part 1: ThreadId(5) \[0, 40) +part 2: ThreadId(6) \[0, 40) +part 3: ThreadId(7) \[0, 40) +part 4: ThreadId(8) \[0, 4) +part 0: ThreadId(4) finished +part 1: ThreadId(5) finished +part 2: ThreadId(6) finished +part 3: ThreadId(7) finished +part 4: ThreadId(8) finished +sum = 3318102132 +leave time: +task 0 = +task 1 = +task 2 = +task 3 = +task 4 = +Priority tests run OK! +Shutting down... diff --git a/apps/task/priority/expect_info_smp1_fifo.out b/apps/task/priority/expect_info_smp1_fifo.out new file mode 100644 index 000000000..54a3546b3 --- /dev/null +++ b/apps/task/priority/expect_info_smp1_fifo.out @@ -0,0 +1,36 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize scheduling... + use FIFO scheduler. +Primary CPU 0 init OK. +part 0: ThreadId(4) \[0, 40) +part 0: ThreadId(4) finished +part 1: ThreadId(5) \[0, 40) +part 1: ThreadId(5) finished +part 2: ThreadId(6) \[0, 40) +part 2: ThreadId(6) finished +part 3: ThreadId(7) \[0, 40) +part 3: ThreadId(7) finished +part 4: ThreadId(8) \[0, 4) +part 4: ThreadId(8) finished +sum = 3318102132 +leave time: +task 0 = +task 1 = +task 2 = +task 3 = +task 4 = +Priority tests run OK! +Shutting down... diff --git a/apps/task/priority/expect_info_smp1_rr.out b/apps/task/priority/expect_info_smp1_rr.out new file mode 100644 index 000000000..da4d57a7f --- /dev/null +++ b/apps/task/priority/expect_info_smp1_rr.out @@ -0,0 +1,37 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize scheduling... + use Round-robin scheduler. +Initialize interrupt handlers... +Primary CPU 0 init OK. +part 0: ThreadId(4) \[0, 40) +part 1: ThreadId(5) \[0, 40) +part 2: ThreadId(6) \[0, 40) +part 3: ThreadId(7) \[0, 40) +part 4: ThreadId(8) \[0, 4) +part 0: ThreadId(4) finished +part 1: ThreadId(5) finished +part 2: ThreadId(6) finished +part 3: ThreadId(7) finished +part 4: ThreadId(8) finished +sum = 3318102132 +leave time: +task 0 = +task 1 = +task 2 = +task 3 = +task 4 = +Priority tests run OK! +Shutting down... diff --git a/apps/task/priority/expect_info_smp4_cfs.out b/apps/task/priority/expect_info_smp4_cfs.out new file mode 100644 index 000000000..b73ed81dd --- /dev/null +++ b/apps/task/priority/expect_info_smp4_cfs.out @@ -0,0 +1,43 @@ +smp = 4 +build_mode = release +log_level = info + +Primary CPU [0-9]\+ started, +Secondary CPU [0-9]\+ started. +Secondary CPU [0-9]\+ started. +Secondary CPU [0-9]\+ started. +Secondary CPU [0-9]\+ init OK. +Secondary CPU [0-9]\+ init OK. +Secondary CPU [0-9]\+ init OK. +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize scheduling... + use Completely Fair scheduler. +Initialize interrupt handlers... +Primary CPU [0-9]\+ init OK. +part 0: ThreadId(7) \[0, 40) +part 1: ThreadId(8) \[0, 40) +part 2: ThreadId(9) \[0, 40) +part 3: ThreadId(10) \[0, 40) +part 4: ThreadId(11) \[0, 4) +part 0: ThreadId(7) finished +part 1: ThreadId(8) finished +part 2: ThreadId(9) finished +part 3: ThreadId(10) finished +part 4: ThreadId(11) finished +sum = 3318102132 +leave time: +task 0 = +task 1 = +task 2 = +task 3 = +task 4 = +Priority tests run OK! +Shutting down... diff --git a/apps/task/priority/src/main.rs b/apps/task/priority/src/main.rs new file mode 100644 index 000000000..ff013efd8 --- /dev/null +++ b/apps/task/priority/src/main.rs @@ -0,0 +1,125 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[macro_use] +#[cfg(feature = "axstd")] +extern crate axstd as std; + +use std::sync::Arc; +use std::{thread, time}; +use std::{vec, vec::Vec}; + +#[cfg(any(feature = "axstd", target_os = "arceos"))] +use std::os::arceos::api::task::ax_set_current_priority; + +struct TaskParam { + data_len: usize, + value: u64, + nice: isize, +} + +const TASK_PARAMS: &[TaskParam] = &[ + // four short tasks + TaskParam { + data_len: 40, + value: 1000000, + nice: 19, + }, + TaskParam { + data_len: 40, + value: 1000000, + nice: 10, + }, + TaskParam { + data_len: 40, + value: 1000000, + nice: 0, + }, + TaskParam { + data_len: 40, + value: 1000000, + nice: -10, + }, + // one long task + TaskParam { + data_len: 4, + value: 10000000, + nice: 0, + }, +]; + +const PAYLOAD_KIND: usize = 5; + +fn load(n: &u64) -> u64 { + // time consuming is linear with *n + let mut sum: u64 = *n; + for i in 0..*n { + sum += ((i ^ (i * 3)) ^ (i + *n)) / (i + 1); + } + sum +} + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + #[cfg(feature = "axstd")] + ax_set_current_priority(-20).ok(); + + let data = (0..PAYLOAD_KIND) + .map(|i| Arc::new(vec![TASK_PARAMS[i].value; TASK_PARAMS[i].data_len])) + .collect::>(); + let mut expect: u64 = 0; + for data_inner in &data { + expect += data_inner.iter().map(load).sum::(); + } + + let mut tasks = Vec::with_capacity(PAYLOAD_KIND); + let start_time = time::Instant::now(); + for i in 0..PAYLOAD_KIND { + let vec = data[i].clone(); + let data_len = TASK_PARAMS[i].data_len; + let nice = TASK_PARAMS[i].nice; + tasks.push(thread::spawn(move || { + #[cfg(feature = "axstd")] + ax_set_current_priority(nice).ok(); + + let left = 0; + let right = data_len; + println!( + "part {}: {:?} [{}, {})", + i, + thread::current().id(), + left, + right + ); + + let partial_sum: u64 = vec[left..right].iter().map(load).sum(); + let leave_time = start_time.elapsed().as_millis() as u64; + + println!("part {}: {:?} finished", i, thread::current().id()); + (partial_sum, leave_time) + })); + } + + let (results, leave_times): (Vec<_>, Vec<_>) = + tasks.into_iter().map(|t| t.join().unwrap()).unzip(); + let actual = results.iter().sum(); + + println!("sum = {}", actual); + println!("leave time:"); + for (i, time) in leave_times.iter().enumerate() { + println!("task {} = {}ms", i, time); + } + + #[cfg(feature = "axstd")] + if cfg!(feature = "sched_cfs") && option_env!("AX_SMP") == Some("1") { + assert!( + leave_times[0] > leave_times[1] + && leave_times[1] > leave_times[2] + && leave_times[2] > leave_times[3] + ); + } + + assert_eq!(expect, actual); + + println!("Priority tests run OK!"); +} diff --git a/apps/task/priority/test_cmd b/apps/task/priority/test_cmd new file mode 100644 index 000000000..6c0bd6b3a --- /dev/null +++ b/apps/task/priority/test_cmd @@ -0,0 +1,4 @@ +test_one "SMP=1 LOG=info" "expect_info_smp1_fifo.out" +test_one "SMP=1 LOG=info APP_FEATURES=sched_cfs" "expect_info_smp1_cfs.out" +test_one "SMP=1 LOG=info APP_FEATURES=sched_rr" "expect_info_smp1_rr.out" +test_one "SMP=4 LOG=info APP_FEATURES=sched_cfs" "expect_info_smp4_cfs.out" diff --git a/apps/task/sleep/Cargo.toml b/apps/task/sleep/Cargo.toml new file mode 100644 index 000000000..2608c3db8 --- /dev/null +++ b/apps/task/sleep/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "arceos-sleep" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axstd = { path = "../../../ulib/axstd", features = ["multitask", "irq"], optional = true } diff --git a/apps/task/sleep/expect_info_smp4_fifo.out b/apps/task/sleep/expect_info_smp4_fifo.out new file mode 100644 index 000000000..ebcdf252b --- /dev/null +++ b/apps/task/sleep/expect_info_smp4_fifo.out @@ -0,0 +1,82 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize platform devices... +Initialize scheduling... + use FIFO scheduler. +Initialize interrupt handlers... +Hello, main task! +main task sleep for 1\.[0-9]\+s + tick 0 +task 0 sleep 1 seconds (0) ... +task 1 sleep 2 seconds (0) ... +task 2 sleep 3 seconds (0) ... +task 3 sleep 4 seconds (0) ... +task 4 sleep 5 seconds (0) ... + tick 1 +task 0 actual sleep 1\.[0-9]\+s seconds (0). +task 0 sleep 1 seconds (1) ... + tick 2 + tick 3 +task 1 actual sleep 2\.[0-9]\+s seconds (0). +task 1 sleep 2 seconds (1) ... +task 0 actual sleep 1\.[0-9]\+s seconds (1). +task 0 sleep 1 seconds (2) ... + tick 4 + tick 5 +task 2 actual sleep 3\.[0-9]\+s seconds (0). +task 2 sleep 3 seconds (1) ... +task 0 actual sleep 1\.[0-9]\+s seconds (2). + tick 6 + tick 7 +task 3 actual sleep 4\.[0-9]\+s seconds (0). +task 3 sleep 4 seconds (1) ... +task 1 actual sleep 2\.[0-9]\+s seconds (1). +task 1 sleep 2 seconds (2) ... + tick 8 + tick 9 +task 4 actual sleep 5\.[0-9]\+s seconds (0). +task 4 sleep 5 seconds (1) ... + tick 10 + tick 11 +task 2 actual sleep 3\.[0-9]\+s seconds (1). +task 2 sleep 3 seconds (2) ... +task 1 actual sleep 2\.[0-9]\+s seconds (2). + tick 12 + tick 13 + tick 14 + tick 15 +task 3 actual sleep 4\.[0-9]\+s seconds (1). +task 3 sleep 4 seconds (2) ... + tick 16 + tick 17 +task 2 actual sleep 3\.[0-9]\+s seconds (2). + tick 18 + tick 19 +task 4 actual sleep 5\.[0-9]\+s seconds (1). +task 4 sleep 5 seconds (2) ... + tick 20 + tick 21 + tick 22 + tick 23 +task 3 actual sleep 4\.[0-9]\+s seconds (2). + tick 24 + tick 25 + tick 26 + tick 27 + tick 28 + tick 29 +task 4 actual sleep 5\.[0-9]\+s seconds (2). +Sleep tests run OK! +Shutting down... diff --git a/apps/task/sleep/expect_info_smp4_rr.out b/apps/task/sleep/expect_info_smp4_rr.out new file mode 100644 index 000000000..7d09432a8 --- /dev/null +++ b/apps/task/sleep/expect_info_smp4_rr.out @@ -0,0 +1,82 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize platform devices... +Initialize scheduling... + use Round-robin scheduler. +Initialize interrupt handlers... +Hello, main task! +main task sleep for 1\.[0-9]\+s + tick 0 +task 0 sleep 1 seconds (0) ... +task 1 sleep 2 seconds (0) ... +task 2 sleep 3 seconds (0) ... +task 3 sleep 4 seconds (0) ... +task 4 sleep 5 seconds (0) ... + tick 1 +task 0 actual sleep 1\.[0-9]\+s seconds (0). +task 0 sleep 1 seconds (1) ... + tick 2 + tick 3 +task 1 actual sleep 2\.[0-9]\+s seconds (0). +task 1 sleep 2 seconds (1) ... +task 0 actual sleep 1\.[0-9]\+s seconds (1). +task 0 sleep 1 seconds (2) ... + tick 4 + tick 5 +task 2 actual sleep 3\.[0-9]\+s seconds (0). +task 2 sleep 3 seconds (1) ... +task 0 actual sleep 1\.[0-9]\+s seconds (2). + tick 6 + tick 7 +task 3 actual sleep 4\.[0-9]\+s seconds (0). +task 3 sleep 4 seconds (1) ... +task 1 actual sleep 2\.[0-9]\+s seconds (1). +task 1 sleep 2 seconds (2) ... + tick 8 + tick 9 +task 4 actual sleep 5\.[0-9]\+s seconds (0). +task 4 sleep 5 seconds (1) ... + tick 10 + tick 11 +task 2 actual sleep 3\.[0-9]\+s seconds (1). +task 2 sleep 3 seconds (2) ... +task 1 actual sleep 2\.[0-9]\+s seconds (2). + tick 12 + tick 13 + tick 14 + tick 15 +task 3 actual sleep 4\.[0-9]\+s seconds (1). +task 3 sleep 4 seconds (2) ... + tick 16 + tick 17 +task 2 actual sleep 3\.[0-9]\+s seconds (2). + tick 18 + tick 19 +task 4 actual sleep 5\.[0-9]\+s seconds (1). +task 4 sleep 5 seconds (2) ... + tick 20 + tick 21 + tick 22 + tick 23 +task 3 actual sleep 4\.[0-9]\+s seconds (2). + tick 24 + tick 25 + tick 26 + tick 27 + tick 28 + tick 29 +task 4 actual sleep 5\.[0-9]\+s seconds (2). +Sleep tests run OK! +Shutting down... diff --git a/apps/task/sleep/src/main.rs b/apps/task/sleep/src/main.rs new file mode 100644 index 000000000..6cc4f0c2f --- /dev/null +++ b/apps/task/sleep/src/main.rs @@ -0,0 +1,51 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[macro_use] +#[cfg(feature = "axstd")] +extern crate axstd as std; + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread; +use std::time::{Duration, Instant}; + +const NUM_TASKS: usize = 5; + +static FINISHED_TASKS: AtomicUsize = AtomicUsize::new(0); + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + println!("Hello, main task!"); + let now = Instant::now(); + thread::sleep(Duration::from_secs(1)); + let elapsed = now.elapsed(); + println!("main task sleep for {:?}", elapsed); + + // backgroud ticks, 0.5s x 30 = 15s + thread::spawn(|| { + for i in 0..30 { + println!(" tick {}", i); + thread::sleep(Duration::from_millis(500)); + } + }); + + // task n: sleep 3 x n (sec) + for i in 0..NUM_TASKS { + thread::spawn(move || { + let sec = i + 1; + for j in 0..3 { + println!("task {} sleep {} seconds ({}) ...", i, sec, j); + let now = Instant::now(); + thread::sleep(Duration::from_secs(sec as _)); + let elapsed = now.elapsed(); + println!("task {} actual sleep {:?} seconds ({}).", i, elapsed, j); + } + FINISHED_TASKS.fetch_add(1, Ordering::Relaxed); + }); + } + + while FINISHED_TASKS.load(Ordering::Relaxed) < NUM_TASKS { + thread::sleep(Duration::from_millis(10)); + } + println!("Sleep tests run OK!"); +} diff --git a/apps/task/sleep/test_cmd b/apps/task/sleep/test_cmd new file mode 100644 index 000000000..beeb3c408 --- /dev/null +++ b/apps/task/sleep/test_cmd @@ -0,0 +1,2 @@ +test_one "SMP=4 LOG=info" "expect_info_smp4_fifo.out" +test_one "SMP=4 LOG=info FEATURES=sched_rr" "expect_info_smp4_rr.out" diff --git a/apps/task/tls/Cargo.toml b/apps/task/tls/Cargo.toml new file mode 100644 index 000000000..86cbe1bd4 --- /dev/null +++ b/apps/task/tls/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "arceos-tls" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axstd = { path = "../../../ulib/axstd", features = ["tls", "alloc", "multitask"], optional = true } diff --git a/apps/task/tls/expect_info_smp1_fifo.out b/apps/task/tls/expect_info_smp1_fifo.out new file mode 100644 index 000000000..9628fda4e --- /dev/null +++ b/apps/task/tls/expect_info_smp1_fifo.out @@ -0,0 +1,31 @@ +smp = 1 +build_mode = release +log_level = info + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize platform devices... +Initialize scheduling... + use FIFO scheduler. +Primary CPU 0 init OK. +Running TLS tests... +main: true 0xaa 0xcafe 0xdeadbeed 0xa2ce05a2ce05 Hello, world! +1: false 0xab 0xcaff 0xdeadbeee 0xa2ce05a2ce06 Hello1 world! +2: true 0xac 0xcb00 0xdeadbeef 0xa2ce05a2ce07 Hello2 world! +3: false 0xad 0xcb01 0xdeadbef0 0xa2ce05a2ce08 Hello3 world! +4: true 0xae 0xcb02 0xdeadbef1 0xa2ce05a2ce09 Hello4 world! +5: false 0xaf 0xcb03 0xdeadbef2 0xa2ce05a2ce0a Hello5 world! +6: true 0xb0 0xcb04 0xdeadbef3 0xa2ce05a2ce0b Hello6 world! +7: false 0xb1 0xcb05 0xdeadbef4 0xa2ce05a2ce0c Hello7 world! +8: true 0xb2 0xcb06 0xdeadbef5 0xa2ce05a2ce0d Hello8 world! +9: false 0xb3 0xcb07 0xdeadbef6 0xa2ce05a2ce0e Hello9 world! +10: true 0xb4 0xcb08 0xdeadbef7 0xa2ce05a2ce0f Hello: world! +TLS tests run OK! +Shutting down... diff --git a/apps/task/tls/expect_info_smp4_rr.out b/apps/task/tls/expect_info_smp4_rr.out new file mode 100644 index 000000000..a65f69af2 --- /dev/null +++ b/apps/task/tls/expect_info_smp4_rr.out @@ -0,0 +1,38 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize platform devices... +Initialize scheduling... + use Round-robin scheduler. +Initialize interrupt handlers... +CPU 0 init OK +CPU 1 started +CPU 2 started +CPU 3 started +CPU 1 init OK +CPU 2 init OK +CPU 3 init OK +Running TLS tests... +main: true 0xaa 0xcafe 0xdeadbeed 0xa2ce05a2ce05 Hello, world! +1: false 0xab 0xcaff 0xdeadbeee 0xa2ce05a2ce06 Hello1 world! +2: true 0xac 0xcb00 0xdeadbeef 0xa2ce05a2ce07 Hello2 world! +3: false 0xad 0xcb01 0xdeadbef0 0xa2ce05a2ce08 Hello3 world! +4: true 0xae 0xcb02 0xdeadbef1 0xa2ce05a2ce09 Hello4 world! +5: false 0xaf 0xcb03 0xdeadbef2 0xa2ce05a2ce0a Hello5 world! +6: true 0xb0 0xcb04 0xdeadbef3 0xa2ce05a2ce0b Hello6 world! +7: false 0xb1 0xcb05 0xdeadbef4 0xa2ce05a2ce0c Hello7 world! +8: true 0xb2 0xcb06 0xdeadbef5 0xa2ce05a2ce0d Hello8 world! +9: false 0xb3 0xcb07 0xdeadbef6 0xa2ce05a2ce0e Hello9 world! +10: true 0xb4 0xcb08 0xdeadbef7 0xa2ce05a2ce0f Hello: world! +TLS tests run OK! +Shutting down... diff --git a/apps/task/tls/src/main.rs b/apps/task/tls/src/main.rs new file mode 100644 index 000000000..07d870922 --- /dev/null +++ b/apps/task/tls/src/main.rs @@ -0,0 +1,111 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] +#![feature(thread_local)] +#![allow(unused_unsafe)] + +#[macro_use] +#[cfg(feature = "axstd")] +extern crate axstd as std; + +use std::{thread, vec::Vec}; + +#[thread_local] +static mut BOOL: bool = true; + +#[thread_local] +static mut U8: u8 = 0xAA; + +#[thread_local] +static mut U16: u16 = 0xcafe; + +#[thread_local] +static mut U32: u32 = 0xdeadbeed; + +#[thread_local] +static mut U64: u64 = 0xa2ce05_a2ce05; + +#[thread_local] +static mut STR: [u8; 13] = *b"Hello, world!"; + +macro_rules! get { + ($var:expr) => { + unsafe { $var } + }; +} + +macro_rules! set { + ($var:expr, $value:expr) => { + unsafe { $var = $value } + }; +} + +macro_rules! add { + ($var:expr, $value:expr) => { + unsafe { $var += $value } + }; +} + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + println!("Running TLS tests..."); + + println!( + "main: {} {:#x} {:#x} {:#x} {:#x} {}", + get!(BOOL), + get!(U8), + get!(U16), + get!(U32), + get!(U64), + get!(std::str::from_utf8_unchecked(&STR)) + ); + assert!(get!(BOOL)); + assert_eq!(get!(U8), 0xAA); + assert_eq!(get!(U16), 0xcafe); + assert_eq!(get!(U32), 0xdeadbeed); + assert_eq!(get!(U64), 0xa2ce05_a2ce05); + assert_eq!(get!(&STR), b"Hello, world!"); + + let mut tasks = Vec::new(); + for i in 1..=10 { + tasks.push(thread::spawn(move || { + set!(BOOL, i % 2 == 0); + add!(U8, i as u8); + add!(U16, i as u16); + add!(U32, i as u32); + add!(U64, i as u64); + set!(STR[5], 48 + i as u8); + + thread::yield_now(); + + println!( + "{}: {} {:#x} {:#x} {:#x} {:#x} {}", + i, + get!(BOOL), + get!(U8), + get!(U16), + get!(U32), + get!(U64), + get!(std::str::from_utf8_unchecked(&STR)) + ); + assert_eq!(get!(BOOL), i % 2 == 0); + assert_eq!(get!(U8), 0xAA + i as u8); + assert_eq!(get!(U16), 0xcafe + i as u16); + assert_eq!(get!(U32), 0xdeadbeed + i as u32); + assert_eq!(get!(U64), 0xa2ce05_a2ce05 + i as u64); + assert_eq!(get!(STR[5]), 48 + i as u8); + assert_eq!(get!(STR.len()), 13); + })); + } + + tasks.into_iter().for_each(|t| t.join().unwrap()); + + // TLS of main thread must not have been changed by the other thread. + assert!(get!(BOOL)); + assert_eq!(get!(U8), 0xAA); + assert_eq!(get!(U16), 0xcafe); + assert_eq!(get!(U32), 0xdeadbeed); + assert_eq!(get!(U64), 0xa2ce05_a2ce05); + assert_eq!(get!(&STR), b"Hello, world!"); + + println!("TLS tests run OK!"); +} diff --git a/apps/task/tls/test_cmd b/apps/task/tls/test_cmd new file mode 100644 index 000000000..e28757954 --- /dev/null +++ b/apps/task/tls/test_cmd @@ -0,0 +1,2 @@ +test_one "LOG=info" "expect_info_smp1_fifo.out" +test_one "SMP=4 LOG=info FEATURES=sched_rr" "expect_info_smp4_rr.out" diff --git a/apps/task/yield/Cargo.toml b/apps/task/yield/Cargo.toml new file mode 100644 index 000000000..b8d775bc6 --- /dev/null +++ b/apps/task/yield/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "arceos-yield" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +sched_rr = ["axstd?/sched_rr"] +sched_cfs = ["axstd?/sched_cfs"] + +[dependencies] +axstd = { path = "../../../ulib/axstd", features = ["multitask"], optional = true } diff --git a/apps/task/yield/expect_debug_smp1_fifo.out b/apps/task/yield/expect_debug_smp1_fifo.out new file mode 100644 index 000000000..38adf4488 --- /dev/null +++ b/apps/task/yield/expect_debug_smp1_fifo.out @@ -0,0 +1,54 @@ +smp = 1 +build_mode = release +log_level = debug + +Primary CPU 0 started, +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize platform devices... +Initialize scheduling... + use FIFO scheduler. +Primary CPU 0 init OK. +Hello, main task! +Hello, task 0! id = ThreadId(4) +Hello, task 1! id = ThreadId(5) +Hello, task 2! id = ThreadId(6) +Hello, task 3! id = ThreadId(7) +Hello, task 4! id = ThreadId(8) +Hello, task 5! id = ThreadId(9) +Hello, task 6! id = ThreadId(10) +Hello, task 7! id = ThreadId(11) +Hello, task 8! id = ThreadId(12) +Hello, task 9! id = ThreadId(13) +task block: Task(3, "gc") +task exit: Task(4, ""), exit_code=0 +task unblock: Task(3, "gc") +task exit: Task(5, ""), exit_code=0 +task exit: Task(6, ""), exit_code=0 +task exit: Task(7, ""), exit_code=0 +task exit: Task(8, ""), exit_code=0 +task exit: Task(9, ""), exit_code=0 +task exit: Task(10, ""), exit_code=0 +task exit: Task(11, ""), exit_code=0 +task exit: Task(12, ""), exit_code=0 +task exit: Task(13, ""), exit_code=0 +Task yielding tests run OK! +task exit: Task(2, "main"), exit_code=0 +task drop: Task(4, "") +task drop: Task(5, "") +task drop: Task(6, "") +task drop: Task(7, "") +task drop: Task(8, "") +task drop: Task(9, "") +task drop: Task(10, "") +task drop: Task(11, "") +task drop: Task(12, "") +task drop: Task(13, "") +Shutting down... diff --git a/apps/task/yield/expect_info_smp4_fifo.out b/apps/task/yield/expect_info_smp4_fifo.out new file mode 100644 index 000000000..5f75f056f --- /dev/null +++ b/apps/task/yield/expect_info_smp4_fifo.out @@ -0,0 +1,38 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize platform devices... +Initialize scheduling... + use FIFO scheduler. +CPU 1 init OK +CPU 0 init OK +CPU 1 started +CPU 2 started +CPU 3 started +CPU 1 init OK +CPU 2 init OK +CPU 3 init OK +Hello, main task! +Hello, task 0! id = ThreadId(7) +Hello, task 1! id = ThreadId(8) +Hello, task 2! id = ThreadId(9) +Hello, task 3! id = ThreadId(10) +Hello, task 4! id = ThreadId(11) +Hello, task 5! id = ThreadId(12) +Hello, task 6! id = ThreadId(13) +Hello, task 7! id = ThreadId(14) +Hello, task 8! id = ThreadId(15) +Hello, task 9! id = ThreadId(16) +Task yielding tests run OK! +Shutting down... diff --git a/apps/task/yield/expect_info_smp4_rr.out b/apps/task/yield/expect_info_smp4_rr.out new file mode 100644 index 000000000..daa0c7126 --- /dev/null +++ b/apps/task/yield/expect_info_smp4_rr.out @@ -0,0 +1,38 @@ +smp = 4 +build_mode = release +log_level = info + +CPU 0 started +Found physcial memory regions: + .text (READ | EXECUTE | RESERVED) + .rodata (READ | RESERVED) + .data .tdata .tbss .percpu (READ | WRITE | RESERVED) + .percpu (READ | WRITE | RESERVED) + boot stack (READ | WRITE | RESERVED) + .bss (READ | WRITE | RESERVED) + free memory (READ | WRITE | FREE) +Initialize global memory allocator... +Initialize platform devices... +Initialize scheduling... + use Round-robin scheduler. +Initialize interrupt handlers... +CPU 0 init OK +CPU 1 started +CPU 2 started +CPU 3 started +CPU 1 init OK +CPU 2 init OK +CPU 3 init OK +Hello, main task! +Hello, task 0! id = ThreadId(7) +Hello, task 1! id = ThreadId(8) +Hello, task 2! id = ThreadId(9) +Hello, task 3! id = ThreadId(10) +Hello, task 4! id = ThreadId(11) +Hello, task 5! id = ThreadId(12) +Hello, task 6! id = ThreadId(13) +Hello, task 7! id = ThreadId(14) +Hello, task 8! id = ThreadId(15) +Hello, task 9! id = ThreadId(16) +Task yielding tests run OK! +Shutting down... diff --git a/apps/task/yield/src/main.rs b/apps/task/yield/src/main.rs new file mode 100644 index 000000000..f8cd27bf2 --- /dev/null +++ b/apps/task/yield/src/main.rs @@ -0,0 +1,36 @@ +#![cfg_attr(feature = "axstd", no_std)] +#![cfg_attr(feature = "axstd", no_main)] + +#[macro_use] +#[cfg(feature = "axstd")] +extern crate axstd as std; + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread; + +const NUM_TASKS: usize = 10; +static FINISHED_TASKS: AtomicUsize = AtomicUsize::new(0); + +#[cfg_attr(feature = "axstd", no_mangle)] +fn main() { + for i in 0..NUM_TASKS { + thread::spawn(move || { + println!("Hello, task {}! id = {:?}", i, thread::current().id()); + + #[cfg(all(not(feature = "sched_rr"), not(feature = "sched_cfs")))] + thread::yield_now(); + + let _order = FINISHED_TASKS.fetch_add(1, Ordering::Relaxed); + #[cfg(not(feature = "sched_cfs"))] + if option_env!("AX_SMP") == Some("1") { + assert!(_order == i); // FIFO scheduler + } + }); + } + println!("Hello, main task!"); + while FINISHED_TASKS.load(Ordering::Relaxed) < NUM_TASKS { + #[cfg(all(not(feature = "sched_rr"), not(feature = "sched_cfs")))] + thread::yield_now(); + } + println!("Task yielding tests run OK!"); +} diff --git a/apps/task/yield/test_cmd b/apps/task/yield/test_cmd new file mode 100644 index 000000000..bf30e3ba4 --- /dev/null +++ b/apps/task/yield/test_cmd @@ -0,0 +1,3 @@ +test_one "LOG=debug" "expect_debug_smp1_fifo.out" +test_one "SMP=4 LOG=info" "expect_info_smp4_fifo.out" +test_one "SMP=4 LOG=info APP_FEATURES=sched_rr" "expect_info_smp4_rr.out" diff --git a/crates/allocator/Cargo.toml b/crates/allocator/Cargo.toml new file mode 100644 index 000000000..b8770f124 --- /dev/null +++ b/crates/allocator/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "allocator" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Various allocator algorithms in a unified interface" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/allocator" +documentation = "https://rcore-os.github.io/arceos/allocator/index.html" + +[features] +default = [] +full = ["bitmap", "tlsf", "slab", "buddy", "allocator_api"] + +bitmap = ["dep:bitmap-allocator"] + +tlsf = ["dep:rlsf"] +slab = ["dep:slab_allocator"] +buddy = ["dep:buddy_system_allocator"] + +allocator_api = [] + +[dependencies] +buddy_system_allocator = { version = "0.9", default-features = false, optional = true } +slab_allocator = { path = "../slab_allocator", optional = true } +rlsf = { version = "0.2", optional = true } +bitmap-allocator = { git = "https://github.com/rcore-os/bitmap-allocator.git", rev = "88e871a", optional = true } + +[dev-dependencies] +allocator = { path = ".", features = ["full"] } +rand = { version = "0.8", features = ["small_rng"] } +criterion = { version = "0.5", features = ["html_reports"] } + +[[bench]] +name = "collections" +harness = false diff --git a/crates/allocator/benches/collections.rs b/crates/allocator/benches/collections.rs new file mode 100644 index 000000000..c31fd84b4 --- /dev/null +++ b/crates/allocator/benches/collections.rs @@ -0,0 +1,101 @@ +#![feature(allocator_api)] +#![feature(btreemap_alloc)] + +mod utils; + +use std::alloc::Allocator; +use std::collections::BTreeMap; +use std::io::Write; + +use allocator::{AllocatorRc, BuddyByteAllocator, SlabByteAllocator, TlsfByteAllocator}; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use rand::{rngs::SmallRng, seq::SliceRandom, RngCore, SeedableRng}; + +use self::utils::MemoryPool; + +const POOL_SIZE: usize = 1024 * 1024 * 128; + +fn vec_push(n: usize, alloc: &(impl Allocator + Clone)) { + let mut v: Vec = Vec::new_in(alloc.clone()); + for _ in 0..n { + v.push(0xdead_beef); + } + drop(v); +} + +fn vec_rand_free(n: usize, blk_size: usize, alloc: &(impl Allocator + Clone)) { + let mut v = Vec::new_in(alloc.clone()); + for _ in 0..n { + let block = Vec::::with_capacity_in(blk_size, alloc.clone()); + v.push(block); + } + + let mut rng = SmallRng::seed_from_u64(0xdead_beef); + let mut index = Vec::with_capacity_in(n, alloc.clone()); + for i in 0..n { + index.push(i); + } + index.shuffle(&mut rng); + + for i in index { + v[i] = Vec::new_in(alloc.clone()); + } + drop(v); +} + +fn btree_map(n: usize, alloc: &(impl Allocator + Clone)) { + let mut rng = SmallRng::seed_from_u64(0xdead_beef); + let mut m = BTreeMap::new_in(alloc.clone()); + for _ in 0..n { + if rng.next_u32() % 5 == 0 && !m.is_empty() { + m.pop_first(); + } else { + let value = rng.next_u32(); + let mut key = Vec::new_in(alloc.clone()); + write!(&mut key, "key_{value}").unwrap(); + m.insert(key, value); + } + } + m.clear(); + drop(m); +} + +fn bench(c: &mut Criterion, alloc_name: &str, alloc: impl Allocator + Clone) { + let mut g = c.benchmark_group(alloc_name); + g.bench_function("vec_push_3M", |b| { + b.iter(|| vec_push(black_box(3_000_000), &alloc)); + }); + g.sample_size(10); + g.bench_function("vec_rand_free_25K_64", |b| { + b.iter(|| vec_rand_free(black_box(25_000), black_box(64), &alloc)); + }); + g.bench_function("vec_rand_free_7500_520", |b| { + b.iter(|| vec_rand_free(black_box(7_500), black_box(520), &alloc)); + }); + g.bench_function("btree_map_50K", |b| { + b.iter(|| btree_map(black_box(50_000), &alloc)); + }); +} + +fn criterion_benchmark(c: &mut Criterion) { + let mut pool = MemoryPool::new(POOL_SIZE); + bench(c, "system", std::alloc::System); + bench( + c, + "tlsf", + AllocatorRc::new(TlsfByteAllocator::new(), pool.as_slice()), + ); + bench( + c, + "slab", + AllocatorRc::new(SlabByteAllocator::new(), pool.as_slice()), + ); + bench( + c, + "buddy", + AllocatorRc::new(BuddyByteAllocator::new(), pool.as_slice()), + ); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); diff --git a/crates/allocator/benches/utils/mod.rs b/crates/allocator/benches/utils/mod.rs new file mode 100644 index 000000000..995e1535c --- /dev/null +++ b/crates/allocator/benches/utils/mod.rs @@ -0,0 +1,25 @@ +use std::alloc::Layout; +use std::ptr::NonNull; + +pub struct MemoryPool { + ptr: NonNull, + layout: Layout, +} + +impl MemoryPool { + pub fn new(size: usize) -> Self { + let layout = Layout::from_size_align(size, 4096).unwrap(); + let ptr = NonNull::new(unsafe { std::alloc::alloc_zeroed(layout) }).unwrap(); + Self { ptr, layout } + } + + pub fn as_slice(&mut self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.ptr.as_ptr(), self.layout.size()) } + } +} + +impl Drop for MemoryPool { + fn drop(&mut self) { + unsafe { std::alloc::dealloc(self.ptr.as_ptr(), self.layout) }; + } +} diff --git a/crates/allocator/src/bitmap.rs b/crates/allocator/src/bitmap.rs new file mode 100644 index 000000000..86018c73d --- /dev/null +++ b/crates/allocator/src/bitmap.rs @@ -0,0 +1,95 @@ +//! Bitmap allocation in page-granularity. +//! +//! TODO: adaptive size + +use bitmap_allocator::BitAlloc; + +use crate::{AllocError, AllocResult, BaseAllocator, PageAllocator}; + +// Support max 1M * 4096 = 4GB memory. +type BitAllocUsed = bitmap_allocator::BitAlloc1M; + +/// A page-granularity memory allocator based on the [bitmap_allocator]. +/// +/// It internally uses a bitmap, each bit indicates whether a page has been +/// allocated. +/// +/// The `PAGE_SIZE` must be a power of two. +/// +/// [bitmap_allocator]: https://github.com/rcore-os/bitmap-allocator +pub struct BitmapPageAllocator { + base: usize, + total_pages: usize, + used_pages: usize, + inner: BitAllocUsed, +} + +impl BitmapPageAllocator { + /// Creates a new empty `BitmapPageAllocator`. + pub const fn new() -> Self { + Self { + base: 0, + total_pages: 0, + used_pages: 0, + inner: BitAllocUsed::DEFAULT, + } + } +} + +impl BaseAllocator for BitmapPageAllocator { + fn init(&mut self, start: usize, size: usize) { + assert!(PAGE_SIZE.is_power_of_two()); + let end = super::align_down(start + size, PAGE_SIZE); + let start = super::align_up(start, PAGE_SIZE); + self.base = start; + self.total_pages = (end - start) / PAGE_SIZE; + self.inner.insert(0..self.total_pages); + } + + fn add_memory(&mut self, _start: usize, _size: usize) -> AllocResult { + Err(AllocError::NoMemory) // unsupported + } +} + +impl PageAllocator for BitmapPageAllocator { + const PAGE_SIZE: usize = PAGE_SIZE; + + fn alloc_pages(&mut self, num_pages: usize, align_pow2: usize) -> AllocResult { + if align_pow2 % PAGE_SIZE != 0 { + return Err(AllocError::InvalidParam); + } + let align_pow2 = align_pow2 / PAGE_SIZE; + if !align_pow2.is_power_of_two() { + return Err(AllocError::InvalidParam); + } + let align_log2 = align_pow2.trailing_zeros() as usize; + match num_pages.cmp(&1) { + core::cmp::Ordering::Equal => self.inner.alloc().map(|idx| idx * PAGE_SIZE + self.base), + core::cmp::Ordering::Greater => self + .inner + .alloc_contiguous(num_pages, align_log2) + .map(|idx| idx * PAGE_SIZE + self.base), + _ => return Err(AllocError::InvalidParam), + } + .ok_or(AllocError::NoMemory) + .inspect(|_| self.used_pages += num_pages) + } + + fn dealloc_pages(&mut self, pos: usize, num_pages: usize) { + // TODO: not decrease `used_pages` if deallocation failed + self.used_pages -= num_pages; + self.inner.dealloc((pos - self.base) / PAGE_SIZE) + } + + fn total_pages(&self) -> usize { + self.total_pages + } + + fn used_pages(&self) -> usize { + self.used_pages + } + + fn available_pages(&self) -> usize { + self.total_pages - self.used_pages + } +} diff --git a/crates/allocator/src/buddy.rs b/crates/allocator/src/buddy.rs new file mode 100644 index 000000000..872f540a4 --- /dev/null +++ b/crates/allocator/src/buddy.rs @@ -0,0 +1,58 @@ +//! Buddy memory allocation. +//! +//! TODO: more efficient + +use buddy_system_allocator::Heap; +use core::alloc::Layout; +use core::ptr::NonNull; + +use crate::{AllocError, AllocResult, BaseAllocator, ByteAllocator}; + +/// A byte-granularity memory allocator based on the [buddy_system_allocator]. +/// +/// [buddy_system_allocator]: https://docs.rs/buddy_system_allocator/latest/buddy_system_allocator/ +pub struct BuddyByteAllocator { + inner: Heap<32>, +} + +impl BuddyByteAllocator { + /// Creates a new empty `BuddyByteAllocator`. + pub const fn new() -> Self { + Self { + inner: Heap::<32>::new(), + } + } +} + +impl BaseAllocator for BuddyByteAllocator { + fn init(&mut self, start: usize, size: usize) { + unsafe { self.inner.init(start, size) }; + } + + fn add_memory(&mut self, start: usize, size: usize) -> AllocResult { + unsafe { self.inner.add_to_heap(start, start + size) }; + Ok(()) + } +} + +impl ByteAllocator for BuddyByteAllocator { + fn alloc(&mut self, layout: Layout) -> AllocResult> { + self.inner.alloc(layout).map_err(|_| AllocError::NoMemory) + } + + fn dealloc(&mut self, pos: NonNull, layout: Layout) { + self.inner.dealloc(pos, layout) + } + + fn total_bytes(&self) -> usize { + self.inner.stats_total_bytes() + } + + fn used_bytes(&self) -> usize { + self.inner.stats_alloc_actual() + } + + fn available_bytes(&self) -> usize { + self.inner.stats_total_bytes() - self.inner.stats_alloc_actual() + } +} diff --git a/crates/allocator/src/lib.rs b/crates/allocator/src/lib.rs new file mode 100644 index 000000000..5ced1b992 --- /dev/null +++ b/crates/allocator/src/lib.rs @@ -0,0 +1,181 @@ +//! Various allocator algorithms in a unified interface. +//! +//! There are three types of allocators: +//! +//! - [`ByteAllocator`]: Byte-granularity memory allocator. (e.g., +//! [`BuddyByteAllocator`], [`SlabByteAllocator`]) +//! - [`PageAllocator`]: Page-granularity memory allocator. (e.g., +//! [`BitmapPageAllocator`]) +//! - [`IdAllocator`]: Used to allocate unique IDs. + +#![no_std] +#![feature(result_option_inspect)] +#![cfg_attr(feature = "allocator_api", feature(allocator_api))] + +#[cfg(feature = "bitmap")] +mod bitmap; +#[cfg(feature = "bitmap")] +pub use bitmap::BitmapPageAllocator; + +#[cfg(feature = "buddy")] +mod buddy; +#[cfg(feature = "buddy")] +pub use buddy::BuddyByteAllocator; + +#[cfg(feature = "slab")] +mod slab; +#[cfg(feature = "slab")] +pub use slab::SlabByteAllocator; + +#[cfg(feature = "tlsf")] +mod tlsf; +#[cfg(feature = "tlsf")] +pub use tlsf::TlsfByteAllocator; + +use core::alloc::Layout; +use core::ptr::NonNull; + +/// The error type used for allocation. +#[derive(Debug)] +pub enum AllocError { + /// Invalid `size` or `align_pow2`. (e.g. unaligned) + InvalidParam, + /// Memory added by `add_memory` overlapped with existed memory. + MemoryOverlap, + /// No enough memory to allocate. + NoMemory, + /// Deallocate an unallocated memory region. + NotAllocated, +} + +/// A [`Result`] type with [`AllocError`] as the error type. +pub type AllocResult = Result; + +/// The base allocator inherited by other allocators. +pub trait BaseAllocator { + /// Initialize the allocator with a free memory region. + fn init(&mut self, start: usize, size: usize); + + /// Add a free memory region to the allocator. + fn add_memory(&mut self, start: usize, size: usize) -> AllocResult; +} + +/// Byte-granularity allocator. +pub trait ByteAllocator: BaseAllocator { + /// Allocate memory with the given size (in bytes) and alignment. + fn alloc(&mut self, layout: Layout) -> AllocResult>; + + /// Deallocate memory at the given position, size, and alignment. + fn dealloc(&mut self, pos: NonNull, layout: Layout); + + /// Returns total memory size in bytes. + fn total_bytes(&self) -> usize; + + /// Returns allocated memory size in bytes. + fn used_bytes(&self) -> usize; + + /// Returns available memory size in bytes. + fn available_bytes(&self) -> usize; +} + +/// Page-granularity allocator. +pub trait PageAllocator: BaseAllocator { + /// The size of a memory page. + const PAGE_SIZE: usize; + + /// Allocate contiguous memory pages with given count and alignment. + fn alloc_pages(&mut self, num_pages: usize, align_pow2: usize) -> AllocResult; + + /// Deallocate contiguous memory pages with given position and count. + fn dealloc_pages(&mut self, pos: usize, num_pages: usize); + + /// Returns the total number of memory pages. + fn total_pages(&self) -> usize; + + /// Returns the number of allocated memory pages. + fn used_pages(&self) -> usize; + + /// Returns the number of available memory pages. + fn available_pages(&self) -> usize; +} + +/// Used to allocate unique IDs (e.g., thread ID). +pub trait IdAllocator: BaseAllocator { + /// Allocate contiguous IDs with given count and alignment. + fn alloc_id(&mut self, count: usize, align_pow2: usize) -> AllocResult; + + /// Deallocate contiguous IDs with given position and count. + fn dealloc_id(&mut self, start_id: usize, count: usize); + + /// Whether the given `id` was allocated. + fn is_allocated(&self, id: usize) -> bool; + + /// Mark the given `id` has been allocated and cannot be reallocated. + fn alloc_fixed_id(&mut self, id: usize) -> AllocResult; + + /// Returns the maximum number of supported IDs. + fn size(&self) -> usize; + + /// Returns the number of allocated IDs. + fn used(&self) -> usize; + + /// Returns the number of available IDs. + fn available(&self) -> usize; +} + +#[inline] +const fn align_down(pos: usize, align: usize) -> usize { + pos & !(align - 1) +} + +#[inline] +const fn align_up(pos: usize, align: usize) -> usize { + (pos + align - 1) & !(align - 1) +} + +#[cfg(feature = "allocator_api")] +mod allocator_api { + extern crate alloc; + + use super::ByteAllocator; + use alloc::rc::Rc; + use core::alloc::{AllocError, Allocator, Layout}; + use core::cell::RefCell; + use core::ptr::NonNull; + + /// A byte-allocator wrapped in [`Rc`] that implements [`core::alloc::Allocator`]. + pub struct AllocatorRc(Rc>); + + impl AllocatorRc { + /// Creates a new allocator with the given memory pool. + pub fn new(mut inner: A, pool: &mut [u8]) -> Self { + inner.init(pool.as_mut_ptr() as usize, pool.len()); + Self(Rc::new(RefCell::new(inner))) + } + } + + unsafe impl Allocator for AllocatorRc { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + match layout.size() { + 0 => Ok(NonNull::slice_from_raw_parts(NonNull::dangling(), 0)), + size => { + let raw_addr = self.0.borrow_mut().alloc(layout).map_err(|_| AllocError)?; + Ok(NonNull::slice_from_raw_parts(raw_addr, size)) + } + } + } + + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + self.0.borrow_mut().dealloc(ptr, layout) + } + } + + impl Clone for AllocatorRc { + fn clone(&self) -> Self { + Self(self.0.clone()) + } + } +} + +#[cfg(feature = "allocator_api")] +pub use allocator_api::AllocatorRc; diff --git a/crates/allocator/src/slab.rs b/crates/allocator/src/slab.rs new file mode 100644 index 000000000..17cb164b3 --- /dev/null +++ b/crates/allocator/src/slab.rs @@ -0,0 +1,68 @@ +//! Slab memory allocation. +//! +//! TODO: comments + +use super::{AllocError, AllocResult, BaseAllocator, ByteAllocator}; +use core::alloc::Layout; +use core::ptr::NonNull; +use slab_allocator::Heap; + +/// A byte-granularity memory allocator based on the [slab allocator]. +/// +/// [slab allocator]: ../slab_allocator/index.html +pub struct SlabByteAllocator { + inner: Option, +} + +impl SlabByteAllocator { + /// Creates a new empty `SlabByteAllocator`. + pub const fn new() -> Self { + Self { inner: None } + } + + fn inner_mut(&mut self) -> &mut Heap { + self.inner.as_mut().unwrap() + } + + fn inner(&self) -> &Heap { + self.inner.as_ref().unwrap() + } +} + +impl BaseAllocator for SlabByteAllocator { + fn init(&mut self, start: usize, size: usize) { + self.inner = unsafe { Some(Heap::new(start, size)) }; + } + + fn add_memory(&mut self, start: usize, size: usize) -> AllocResult { + unsafe { + self.inner_mut().add_memory(start, size); + } + Ok(()) + } +} + +impl ByteAllocator for SlabByteAllocator { + fn alloc(&mut self, layout: Layout) -> AllocResult> { + self.inner_mut() + .allocate(layout) + .map(|addr| unsafe { NonNull::new_unchecked(addr as *mut u8) }) + .map_err(|_| AllocError::NoMemory) + } + + fn dealloc(&mut self, pos: NonNull, layout: Layout) { + unsafe { self.inner_mut().deallocate(pos.as_ptr() as usize, layout) } + } + + fn total_bytes(&self) -> usize { + self.inner().total_bytes() + } + + fn used_bytes(&self) -> usize { + self.inner().used_bytes() + } + + fn available_bytes(&self) -> usize { + self.inner().available_bytes() + } +} diff --git a/crates/allocator/src/tlsf.rs b/crates/allocator/src/tlsf.rs new file mode 100644 index 000000000..3d2cd99d9 --- /dev/null +++ b/crates/allocator/src/tlsf.rs @@ -0,0 +1,77 @@ +//! The TLSF (Two-Level Segregated Fit) dynamic memory allocation algorithm. +//! +//! This module wraps the implementation provided by the [rlsf] crate. + +use super::{AllocError, AllocResult, BaseAllocator, ByteAllocator}; +use core::alloc::Layout; +use core::ptr::NonNull; +use rlsf::Tlsf; + +/// A TLSF (Two-Level Segregated Fit) memory allocator. +/// +/// It's just a wrapper structure of [`rlsf::Tlsf`], with `FLLEN` and `SLLEN` +/// fixed to 28 and 32. +pub struct TlsfByteAllocator { + inner: Tlsf<'static, u32, u32, 28, 32>, // max pool size: 32 * 2^28 = 8G + total_bytes: usize, + used_bytes: usize, +} + +impl TlsfByteAllocator { + /// Creates a new empty [`TlsfByteAllocator`]. + pub const fn new() -> Self { + Self { + inner: Tlsf::new(), + total_bytes: 0, + used_bytes: 0, + } + } +} + +impl BaseAllocator for TlsfByteAllocator { + fn init(&mut self, start: usize, size: usize) { + unsafe { + let pool = core::slice::from_raw_parts_mut(start as *mut u8, size); + self.inner + .insert_free_block_ptr(NonNull::new(pool).unwrap()) + .unwrap(); + } + self.total_bytes = size; + } + + fn add_memory(&mut self, start: usize, size: usize) -> AllocResult { + unsafe { + let pool = core::slice::from_raw_parts_mut(start as *mut u8, size); + self.inner + .insert_free_block_ptr(NonNull::new(pool).unwrap()) + .ok_or(AllocError::InvalidParam)?; + } + self.total_bytes += size; + Ok(()) + } +} + +impl ByteAllocator for TlsfByteAllocator { + fn alloc(&mut self, layout: Layout) -> AllocResult> { + let ptr = self.inner.allocate(layout).ok_or(AllocError::NoMemory)?; + self.used_bytes += layout.size(); + Ok(ptr) + } + + fn dealloc(&mut self, pos: NonNull, layout: Layout) { + unsafe { self.inner.deallocate(pos, layout.align()) } + self.used_bytes -= layout.size(); + } + + fn total_bytes(&self) -> usize { + self.total_bytes + } + + fn used_bytes(&self) -> usize { + self.used_bytes + } + + fn available_bytes(&self) -> usize { + self.total_bytes - self.used_bytes + } +} diff --git a/crates/allocator/tests/allocator.rs b/crates/allocator/tests/allocator.rs new file mode 100644 index 000000000..58962b50d --- /dev/null +++ b/crates/allocator/tests/allocator.rs @@ -0,0 +1,143 @@ +#![feature(btreemap_alloc)] +#![feature(allocator_api)] + +use std::alloc::{Allocator, Layout}; +use std::collections::BTreeMap; +use std::io::Write; + +use allocator::{AllocatorRc, BuddyByteAllocator, SlabByteAllocator, TlsfByteAllocator}; +use rand::{prelude::SliceRandom, Rng}; + +const POOL_SIZE: usize = 1024 * 1024 * 128; + +fn test_vec(n: usize, alloc: &(impl Allocator + Clone)) { + let mut v = Vec::with_capacity_in(n, alloc.clone()); + for _ in 0..n { + v.push(rand::random::()); + } + v.sort(); + for i in 0..n - 1 { + assert!(v[i] <= v[i + 1]); + } +} + +fn test_vec2(n: usize, blk_size: usize, alloc: &(impl Allocator + Clone)) { + let mut v = Vec::new_in(alloc.clone()); + for _ in 0..n { + let block = Vec::::with_capacity_in(blk_size, alloc.clone()); + v.push(block); + } + + let mut index = Vec::with_capacity_in(n, alloc.clone()); + for i in 0..n { + index.push(i); + } + index.shuffle(&mut rand::thread_rng()); + + for i in index { + v[i] = Vec::new_in(alloc.clone()) + } +} + +fn test_btree_map(n: usize, alloc: &(impl Allocator + Clone)) { + let mut m = BTreeMap::new_in(alloc.clone()); + for _ in 0..n { + if rand::random::() % 5 == 0 && !m.is_empty() { + m.pop_first(); + } else { + let value = rand::random::(); + let mut key = Vec::new_in(alloc.clone()); + write!(&mut key, "key_{value}").unwrap(); + m.insert(key, value); + } + } + for (k, v) in m.iter() { + let key = std::str::from_utf8(k) + .unwrap() + .strip_prefix("key_") + .unwrap(); + assert_eq!(key.parse::().unwrap(), *v); + } +} + +pub fn test_alignment(n: usize, alloc: &(impl Allocator + Clone)) { + let mut rng = rand::thread_rng(); + let mut blocks = vec![]; + for _ in 0..n { + if rng.gen_ratio(2, 3) || blocks.len() == 0 { + // insert a block + let size = + ((1 << rng.gen_range(0..16)) as f32 * rng.gen_range(1.0..2.0)).round() as usize; + let align = 1 << rng.gen_range(0..8); + let layout = Layout::from_size_align(size, align).unwrap(); + let ptr = alloc.allocate(layout).unwrap(); + blocks.push((ptr, layout)); + } else { + // delete a block + let idx = rng.gen_range(0..blocks.len()); + let blk = blocks.swap_remove(idx); + unsafe { alloc.deallocate(blk.0.cast(), blk.1) }; + } + } + for blk in blocks { + unsafe { alloc.deallocate(blk.0.cast(), blk.1) }; + } +} + +fn run_test(f: impl FnOnce(&mut [u8])) { + let layout = Layout::from_size_align(POOL_SIZE, 4096).unwrap(); + let ptr = unsafe { std::alloc::alloc_zeroed(layout) }; + let pool = unsafe { core::slice::from_raw_parts_mut(ptr, POOL_SIZE) }; + + f(pool); + + unsafe { std::alloc::dealloc(ptr, layout) }; +} + +#[test] +fn system_alloc() { + run_test(|_pool| { + let alloc = std::alloc::System; + test_alignment(50, &alloc); + test_vec(3_000_000, &alloc); + test_vec2(30_000, 64, &alloc); + test_vec2(7_500, 520, &alloc); + test_btree_map(50_000, &alloc); + }) +} + +#[test] +fn buddy_alloc() { + run_test(|pool| { + let alloc = AllocatorRc::new(BuddyByteAllocator::new(), pool); + test_alignment(50, &alloc); + test_vec(3_000_000, &alloc); + test_vec2(30_000, 64, &alloc); + test_vec2(7_500, 520, &alloc); + test_btree_map(50_000, &alloc); + }) +} + +#[test] +fn slab_alloc() { + run_test(|pool| { + let alloc = AllocatorRc::new(SlabByteAllocator::new(), pool); + test_alignment(50, &alloc); + test_vec(3_000_000, &alloc); + test_vec2(30_000, 64, &alloc); + test_vec2(7_500, 520, &alloc); + test_btree_map(50_000, &alloc); + }) +} + +#[test] +fn tlsf_alloc() { + run_test(|pool| { + let alloc = AllocatorRc::new(TlsfByteAllocator::new(), pool); + test_alignment(50, &alloc); + test_vec(3_000_000, &alloc); + test_vec2(30_000, 64, &alloc); + test_vec2(7_500, 520, &alloc); + test_btree_map(50_000, &alloc); + }) +} diff --git a/crates/arm_gic/Cargo.toml b/crates/arm_gic/Cargo.toml new file mode 100644 index 000000000..ce2f06530 --- /dev/null +++ b/crates/arm_gic/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "arm_gic" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "ARM Generic Interrupt Controller (GIC) register definitions and basic operations" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/arm_gic" +documentation = "https://rcore-os.github.io/arceos/arm_gic/index.html" + +[dependencies] +tock-registers = "0.8" diff --git a/crates/arm_gic/src/gic_v2.rs b/crates/arm_gic/src/gic_v2.rs new file mode 100644 index 000000000..9db18f80a --- /dev/null +++ b/crates/arm_gic/src/gic_v2.rs @@ -0,0 +1,275 @@ +//! Types and definitions for GICv2. +//! +//! The official documentation: + +use core::ptr::NonNull; + +use crate::{TriggerMode, GIC_MAX_IRQ, SPI_RANGE}; +use tock_registers::interfaces::{Readable, Writeable}; +use tock_registers::register_structs; +use tock_registers::registers::{ReadOnly, ReadWrite, WriteOnly}; + +register_structs! { + /// GIC Distributor registers. + #[allow(non_snake_case)] + GicDistributorRegs { + /// Distributor Control Register. + (0x0000 => CTLR: ReadWrite), + /// Interrupt Controller Type Register. + (0x0004 => TYPER: ReadOnly), + /// Distributor Implementer Identification Register. + (0x0008 => IIDR: ReadOnly), + (0x000c => _reserved_0), + /// Interrupt Group Registers. + (0x0080 => IGROUPR: [ReadWrite; 0x20]), + /// Interrupt Set-Enable Registers. + (0x0100 => ISENABLER: [ReadWrite; 0x20]), + /// Interrupt Clear-Enable Registers. + (0x0180 => ICENABLER: [ReadWrite; 0x20]), + /// Interrupt Set-Pending Registers. + (0x0200 => ISPENDR: [ReadWrite; 0x20]), + /// Interrupt Clear-Pending Registers. + (0x0280 => ICPENDR: [ReadWrite; 0x20]), + /// Interrupt Set-Active Registers. + (0x0300 => ISACTIVER: [ReadWrite; 0x20]), + /// Interrupt Clear-Active Registers. + (0x0380 => ICACTIVER: [ReadWrite; 0x20]), + /// Interrupt Priority Registers. + (0x0400 => IPRIORITYR: [ReadWrite; 0x100]), + /// Interrupt Processor Targets Registers. + (0x0800 => ITARGETSR: [ReadWrite; 0x100]), + /// Interrupt Configuration Registers. + (0x0c00 => ICFGR: [ReadWrite; 0x40]), + (0x0d00 => _reserved_1), + /// Software Generated Interrupt Register. + (0x0f00 => SGIR: WriteOnly), + (0x0f04 => @END), + } +} + +register_structs! { + /// GIC CPU Interface registers. + #[allow(non_snake_case)] + GicCpuInterfaceRegs { + /// CPU Interface Control Register. + (0x0000 => CTLR: ReadWrite), + /// Interrupt Priority Mask Register. + (0x0004 => PMR: ReadWrite), + /// Binary Point Register. + (0x0008 => BPR: ReadWrite), + /// Interrupt Acknowledge Register. + (0x000c => IAR: ReadOnly), + /// End of Interrupt Register. + (0x0010 => EOIR: WriteOnly), + /// Running Priority Register. + (0x0014 => RPR: ReadOnly), + /// Highest Priority Pending Interrupt Register. + (0x0018 => HPPIR: ReadOnly), + (0x001c => _reserved_1), + /// CPU Interface Identification Register. + (0x00fc => IIDR: ReadOnly), + (0x0100 => _reserved_2), + /// Deactivate Interrupt Register. + (0x1000 => DIR: WriteOnly), + (0x1004 => @END), + } +} + +/// The GIC distributor. +/// +/// The Distributor block performs interrupt prioritization and distribution +/// to the CPU interface blocks that connect to the processors in the system. +/// +/// The Distributor provides a programming interface for: +/// - Globally enabling the forwarding of interrupts to the CPU interfaces. +/// - Enabling or disabling each interrupt. +/// - Setting the priority level of each interrupt. +/// - Setting the target processor list of each interrupt. +/// - Setting each peripheral interrupt to be level-sensitive or edge-triggered. +/// - Setting each interrupt as either Group 0 or Group 1. +/// - Forwarding an SGI to one or more target processors. +/// +/// In addition, the Distributor provides: +/// - visibility of the state of each interrupt +/// - a mechanism for software to set or clear the pending state of a peripheral +/// interrupt. +pub struct GicDistributor { + base: NonNull, + max_irqs: usize, +} + +/// The GIC CPU interface. +/// +/// Each CPU interface block performs priority masking and preemption +/// handling for a connected processor in the system. +/// +/// Each CPU interface provides a programming interface for: +/// +/// - enabling the signaling of interrupt requests to the processor +/// - acknowledging an interrupt +/// - indicating completion of the processing of an interrupt +/// - setting an interrupt priority mask for the processor +/// - defining the preemption policy for the processor +/// - determining the highest priority pending interrupt for the processor. +pub struct GicCpuInterface { + base: NonNull, +} + +unsafe impl Send for GicDistributor {} +unsafe impl Sync for GicDistributor {} + +unsafe impl Send for GicCpuInterface {} +unsafe impl Sync for GicCpuInterface {} + +impl GicDistributor { + /// Construct a new GIC distributor instance from the base address. + pub const fn new(base: *mut u8) -> Self { + Self { + base: NonNull::new(base).unwrap().cast(), + max_irqs: GIC_MAX_IRQ, + } + } + + const fn regs(&self) -> &GicDistributorRegs { + unsafe { self.base.as_ref() } + } + + /// The number of implemented CPU interfaces. + pub fn cpu_num(&self) -> usize { + ((self.regs().TYPER.get() as usize >> 5) & 0b111) + 1 + } + + /// The maximum number of interrupts that the GIC supports + pub fn max_irqs(&self) -> usize { + ((self.regs().TYPER.get() as usize & 0b11111) + 1) * 32 + } + + /// Configures the trigger mode for the given interrupt. + pub fn configure_interrupt(&mut self, vector: usize, tm: TriggerMode) { + // Only configurable for SPI interrupts + if vector >= self.max_irqs || vector < SPI_RANGE.start { + return; + } + + // type is encoded with two bits, MSB of the two determine type + // 16 irqs encoded per ICFGR register + let reg_idx = vector >> 4; + let bit_shift = ((vector & 0xf) << 1) + 1; + let mut reg_val = self.regs().ICFGR[reg_idx].get(); + match tm { + TriggerMode::Edge => reg_val |= 1 << bit_shift, + TriggerMode::Level => reg_val &= !(1 << bit_shift), + } + self.regs().ICFGR[reg_idx].set(reg_val); + } + + /// Enables or disables the given interrupt. + pub fn set_enable(&mut self, vector: usize, enable: bool) { + if vector >= self.max_irqs { + return; + } + let reg = vector / 32; + let mask = 1 << (vector % 32); + if enable { + self.regs().ISENABLER[reg].set(mask); + } else { + self.regs().ICENABLER[reg].set(mask); + } + } + + /// Initializes the GIC distributor. + /// + /// It disables all interrupts, sets the target of all SPIs to CPU 0, + /// configures all SPIs to be edge-triggered, and finally enables the GICD. + /// + /// This function should be called only once. + pub fn init(&mut self) { + let max_irqs = self.max_irqs(); + assert!(max_irqs <= GIC_MAX_IRQ); + self.max_irqs = max_irqs; + + // Disable all interrupts + for i in (0..max_irqs).step_by(32) { + self.regs().ICENABLER[i / 32].set(u32::MAX); + self.regs().ICPENDR[i / 32].set(u32::MAX); + } + if self.cpu_num() > 1 { + for i in (SPI_RANGE.start..max_irqs).step_by(4) { + // Set external interrupts to target cpu 0 + self.regs().ITARGETSR[i / 4].set(0x01_01_01_01); + } + } + // Initialize all the SPIs to edge triggered + for i in SPI_RANGE.start..max_irqs { + self.configure_interrupt(i, TriggerMode::Edge); + } + + // enable GIC0 + self.regs().CTLR.set(1); + } +} + +impl GicCpuInterface { + /// Construct a new GIC CPU interface instance from the base address. + pub const fn new(base: *mut u8) -> Self { + Self { + base: NonNull::new(base).unwrap().cast(), + } + } + + const fn regs(&self) -> &GicCpuInterfaceRegs { + unsafe { self.base.as_ref() } + } + + /// Returns the interrupt ID of the highest priority pending interrupt for + /// the CPU interface. (read GICC_IAR) + /// + /// The read returns a spurious interrupt ID of `1023` if the distributor + /// or the CPU interface are disabled, or there is no pending interrupt on + /// the CPU interface. + pub fn iar(&self) -> u32 { + self.regs().IAR.get() + } + + /// Informs the CPU interface that it has completed the processing of the + /// specified interrupt. (write GICC_EOIR) + /// + /// The value written must be the value returns from [`Self::iar`]. + pub fn eoi(&self, iar: u32) { + self.regs().EOIR.set(iar); + } + + /// handles the signaled interrupt. + /// + /// It first reads GICC_IAR to obtain the pending interrupt ID and then + /// calls the given handler. After the handler returns, it writes GICC_EOIR + /// to acknowledge the interrupt. + /// + /// If read GICC_IAR returns a spurious interrupt ID of `1023`, it does + /// nothing. + pub fn handle_irq(&self, handler: F) + where + F: FnOnce(u32), + { + let iar = self.iar(); + let vector = iar & 0x3ff; + if vector < 1020 { + handler(vector); + self.eoi(iar); + } else { + // spurious + } + } + + /// Initializes the GIC CPU interface. + /// + /// It unmask interrupts at all priority levels and enables the GICC. + /// + /// This function should be called only once. + pub fn init(&self) { + // enable GIC0 + self.regs().CTLR.set(1); + // unmask interrupts at all priority levels + self.regs().PMR.set(0xff); + } +} diff --git a/crates/arm_gic/src/lib.rs b/crates/arm_gic/src/lib.rs new file mode 100644 index 000000000..56e357873 --- /dev/null +++ b/crates/arm_gic/src/lib.rs @@ -0,0 +1,91 @@ +//! ARM Generic Interrupt Controller (GIC) register definitions and basic +//! operations. + +#![no_std] +#![feature(const_ptr_as_ref)] +#![feature(const_option)] +#![feature(const_nonnull_new)] + +pub mod gic_v2; + +use core::ops::Range; + +/// Interrupt ID 0-15 are used for SGIs (Software-generated interrupt). +/// +/// SGI is an interrupt generated by software writing to a GICD_SGIR register in +/// the GIC. The system uses SGIs for interprocessor communication. +pub const SGI_RANGE: Range = 0..16; + +/// Interrupt ID 16-31 are used for PPIs (Private Peripheral Interrupt). +/// +/// PPI is a peripheral interrupt that is specific to a single processor. +pub const PPI_RANGE: Range = 16..32; + +/// Interrupt ID 32-1019 are used for SPIs (Shared Peripheral Interrupt). +/// +/// SPI is a peripheral interrupt that the Distributor can route to any of a +/// specified combination of processors. +pub const SPI_RANGE: Range = 32..1020; + +/// Maximum number of interrupts supported by the GIC. +pub const GIC_MAX_IRQ: usize = 1024; + +/// Interrupt trigger mode. +pub enum TriggerMode { + /// Edge-triggered. + /// + /// This is an interrupt that is asserted on detection of a rising edge of + /// an interrupt signal and then, regardless of the state of the signal, + /// remains asserted until it is cleared by the conditions defined by this + /// specification. + Edge = 0, + /// Level-sensitive. + /// + /// This is an interrupt that is asserted whenever the interrupt signal + /// level is active, and deasserted whenever the level is not active. + Level = 1, +} + +/// Different types of interrupt that the GIC handles. +pub enum InterruptType { + /// Software-generated interrupt. + /// + /// SGIs are typically used for inter-processor communication and are + /// generated by a write to an SGI register in the GIC. + SGI, + /// Private Peripheral Interrupt. + /// + /// Peripheral interrupts that are private to one core. + PPI, + /// Shared Peripheral Interrupt. + /// + /// Peripheral interrupts that can delivered to any connected core. + SPI, +} + +/// Translate an interrupt of a given type to a GIC INTID. +pub const fn translate_irq(id: usize, int_type: InterruptType) -> Option { + match int_type { + InterruptType::SGI => { + if id < SGI_RANGE.end { + Some(id) + } else { + None + } + } + InterruptType::PPI => { + if id < PPI_RANGE.end - PPI_RANGE.start { + Some(id + PPI_RANGE.start) + } else { + None + } + } + InterruptType::SPI => { + if id < SPI_RANGE.end - SPI_RANGE.start { + Some(id + SPI_RANGE.start) + } else { + None + } + } + } +} diff --git a/crates/arm_pl011/Cargo.toml b/crates/arm_pl011/Cargo.toml new file mode 100644 index 000000000..49452ed92 --- /dev/null +++ b/crates/arm_pl011/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "arm_pl011" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "ARM Uart pl011 register definitions and basic operations" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/arm_pl011" +documentation = "https://rcore-os.github.io/arceos/arm_pl011/index.html" + +[dependencies] +tock-registers = "0.8" \ No newline at end of file diff --git a/crates/arm_pl011/src/lib.rs b/crates/arm_pl011/src/lib.rs new file mode 100644 index 000000000..60c062e4f --- /dev/null +++ b/crates/arm_pl011/src/lib.rs @@ -0,0 +1,8 @@ +//! Definitions for PL011 UART. + +#![no_std] +#![feature(const_ptr_as_ref)] +#![feature(const_option)] +#![feature(const_nonnull_new)] + +pub mod pl011; diff --git a/crates/arm_pl011/src/pl011.rs b/crates/arm_pl011/src/pl011.rs new file mode 100644 index 000000000..cdbb7a37b --- /dev/null +++ b/crates/arm_pl011/src/pl011.rs @@ -0,0 +1,107 @@ +//! Types and definitions for PL011 UART. +//! +//! The official documentation: + +use core::ptr::NonNull; + +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::{ReadOnly, ReadWrite, WriteOnly}, +}; + +register_structs! { + /// Pl011 registers. + Pl011UartRegs { + /// Data Register. + (0x00 => dr: ReadWrite), + (0x04 => _reserved0), + /// Flag Register. + (0x18 => fr: ReadOnly), + (0x1c => _reserved1), + /// Control register. + (0x30 => cr: ReadWrite), + /// Interrupt FIFO Level Select Register. + (0x34 => ifls: ReadWrite), + /// Interrupt Mask Set Clear Register. + (0x38 => imsc: ReadWrite), + /// Raw Interrupt Status Register. + (0x3c => ris: ReadOnly), + /// Masked Interrupt Status Register. + (0x40 => mis: ReadOnly), + /// Interrupt Clear Register. + (0x44 => icr: WriteOnly), + (0x48 => @END), + } +} + +/// The Pl011 Uart +/// +/// The Pl011 Uart provides a programing interface for: +/// 1. Construct a new Pl011 UART instance +/// 2. Initialize the Pl011 UART +/// 3. Read a char from the UART +/// 4. Write a char to the UART +/// 5. Handle a UART IRQ +pub struct Pl011Uart { + base: NonNull, +} + +unsafe impl Send for Pl011Uart {} +unsafe impl Sync for Pl011Uart {} + +impl Pl011Uart { + /// Constrcut a new Pl011 UART instance from the base address. + pub const fn new(base: *mut u8) -> Self { + Self { + base: NonNull::new(base).unwrap().cast(), + } + } + + const fn regs(&self) -> &Pl011UartRegs { + unsafe { self.base.as_ref() } + } + + /// Initializes the Pl011 UART. + /// + /// It clears all irqs, sets fifo trigger level, enables rx interrupt, enables receives + pub fn init(&mut self) { + // clear all irqs + self.regs().icr.set(0x7ff); + + // set fifo trigger level + self.regs().ifls.set(0); // 1/8 rxfifo, 1/8 txfifo. + + // enable rx interrupt + self.regs().imsc.set(1 << 4); // rxim + + // enable receive + self.regs().cr.set((1 << 0) | (1 << 8) | (1 << 9)); // tx enable, rx enable, uart enable + } + + /// Output a char c to data register + pub fn putchar(&mut self, c: u8) { + while self.regs().fr.get() & (1 << 5) != 0 {} + self.regs().dr.set(c as u32); + } + + /// Return a byte if pl011 has received, or it will return `None`. + pub fn getchar(&mut self) -> Option { + if self.regs().fr.get() & (1 << 4) == 0 { + Some(self.regs().dr.get() as u8) + } else { + None + } + } + + /// Return true if pl011 has received an interrupt + pub fn is_receive_interrupt(&self) -> bool { + let pending = self.regs().mis.get(); + pending & (1 << 4) != 0 + } + + /// Clear all interrupts + pub fn ack_interrupts(&mut self) { + self.regs().icr.set(0x7ff); + } +} diff --git a/crates/axerrno/.gitignore b/crates/axerrno/.gitignore new file mode 100644 index 000000000..59e82d70b --- /dev/null +++ b/crates/axerrno/.gitignore @@ -0,0 +1 @@ +src/linux_errno.rs diff --git a/crates/axerrno/Cargo.toml b/crates/axerrno/Cargo.toml new file mode 100644 index 000000000..e686496e8 --- /dev/null +++ b/crates/axerrno/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "axerrno" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Error code definition used by ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/axerrno" +documentation = "https://rcore-os.github.io/arceos/axerrno/index.html" + +[dependencies] +log = "0.4" diff --git a/crates/axerrno/build.rs b/crates/axerrno/build.rs new file mode 100644 index 000000000..51320f152 --- /dev/null +++ b/crates/axerrno/build.rs @@ -0,0 +1,86 @@ +use std::env; +use std::fs::{self, File}; +use std::io::{BufRead, BufReader, Result, Write}; +use std::path::Path; + +macro_rules! template { + () => { + "\ +// Generated by build.rs, DO NOT edit + +/// Linux specific error codes defined in `errno.h`. +#[repr(i32)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum LinuxError {{ +{0}\ +}} + +impl TryFrom for LinuxError {{ + type Error = i32; + + fn try_from(value: i32) -> Result {{ + use self::LinuxError::*; + match value {{ +{1} _ => Err(value), + }} + }} +}} + +impl LinuxError {{ + /// Returns the error description. + pub const fn as_str(&self) -> &'static str {{ + use self::LinuxError::*; + match self {{ +{2} }} + }} + + /// Returns the error code value in `i32`. + pub const fn code(self) -> i32 {{ + self as i32 + }} +}} +" + }; +} + +fn main() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + gen_linux_errno(&Path::new(&out_dir).join("linux_errno.rs")).unwrap(); +} + +fn gen_linux_errno(dest_path: &Path) -> Result<()> { + let mut enum_define = Vec::new(); + let mut try_from_i32 = Vec::new(); + let mut detail_info = Vec::new(); + + let file = File::open("src/errno.h")?; + for line in BufReader::new(file).lines().map_while(Result::ok) { + if line.starts_with("#define") { + let mut iter = line.split_whitespace(); + if let Some(name) = iter.nth(1) { + if let Some(num) = iter.next() { + let description = if let Some(pos) = line.find("/* ") { + String::from(line[pos + 3..].trim_end_matches(" */")) + } else { + format!("Error number {num}") + }; + writeln!(enum_define, " /// {description}\n {name} = {num},")?; + writeln!(try_from_i32, " {num} => Ok({name}),")?; + writeln!(detail_info, " {name} => \"{description}\",")?; + } + } + } + } + + fs::write( + dest_path, + format!( + template!(), + String::from_utf8_lossy(&enum_define), + String::from_utf8_lossy(&try_from_i32), + String::from_utf8_lossy(&detail_info) + ), + )?; + + Ok(()) +} diff --git a/crates/axerrno/src/errno.h b/crates/axerrno/src/errno.h new file mode 100644 index 000000000..581321174 --- /dev/null +++ b/crates/axerrno/src/errno.h @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* Copy from /usr/include/asm-generic/errno-base.h and /usr/include/asm-generic/errno.h */ +#ifndef _ASM_GENERIC_ERRNO_BASE_H +#define _ASM_GENERIC_ERRNO_BASE_H + +#define EPERM 1 /* Operation not permitted */ +#define ENOENT 2 /* No such file or directory */ +#define ESRCH 3 /* No such process */ +#define EINTR 4 /* Interrupted system call */ +#define EIO 5 /* I/O error */ +#define ENXIO 6 /* No such device or address */ +#define E2BIG 7 /* Argument list too long */ +#define ENOEXEC 8 /* Exec format error */ +#define EBADF 9 /* Bad file number */ +#define ECHILD 10 /* No child processes */ +#define EAGAIN 11 /* Try again */ +#define ENOMEM 12 /* Out of memory */ +#define EACCES 13 /* Permission denied */ +#define EFAULT 14 /* Bad address */ +#define ENOTBLK 15 /* Block device required */ +#define EBUSY 16 /* Device or resource busy */ +#define EEXIST 17 /* File exists */ +#define EXDEV 18 /* Cross-device link */ +#define ENODEV 19 /* No such device */ +#define ENOTDIR 20 /* Not a directory */ +#define EISDIR 21 /* Is a directory */ +#define EINVAL 22 /* Invalid argument */ +#define ENFILE 23 /* File table overflow */ +#define EMFILE 24 /* Too many open files */ +#define ENOTTY 25 /* Not a typewriter */ +#define ETXTBSY 26 /* Text file busy */ +#define EFBIG 27 /* File too large */ +#define ENOSPC 28 /* No space left on device */ +#define ESPIPE 29 /* Illegal seek */ +#define EROFS 30 /* Read-only file system */ +#define EMLINK 31 /* Too many links */ +#define EPIPE 32 /* Broken pipe */ +#define EDOM 33 /* Math argument out of domain of func */ +#define ERANGE 34 /* Math result not representable */ +#define EDEADLK 35 /* Resource deadlock would occur */ +#define ENAMETOOLONG 36 /* File name too long */ +#define ENOLCK 37 /* No record locks available */ + +/* + * This error code is special: arch syscall entry code will return + * -ENOSYS if users try to call a syscall that doesn't exist. To keep + * failures of syscalls that really do exist distinguishable from + * failures due to attempts to use a nonexistent syscall, syscall + * implementations should refrain from returning -ENOSYS. + */ +#define ENOSYS 38 /* Invalid system call number */ +#define ENOTEMPTY 39 /* Directory not empty */ +#define ELOOP 40 /* Too many symbolic links encountered */ +// #define EWOULDBLOCK EAGAIN /* Operation would block */ +#define ENOMSG 42 /* No message of desired type */ +#define EIDRM 43 /* Identifier removed */ +#define ECHRNG 44 /* Channel number out of range */ +#define EL2NSYNC 45 /* Level 2 not synchronized */ +#define EL3HLT 46 /* Level 3 halted */ +#define EL3RST 47 /* Level 3 reset */ +#define ELNRNG 48 /* Link number out of range */ +#define EUNATCH 49 /* Protocol driver not attached */ +#define ENOCSI 50 /* No CSI structure available */ +#define EL2HLT 51 /* Level 2 halted */ +#define EBADE 52 /* Invalid exchange */ +#define EBADR 53 /* Invalid request descriptor */ +#define EXFULL 54 /* Exchange full */ +#define ENOANO 55 /* No anode */ +#define EBADRQC 56 /* Invalid request code */ +#define EBADSLT 57 /* Invalid slot */ +// #define EDEADLOCK EDEADLK +#define EBFONT 59 /* Bad font file format */ +#define ENOSTR 60 /* Device not a stream */ +#define ENODATA 61 /* No data available */ +#define ETIME 62 /* Timer expired */ +#define ENOSR 63 /* Out of streams resources */ +#define ENONET 64 /* Machine is not on the network */ +#define ENOPKG 65 /* Package not installed */ +#define EREMOTE 66 /* Object is remote */ +#define ENOLINK 67 /* Link has been severed */ +#define EADV 68 /* Advertise error */ +#define ESRMNT 69 /* Srmount error */ +#define ECOMM 70 /* Communication error on send */ +#define EPROTO 71 /* Protocol error */ +#define EMULTIHOP 72 /* Multihop attempted */ +#define EDOTDOT 73 /* RFS specific error */ +#define EBADMSG 74 /* Not a data message */ +#define EOVERFLOW 75 /* Value too large for defined data type */ +#define ENOTUNIQ 76 /* Name not unique on network */ +#define EBADFD 77 /* File descriptor in bad state */ +#define EREMCHG 78 /* Remote address changed */ +#define ELIBACC 79 /* Can not access a needed shared library */ +#define ELIBBAD 80 /* Accessing a corrupted shared library */ +#define ELIBSCN 81 /* .lib section in a.out corrupted */ +#define ELIBMAX 82 /* Attempting to link in too many shared libraries */ +#define ELIBEXEC 83 /* Cannot exec a shared library directly */ +#define EILSEQ 84 /* Illegal byte sequence */ +#define ERESTART 85 /* Interrupted system call should be restarted */ +#define ESTRPIPE 86 /* Streams pipe error */ +#define EUSERS 87 /* Too many users */ +#define ENOTSOCK 88 /* Socket operation on non-socket */ +#define EDESTADDRREQ 89 /* Destination address required */ +#define EMSGSIZE 90 /* Message too long */ +#define EPROTOTYPE 91 /* Protocol wrong type for socket */ +#define ENOPROTOOPT 92 /* Protocol not available */ +#define EPROTONOSUPPORT 93 /* Protocol not supported */ +#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ +#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ +#define EPFNOSUPPORT 96 /* Protocol family not supported */ +#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ +#define EADDRINUSE 98 /* Address already in use */ +#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ +#define ENETDOWN 100 /* Network is down */ +#define ENETUNREACH 101 /* Network is unreachable */ +#define ENETRESET 102 /* Network dropped connection because of reset */ +#define ECONNABORTED 103 /* Software caused connection abort */ +#define ECONNRESET 104 /* Connection reset by peer */ +#define ENOBUFS 105 /* No buffer space available */ +#define EISCONN 106 /* Transport endpoint is already connected */ +#define ENOTCONN 107 /* Transport endpoint is not connected */ +#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ +#define ETOOMANYREFS 109 /* Too many references: cannot splice */ +#define ETIMEDOUT 110 /* Connection timed out */ +#define ECONNREFUSED 111 /* Connection refused */ +#define EHOSTDOWN 112 /* Host is down */ +#define EHOSTUNREACH 113 /* No route to host */ +#define EALREADY 114 /* Operation already in progress */ +#define EINPROGRESS 115 /* Operation now in progress */ +#define ESTALE 116 /* Stale file handle */ +#define EUCLEAN 117 /* Structure needs cleaning */ +#define ENOTNAM 118 /* Not a XENIX named type file */ +#define ENAVAIL 119 /* No XENIX semaphores available */ +#define EISNAM 120 /* Is a named type file */ +#define EREMOTEIO 121 /* Remote I/O error */ +#define EDQUOT 122 /* Quota exceeded */ + +#define ENOMEDIUM 123 /* No medium found */ +#define EMEDIUMTYPE 124 /* Wrong medium type */ +#define ECANCELED 125 /* Operation Canceled */ +#define ENOKEY 126 /* Required key not available */ +#define EKEYEXPIRED 127 /* Key has expired */ +#define EKEYREVOKED 128 /* Key has been revoked */ +#define EKEYREJECTED 129 /* Key was rejected by service */ +#define EOWNERDEAD 130 /* Owner died */ +#define ENOTRECOVERABLE 131 /* State not recoverable */ +#define ERFKILL 132 /* Operation not possible due to RF-kill */ +#define EHWPOISON 133 /* Memory page has hardware error */ + + +#endif diff --git a/crates/axerrno/src/lib.rs b/crates/axerrno/src/lib.rs new file mode 100644 index 000000000..986040cb4 --- /dev/null +++ b/crates/axerrno/src/lib.rs @@ -0,0 +1,298 @@ +//! Error code definition used by [ArceOS](https://github.com/rcore-os/arceos). +//! +//! It provides two error types and the corresponding result types: +//! +//! - [`AxError`] and [`AxResult`]: A generic error type similar to +//! [`std::io::ErrorKind`]. +//! - [`LinuxError`] and [`LinuxResult`]: Linux specific error codes defined in +//! `errno.h`. It can be converted from [`AxError`]. +//! +//! [`std::io::ErrorKind`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html + +#![no_std] +#![feature(variant_count)] + +use core::fmt; + +mod linux_errno { + include!(concat!(env!("OUT_DIR"), "/linux_errno.rs")); +} + +pub use linux_errno::LinuxError; + +/// The error type used by ArceOS. +/// +/// Similar to [`std::io::ErrorKind`]. +/// +/// [`std::io::ErrorKind`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html +#[repr(i32)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AxError { + /// A socket address could not be bound because the address is already in use elsewhere. + AddrInUse = 1, + /// An entity already exists, often a file. + AlreadyExists, + /// Bad address. + BadAddress, + /// Bad internal state. + BadState, + /// The connection was refused by the remote server, + ConnectionRefused, + /// The connection was reset by the remote server. + ConnectionReset, + /// A non-empty directory was specified where an empty directory was expected. + DirectoryNotEmpty, + /// Data not valid for the operation were encountered. + /// + /// Unlike [`InvalidInput`], this typically means that the operation + /// parameters were valid, however the error was caused by malformed + /// input data. + /// + /// For example, a function that reads a file into a string will error with + /// `InvalidData` if the file's contents are not valid UTF-8. + /// + /// [`InvalidInput`]: AxError::InvalidInput + InvalidData, + /// Invalid parameter/argument. + InvalidInput, + /// Input/output error. + Io, + /// The filesystem object is, unexpectedly, a directory. + IsADirectory, + /// Not enough space/cannot allocate memory. + NoMemory, + /// A filesystem object is, unexpectedly, not a directory. + NotADirectory, + /// The network operation failed because it was not connected yet. + NotConnected, + /// The requested entity is not found. + NotFound, + /// The operation lacked the necessary privileges to complete. + PermissionDenied, + /// Device or resource is busy. + ResourceBusy, + /// The underlying storage (typically, a filesystem) is full. + StorageFull, + /// An error returned when an operation could not be completed because an + /// "end of file" was reached prematurely. + UnexpectedEof, + /// This operation is unsupported or unimplemented. + Unsupported, + /// The operation needs to block to complete, but the blocking operation was + /// requested to not occur. + WouldBlock, + /// An error returned when an operation could not be completed because a + /// call to `write()` returned [`Ok(0)`](Ok). + WriteZero, +} + +/// A specialized [`Result`] type with [`AxError`] as the error type. +pub type AxResult = Result; + +/// A specialized [`Result`] type with [`LinuxError`] as the error type. +pub type LinuxResult = Result; + +/// Convenience method to construct an [`AxError`] type while printing a warning +/// message. +/// +/// # Examples +/// +/// ``` +/// # use axerrno::{ax_err_type, AxError}; +/// # +/// // Also print "[AxError::AlreadyExists]" if the `log` crate is enabled. +/// assert_eq!( +/// ax_err_type!(AlreadyExists), +/// AxError::AlreadyExists, +/// ); +/// +/// // Also print "[AxError::BadAddress] the address is 0!" if the `log` crate +/// // is enabled. +/// assert_eq!( +/// ax_err_type!(BadAddress, "the address is 0!"), +/// AxError::BadAddress, +/// ); +/// ``` +#[macro_export] +macro_rules! ax_err_type { + ($err: ident) => {{ + use $crate::AxError::*; + $crate::__priv::warn!("[AxError::{:?}]", $err); + $err + }}; + ($err: ident, $msg: expr) => {{ + use $crate::AxError::*; + $crate::__priv::warn!("[AxError::{:?}] {}", $err, $msg); + $err + }}; +} + +/// Ensure a condition is true. If it is not, return from the function +/// with an error. +/// +/// ## Examples +/// +/// ```rust +/// # use axerrno::{ensure, ax_err, AxError, AxResult}; +/// +/// fn example(user_id: i32) -> AxResult { +/// ensure!(user_id > 0, ax_err!(InvalidInput)); +/// // After this point, we know that `user_id` is positive. +/// let user_id = user_id as u32; +/// Ok(()) +/// } +/// ``` +#[macro_export] +macro_rules! ensure { + ($predicate:expr, $context_selector:expr $(,)?) => { + if !$predicate { + return $context_selector; + } + }; +} + +/// Convenience method to construct an [`Err(AxError)`] type while printing a +/// warning message. +/// +/// # Examples +/// +/// ``` +/// # use axerrno::{ax_err, AxResult, AxError}; +/// # +/// // Also print "[AxError::AlreadyExists]" if the `log` crate is enabled. +/// assert_eq!( +/// ax_err!(AlreadyExists), +/// AxResult::<()>::Err(AxError::AlreadyExists), +/// ); +/// +/// // Also print "[AxError::BadAddress] the address is 0!" if the `log` crate is enabled. +/// assert_eq!( +/// ax_err!(BadAddress, "the address is 0!"), +/// AxResult::<()>::Err(AxError::BadAddress), +/// ); +/// ``` +/// [`Err(AxError)`]: Err +#[macro_export] +macro_rules! ax_err { + ($err: ident) => { + Err($crate::ax_err_type!($err)) + }; + ($err: ident, $msg: expr) => { + Err($crate::ax_err_type!($err, $msg)) + }; +} + +impl AxError { + /// Returns the error description. + pub fn as_str(&self) -> &'static str { + use AxError::*; + match *self { + AddrInUse => "Address in use", + BadAddress => "Bad address", + BadState => "Bad internal state", + AlreadyExists => "Entity already exists", + ConnectionRefused => "Connection refused", + ConnectionReset => "Connection reset", + DirectoryNotEmpty => "Directory not empty", + InvalidData => "Invalid data", + InvalidInput => "Invalid input parameter", + Io => "I/O error", + IsADirectory => "Is a directory", + NoMemory => "Out of memory", + NotADirectory => "Not a directory", + NotConnected => "Not connected", + NotFound => "Entity not found", + PermissionDenied => "Permission denied", + ResourceBusy => "Resource busy", + StorageFull => "No storage space", + UnexpectedEof => "Unexpected end of file", + Unsupported => "Operation not supported", + WouldBlock => "Operation would block", + WriteZero => "Write zero", + } + } + + /// Returns the error code value in `i32`. + pub const fn code(self) -> i32 { + self as i32 + } +} + +impl TryFrom for AxError { + type Error = i32; + + #[inline] + fn try_from(value: i32) -> Result { + if value > 0 && value <= core::mem::variant_count::() as i32 { + Ok(unsafe { core::mem::transmute(value) }) + } else { + Err(value) + } + } +} + +impl fmt::Display for AxError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl From for LinuxError { + fn from(e: AxError) -> Self { + use AxError::*; + match e { + AddrInUse => LinuxError::EADDRINUSE, + AlreadyExists => LinuxError::EEXIST, + BadAddress | BadState => LinuxError::EFAULT, + ConnectionRefused => LinuxError::ECONNREFUSED, + ConnectionReset => LinuxError::ECONNRESET, + DirectoryNotEmpty => LinuxError::ENOTEMPTY, + InvalidInput | InvalidData => LinuxError::EINVAL, + Io => LinuxError::EIO, + IsADirectory => LinuxError::EISDIR, + NoMemory => LinuxError::ENOMEM, + NotADirectory => LinuxError::ENOTDIR, + NotConnected => LinuxError::ENOTCONN, + NotFound => LinuxError::ENOENT, + PermissionDenied => LinuxError::EACCES, + ResourceBusy => LinuxError::EBUSY, + StorageFull => LinuxError::ENOSPC, + Unsupported => LinuxError::ENOSYS, + UnexpectedEof | WriteZero => LinuxError::EIO, + WouldBlock => LinuxError::EAGAIN, + } + } +} + +impl fmt::Display for LinuxError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +#[doc(hidden)] +pub mod __priv { + pub use log::warn; +} + +#[cfg(test)] +mod tests { + use crate::AxError; + + #[test] + fn test_try_from() { + let max_code = core::mem::variant_count::() as i32; + assert_eq!(max_code, 22); + assert_eq!(max_code, AxError::WriteZero.code()); + + assert_eq!(AxError::AddrInUse.code(), 1); + assert_eq!(Ok(AxError::AddrInUse), AxError::try_from(1)); + assert_eq!(Ok(AxError::AlreadyExists), AxError::try_from(2)); + assert_eq!(Ok(AxError::WriteZero), AxError::try_from(max_code)); + assert_eq!(Err(max_code + 1), AxError::try_from(max_code + 1)); + assert_eq!(Err(0), AxError::try_from(0)); + assert_eq!(Err(-1), AxError::try_from(-1)); + assert_eq!(Err(i32::MAX), AxError::try_from(i32::MAX)); + } +} diff --git a/crates/axfs_devfs/Cargo.toml b/crates/axfs_devfs/Cargo.toml new file mode 100644 index 000000000..c618c80bb --- /dev/null +++ b/crates/axfs_devfs/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "axfs_devfs" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Device filesystem used by ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/axfs_devfs" +documentation = "https://rcore-os.github.io/arceos/axfs_devfs/index.html" + +[dependencies] +axfs_vfs = { path = "../axfs_vfs" } +spin = "0.9" +log = "0.4" diff --git a/crates/axfs_devfs/src/dir.rs b/crates/axfs_devfs/src/dir.rs new file mode 100644 index 000000000..69a6b8380 --- /dev/null +++ b/crates/axfs_devfs/src/dir.rs @@ -0,0 +1,138 @@ +use alloc::collections::BTreeMap; +use alloc::sync::{Arc, Weak}; +use axfs_vfs::{VfsDirEntry, VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType}; +use axfs_vfs::{VfsError, VfsResult}; +use spin::RwLock; + +/// The directory node in the device filesystem. +/// +/// It implements [`axfs_vfs::VfsNodeOps`]. +pub struct DirNode { + parent: RwLock>, + children: RwLock>, +} + +impl DirNode { + pub(super) fn new(parent: Option<&VfsNodeRef>) -> Arc { + let parent = parent.map_or(Weak::::new() as _, Arc::downgrade); + Arc::new(Self { + parent: RwLock::new(parent), + children: RwLock::new(BTreeMap::new()), + }) + } + + pub(super) fn set_parent(&self, parent: Option<&VfsNodeRef>) { + *self.parent.write() = parent.map_or(Weak::::new() as _, Arc::downgrade); + } + + /// Create a subdirectory at this directory. + pub fn mkdir(self: &Arc, name: &'static str) -> Arc { + let parent = self.clone() as VfsNodeRef; + let node = Self::new(Some(&parent)); + self.children.write().insert(name, node.clone()); + node + } + + /// Add a node to this directory. + pub fn add(&self, name: &'static str, node: VfsNodeRef) { + self.children.write().insert(name, node); + } +} + +impl VfsNodeOps for DirNode { + fn get_attr(&self) -> VfsResult { + Ok(VfsNodeAttr::new_dir(4096, 0)) + } + + fn parent(&self) -> Option { + self.parent.read().upgrade() + } + + fn lookup(self: Arc, path: &str) -> VfsResult { + let (name, rest) = split_path(path); + let node = match name { + "" | "." => Ok(self.clone() as VfsNodeRef), + ".." => self.parent().ok_or(VfsError::NotFound), + _ => self + .children + .read() + .get(name) + .cloned() + .ok_or(VfsError::NotFound), + }?; + + if let Some(rest) = rest { + node.lookup(rest) + } else { + Ok(node) + } + } + + fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { + let children = self.children.read(); + let mut children = children.iter().skip(start_idx.max(2) - 2); + for (i, ent) in dirents.iter_mut().enumerate() { + match i + start_idx { + 0 => *ent = VfsDirEntry::new(".", VfsNodeType::Dir), + 1 => *ent = VfsDirEntry::new("..", VfsNodeType::Dir), + _ => { + if let Some((name, node)) = children.next() { + *ent = VfsDirEntry::new(name, node.get_attr().unwrap().file_type()); + } else { + return Ok(i); + } + } + } + } + Ok(dirents.len()) + } + + fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { + log::debug!("create {:?} at devfs: {}", ty, path); + let (name, rest) = split_path(path); + if let Some(rest) = rest { + match name { + "" | "." => self.create(rest, ty), + ".." => self.parent().ok_or(VfsError::NotFound)?.create(rest, ty), + _ => self + .children + .read() + .get(name) + .ok_or(VfsError::NotFound)? + .create(rest, ty), + } + } else if name.is_empty() || name == "." || name == ".." { + Ok(()) // already exists + } else { + Err(VfsError::PermissionDenied) // do not support to create nodes dynamically + } + } + + fn remove(&self, path: &str) -> VfsResult { + log::debug!("remove at devfs: {}", path); + let (name, rest) = split_path(path); + if let Some(rest) = rest { + match name { + "" | "." => self.remove(rest), + ".." => self.parent().ok_or(VfsError::NotFound)?.remove(rest), + _ => self + .children + .read() + .get(name) + .ok_or(VfsError::NotFound)? + .remove(rest), + } + } else { + Err(VfsError::PermissionDenied) // do not support to remove nodes dynamically + } + } + + axfs_vfs::impl_vfs_dir_default! {} +} + +fn split_path(path: &str) -> (&str, Option<&str>) { + let trimmed_path = path.trim_start_matches('/'); + trimmed_path.find('/').map_or((trimmed_path, None), |n| { + (&trimmed_path[..n], Some(&trimmed_path[n + 1..])) + }) +} diff --git a/crates/axfs_devfs/src/lib.rs b/crates/axfs_devfs/src/lib.rs new file mode 100644 index 000000000..3e6102374 --- /dev/null +++ b/crates/axfs_devfs/src/lib.rs @@ -0,0 +1,71 @@ +//! Device filesystem used by [ArceOS](https://github.com/rcore-os/arceos). +//! +//! The implementation is based on [`axfs_vfs`]. + +#![cfg_attr(not(test), no_std)] + +extern crate alloc; + +mod dir; +mod null; +mod zero; + +#[cfg(test)] +mod tests; + +pub use self::dir::DirNode; +pub use self::null::NullDev; +pub use self::zero::ZeroDev; + +use alloc::sync::Arc; +use axfs_vfs::{VfsNodeRef, VfsOps, VfsResult}; +use spin::once::Once; + +/// A device filesystem that implements [`axfs_vfs::VfsOps`]. +pub struct DeviceFileSystem { + parent: Once, + root: Arc, +} + +impl DeviceFileSystem { + /// Create a new instance. + pub fn new() -> Self { + Self { + parent: Once::new(), + root: DirNode::new(None), + } + } + + /// Create a subdirectory at the root directory. + pub fn mkdir(&self, name: &'static str) -> Arc { + self.root.mkdir(name) + } + + /// Add a node to the root directory. + /// + /// The node must implement [`axfs_vfs::VfsNodeOps`], and be wrapped in [`Arc`]. + pub fn add(&self, name: &'static str, node: VfsNodeRef) { + self.root.add(name, node); + } +} + +impl VfsOps for DeviceFileSystem { + fn mount(&self, _path: &str, mount_point: VfsNodeRef) -> VfsResult { + if let Some(parent) = mount_point.parent() { + self.root.set_parent(Some(self.parent.call_once(|| parent))); + } else { + self.root.set_parent(None); + } + Ok(()) + } + + fn root_dir(&self) -> VfsNodeRef { + self.root.clone() + } +} + +impl Default for DeviceFileSystem { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/axfs_devfs/src/null.rs b/crates/axfs_devfs/src/null.rs new file mode 100644 index 000000000..a86eddb77 --- /dev/null +++ b/crates/axfs_devfs/src/null.rs @@ -0,0 +1,31 @@ +use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodePerm, VfsNodeType, VfsResult}; + +/// A null device behaves like `/dev/null`. +/// +/// Nothing can be read and all writes are discarded. +pub struct NullDev; + +impl VfsNodeOps for NullDev { + fn get_attr(&self) -> VfsResult { + Ok(VfsNodeAttr::new( + VfsNodePerm::default_file(), + VfsNodeType::CharDevice, + 0, + 0, + )) + } + + fn read_at(&self, _offset: u64, _buf: &mut [u8]) -> VfsResult { + Ok(0) + } + + fn write_at(&self, _offset: u64, buf: &[u8]) -> VfsResult { + Ok(buf.len()) + } + + fn truncate(&self, _size: u64) -> VfsResult { + Ok(()) + } + + axfs_vfs::impl_vfs_non_dir_default! {} +} diff --git a/crates/axfs_devfs/src/tests.rs b/crates/axfs_devfs/src/tests.rs new file mode 100644 index 000000000..cfc6e9f1b --- /dev/null +++ b/crates/axfs_devfs/src/tests.rs @@ -0,0 +1,115 @@ +use std::sync::Arc; + +use axfs_vfs::{VfsError, VfsNodeType, VfsResult}; + +use crate::*; + +fn test_devfs_ops(devfs: &DeviceFileSystem) -> VfsResult { + const N: usize = 32; + let mut buf = [1; N]; + + let root = devfs.root_dir(); + assert!(root.get_attr()?.is_dir()); + assert_eq!(root.get_attr()?.file_type(), VfsNodeType::Dir); + assert_eq!( + root.clone().lookup("urandom").err(), + Some(VfsError::NotFound) + ); + assert_eq!( + root.clone().lookup("zero/").err(), + Some(VfsError::NotADirectory) + ); + + let node = root.lookup("////null")?; + assert_eq!(node.get_attr()?.file_type(), VfsNodeType::CharDevice); + assert!(!node.get_attr()?.is_dir()); + assert_eq!(node.get_attr()?.size(), 0); + assert_eq!(node.read_at(0, &mut buf)?, 0); + assert_eq!(buf, [1; N]); + assert_eq!(node.write_at(N as _, &buf)?, N); + assert_eq!(node.lookup("/").err(), Some(VfsError::NotADirectory)); + + let node = devfs.root_dir().lookup(".///.//././/.////zero")?; + assert_eq!(node.get_attr()?.file_type(), VfsNodeType::CharDevice); + assert!(!node.get_attr()?.is_dir()); + assert_eq!(node.get_attr()?.size(), 0); + assert_eq!(node.read_at(10, &mut buf)?, N); + assert_eq!(buf, [0; N]); + assert_eq!(node.write_at(0, &buf)?, N); + + let foo = devfs.root_dir().lookup(".///.//././/.////foo")?; + assert!(foo.get_attr()?.is_dir()); + assert_eq!( + foo.read_at(10, &mut buf).err(), + Some(VfsError::IsADirectory) + ); + assert!(Arc::ptr_eq( + &foo.clone().lookup("/f2")?, + &devfs.root_dir().lookup(".//./foo///f2")?, + )); + assert_eq!( + foo.clone().lookup("/bar//f1")?.get_attr()?.file_type(), + VfsNodeType::CharDevice + ); + assert_eq!( + foo.lookup("/bar///")?.get_attr()?.file_type(), + VfsNodeType::Dir + ); + + Ok(()) +} + +fn test_get_parent(devfs: &DeviceFileSystem) -> VfsResult { + let root = devfs.root_dir(); + assert!(root.parent().is_none()); + + let node = root.clone().lookup("null")?; + assert!(node.parent().is_none()); + + let node = root.clone().lookup(".//foo/bar")?; + assert!(node.parent().is_some()); + let parent = node.parent().unwrap(); + assert!(Arc::ptr_eq(&parent, &root.clone().lookup("foo")?)); + assert!(parent.lookup("bar").is_ok()); + + let node = root.clone().lookup("foo/..")?; + assert!(Arc::ptr_eq(&node, &root.clone().lookup(".")?)); + + assert!(Arc::ptr_eq( + &root.clone().lookup("/foo/..")?, + &devfs.root_dir().lookup(".//./foo/././bar/../..")?, + )); + assert!(Arc::ptr_eq( + &root.clone().lookup("././/foo//./../foo//bar///..//././")?, + &devfs.root_dir().lookup(".//./foo/")?, + )); + assert!(Arc::ptr_eq( + &root.clone().lookup("///foo//bar///../f2")?, + &root.lookup("foo/.//f2")?, + )); + + Ok(()) +} + +#[test] +fn test_devfs() { + // . + // ├── foo + // │   ├── bar + // │   │   └── f1 (null) + // │   └── f2 (zero) + // ├── null + // └── zero + + let devfs = DeviceFileSystem::new(); + devfs.add("null", Arc::new(NullDev)); + devfs.add("zero", Arc::new(ZeroDev)); + + let dir_foo = devfs.mkdir("foo"); + dir_foo.add("f2", Arc::new(ZeroDev)); + let dir_bar = dir_foo.mkdir("bar"); + dir_bar.add("f1", Arc::new(NullDev)); + + test_devfs_ops(&devfs).unwrap(); + test_get_parent(&devfs).unwrap(); +} diff --git a/crates/axfs_devfs/src/zero.rs b/crates/axfs_devfs/src/zero.rs new file mode 100644 index 000000000..0f89fd132 --- /dev/null +++ b/crates/axfs_devfs/src/zero.rs @@ -0,0 +1,32 @@ +use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodePerm, VfsNodeType, VfsResult}; + +/// A zero device behaves like `/dev/zero`. +/// +/// It always returns a chunk of `\0` bytes when read, and all writes are discarded. +pub struct ZeroDev; + +impl VfsNodeOps for ZeroDev { + fn get_attr(&self) -> VfsResult { + Ok(VfsNodeAttr::new( + VfsNodePerm::default_file(), + VfsNodeType::CharDevice, + 0, + 0, + )) + } + + fn read_at(&self, _offset: u64, buf: &mut [u8]) -> VfsResult { + buf.fill(0); + Ok(buf.len()) + } + + fn write_at(&self, _offset: u64, buf: &[u8]) -> VfsResult { + Ok(buf.len()) + } + + fn truncate(&self, _size: u64) -> VfsResult { + Ok(()) + } + + axfs_vfs::impl_vfs_non_dir_default! {} +} diff --git a/crates/axfs_ramfs/Cargo.toml b/crates/axfs_ramfs/Cargo.toml new file mode 100644 index 000000000..928cc2a78 --- /dev/null +++ b/crates/axfs_ramfs/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "axfs_ramfs" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "RAM filesystem used by ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/axfs_ramfs" +documentation = "https://rcore-os.github.io/arceos/axfs_ramfs/index.html" + +[dependencies] +axfs_vfs = { path = "../axfs_vfs" } +spin = "0.9" +log = "0.4" diff --git a/crates/axfs_ramfs/src/dir.rs b/crates/axfs_ramfs/src/dir.rs new file mode 100644 index 000000000..b64707e84 --- /dev/null +++ b/crates/axfs_ramfs/src/dir.rs @@ -0,0 +1,176 @@ +use alloc::collections::BTreeMap; +use alloc::sync::{Arc, Weak}; +use alloc::{string::String, vec::Vec}; + +use axfs_vfs::{VfsDirEntry, VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType}; +use axfs_vfs::{VfsError, VfsResult}; +use spin::RwLock; + +use crate::file::FileNode; + +/// The directory node in the RAM filesystem. +/// +/// It implements [`axfs_vfs::VfsNodeOps`]. +pub struct DirNode { + this: Weak, + parent: RwLock>, + children: RwLock>, +} + +impl DirNode { + pub(super) fn new(parent: Option>) -> Arc { + Arc::new_cyclic(|this| Self { + this: this.clone(), + parent: RwLock::new(parent.unwrap_or_else(|| Weak::::new())), + children: RwLock::new(BTreeMap::new()), + }) + } + + pub(super) fn set_parent(&self, parent: Option<&VfsNodeRef>) { + *self.parent.write() = parent.map_or(Weak::::new() as _, Arc::downgrade); + } + + /// Returns a string list of all entries in this directory. + pub fn get_entries(&self) -> Vec { + self.children.read().keys().cloned().collect() + } + + /// Checks whether a node with the given name exists in this directory. + pub fn exist(&self, name: &str) -> bool { + self.children.read().contains_key(name) + } + + /// Creates a new node with the given name and type in this directory. + pub fn create_node(&self, name: &str, ty: VfsNodeType) -> VfsResult { + if self.exist(name) { + log::error!("AlreadyExists {}", name); + return Err(VfsError::AlreadyExists); + } + let node: VfsNodeRef = match ty { + VfsNodeType::File => Arc::new(FileNode::new()), + VfsNodeType::Dir => Self::new(Some(self.this.clone())), + _ => return Err(VfsError::Unsupported), + }; + self.children.write().insert(name.into(), node); + Ok(()) + } + + /// Removes a node by the given name in this directory. + pub fn remove_node(&self, name: &str) -> VfsResult { + let mut children = self.children.write(); + let node = children.get(name).ok_or(VfsError::NotFound)?; + if let Some(dir) = node.as_any().downcast_ref::() { + if !dir.children.read().is_empty() { + return Err(VfsError::DirectoryNotEmpty); + } + } + children.remove(name); + Ok(()) + } +} + +impl VfsNodeOps for DirNode { + fn get_attr(&self) -> VfsResult { + Ok(VfsNodeAttr::new_dir(4096, 0)) + } + + fn parent(&self) -> Option { + self.parent.read().upgrade() + } + + fn lookup(self: Arc, path: &str) -> VfsResult { + let (name, rest) = split_path(path); + let node = match name { + "" | "." => Ok(self.clone() as VfsNodeRef), + ".." => self.parent().ok_or(VfsError::NotFound), + _ => self + .children + .read() + .get(name) + .cloned() + .ok_or(VfsError::NotFound), + }?; + + if let Some(rest) = rest { + node.lookup(rest) + } else { + Ok(node) + } + } + + fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { + let children = self.children.read(); + let mut children = children.iter().skip(start_idx.max(2) - 2); + for (i, ent) in dirents.iter_mut().enumerate() { + match i + start_idx { + 0 => *ent = VfsDirEntry::new(".", VfsNodeType::Dir), + 1 => *ent = VfsDirEntry::new("..", VfsNodeType::Dir), + _ => { + if let Some((name, node)) = children.next() { + *ent = VfsDirEntry::new(name, node.get_attr().unwrap().file_type()); + } else { + return Ok(i); + } + } + } + } + Ok(dirents.len()) + } + + fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { + log::debug!("create {:?} at ramfs: {}", ty, path); + let (name, rest) = split_path(path); + if let Some(rest) = rest { + match name { + "" | "." => self.create(rest, ty), + ".." => self.parent().ok_or(VfsError::NotFound)?.create(rest, ty), + _ => { + let subdir = self + .children + .read() + .get(name) + .ok_or(VfsError::NotFound)? + .clone(); + subdir.create(rest, ty) + } + } + } else if name.is_empty() || name == "." || name == ".." { + Ok(()) // already exists + } else { + self.create_node(name, ty) + } + } + + fn remove(&self, path: &str) -> VfsResult { + log::debug!("remove at ramfs: {}", path); + let (name, rest) = split_path(path); + if let Some(rest) = rest { + match name { + "" | "." => self.remove(rest), + ".." => self.parent().ok_or(VfsError::NotFound)?.remove(rest), + _ => { + let subdir = self + .children + .read() + .get(name) + .ok_or(VfsError::NotFound)? + .clone(); + subdir.remove(rest) + } + } + } else if name.is_empty() || name == "." || name == ".." { + Err(VfsError::InvalidInput) // remove '.' or '.. + } else { + self.remove_node(name) + } + } + + axfs_vfs::impl_vfs_dir_default! {} +} + +fn split_path(path: &str) -> (&str, Option<&str>) { + let trimmed_path = path.trim_start_matches('/'); + trimmed_path.find('/').map_or((trimmed_path, None), |n| { + (&trimmed_path[..n], Some(&trimmed_path[n + 1..])) + }) +} diff --git a/crates/axfs_ramfs/src/file.rs b/crates/axfs_ramfs/src/file.rs new file mode 100644 index 000000000..b378ee7cf --- /dev/null +++ b/crates/axfs_ramfs/src/file.rs @@ -0,0 +1,56 @@ +use alloc::vec::Vec; +use axfs_vfs::{impl_vfs_non_dir_default, VfsNodeAttr, VfsNodeOps, VfsResult}; +use spin::RwLock; + +/// The file node in the RAM filesystem. +/// +/// It implements [`axfs_vfs::VfsNodeOps`]. +pub struct FileNode { + content: RwLock>, +} + +impl FileNode { + pub(super) const fn new() -> Self { + Self { + content: RwLock::new(Vec::new()), + } + } +} + +impl VfsNodeOps for FileNode { + fn get_attr(&self) -> VfsResult { + Ok(VfsNodeAttr::new_file(self.content.read().len() as _, 0)) + } + + fn truncate(&self, size: u64) -> VfsResult { + let mut content = self.content.write(); + if size < content.len() as u64 { + content.truncate(size as _); + } else { + content.resize(size as _, 0); + } + Ok(()) + } + + fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult { + let content = self.content.read(); + let start = content.len().min(offset as usize); + let end = content.len().min(offset as usize + buf.len()); + let src = &content[start..end]; + buf[..src.len()].copy_from_slice(src); + Ok(src.len()) + } + + fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult { + let offset = offset as usize; + let mut content = self.content.write(); + if offset + buf.len() > content.len() { + content.resize(offset + buf.len(), 0); + } + let dst = &mut content[offset..offset + buf.len()]; + dst.copy_from_slice(&buf[..dst.len()]); + Ok(buf.len()) + } + + impl_vfs_non_dir_default! {} +} diff --git a/crates/axfs_ramfs/src/lib.rs b/crates/axfs_ramfs/src/lib.rs new file mode 100644 index 000000000..4d57ecaf5 --- /dev/null +++ b/crates/axfs_ramfs/src/lib.rs @@ -0,0 +1,62 @@ +//! RAM filesystem used by [ArceOS](https://github.com/rcore-os/arceos). +//! +//! The implementation is based on [`axfs_vfs`]. + +#![cfg_attr(not(test), no_std)] + +extern crate alloc; + +mod dir; +mod file; + +#[cfg(test)] +mod tests; + +pub use self::dir::DirNode; +pub use self::file::FileNode; + +use alloc::sync::Arc; +use axfs_vfs::{VfsNodeRef, VfsOps, VfsResult}; +use spin::once::Once; + +/// A RAM filesystem that implements [`axfs_vfs::VfsOps`]. +pub struct RamFileSystem { + parent: Once, + root: Arc, +} + +impl RamFileSystem { + /// Create a new instance. + pub fn new() -> Self { + Self { + parent: Once::new(), + root: DirNode::new(None), + } + } + + /// Returns the root directory node in [`Arc`](DirNode). + pub fn root_dir_node(&self) -> Arc { + self.root.clone() + } +} + +impl VfsOps for RamFileSystem { + fn mount(&self, _path: &str, mount_point: VfsNodeRef) -> VfsResult { + if let Some(parent) = mount_point.parent() { + self.root.set_parent(Some(self.parent.call_once(|| parent))); + } else { + self.root.set_parent(None); + } + Ok(()) + } + + fn root_dir(&self) -> VfsNodeRef { + self.root.clone() + } +} + +impl Default for RamFileSystem { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/axfs_ramfs/src/tests.rs b/crates/axfs_ramfs/src/tests.rs new file mode 100644 index 000000000..fda7001d5 --- /dev/null +++ b/crates/axfs_ramfs/src/tests.rs @@ -0,0 +1,136 @@ +use std::sync::Arc; + +use axfs_vfs::{VfsError, VfsNodeType, VfsResult}; + +use crate::*; + +fn test_ramfs_ops(devfs: &RamFileSystem) -> VfsResult { + const N: usize = 32; + const N_HALF: usize = N / 2; + let mut buf = [1; N]; + + let root = devfs.root_dir(); + assert!(root.get_attr()?.is_dir()); + assert_eq!(root.get_attr()?.file_type(), VfsNodeType::Dir); + assert_eq!( + root.clone().lookup("urandom").err(), + Some(VfsError::NotFound) + ); + assert_eq!( + root.clone().lookup("f1/").err(), + Some(VfsError::NotADirectory) + ); + + let node = root.lookup("////f1")?; + assert_eq!(node.get_attr()?.file_type(), VfsNodeType::File); + assert!(!node.get_attr()?.is_dir()); + assert_eq!(node.get_attr()?.size(), 0); + assert_eq!(node.read_at(0, &mut buf)?, 0); + assert_eq!(buf, [1; N]); + + assert_eq!(node.write_at(N_HALF as _, &buf[..N_HALF])?, N_HALF); + assert_eq!(node.read_at(0, &mut buf)?, N); + assert_eq!(buf[..N_HALF], [0; N_HALF]); + assert_eq!(buf[N_HALF..], [1; N_HALF]); + assert_eq!(node.lookup("/").err(), Some(VfsError::NotADirectory)); + + let foo = devfs.root_dir().lookup(".///.//././/.////foo")?; + assert!(foo.get_attr()?.is_dir()); + assert_eq!( + foo.read_at(10, &mut buf).err(), + Some(VfsError::IsADirectory) + ); + assert!(Arc::ptr_eq( + &foo.clone().lookup("/f3")?, + &devfs.root_dir().lookup(".//./foo///f3")?, + )); + assert_eq!( + foo.clone().lookup("/bar//f4")?.get_attr()?.file_type(), + VfsNodeType::File + ); + assert_eq!( + foo.lookup("/bar///")?.get_attr()?.file_type(), + VfsNodeType::Dir + ); + + Ok(()) +} + +fn test_get_parent(devfs: &RamFileSystem) -> VfsResult { + let root = devfs.root_dir(); + assert!(root.parent().is_none()); + + let node = root.clone().lookup("f1")?; + assert!(node.parent().is_none()); + + let node = root.clone().lookup(".//foo/bar")?; + assert!(node.parent().is_some()); + let parent = node.parent().unwrap(); + assert!(Arc::ptr_eq(&parent, &root.clone().lookup("foo")?)); + assert!(parent.lookup("bar").is_ok()); + + let node = root.clone().lookup("foo/..")?; + assert!(Arc::ptr_eq(&node, &root.clone().lookup(".")?)); + + assert!(Arc::ptr_eq( + &root.clone().lookup("/foo/..")?, + &devfs.root_dir().lookup(".//./foo/././bar/../..")?, + )); + assert!(Arc::ptr_eq( + &root.clone().lookup("././/foo//./../foo//bar///..//././")?, + &devfs.root_dir().lookup(".//./foo/")?, + )); + assert!(Arc::ptr_eq( + &root.clone().lookup("///foo//bar///../f3")?, + &root.lookup("foo/.//f3")?, + )); + + Ok(()) +} + +#[test] +fn test_ramfs() { + // . + // ├── foo + // │   ├── bar + // │   │   └── f4 + // │   └── f3 + // ├── f1 + // └── f2 + + let ramfs = RamFileSystem::new(); + let root = ramfs.root_dir(); + root.create("f1", VfsNodeType::File).unwrap(); + root.create("f2", VfsNodeType::File).unwrap(); + root.create("foo", VfsNodeType::Dir).unwrap(); + + let dir_foo = root.lookup("foo").unwrap(); + dir_foo.create("f3", VfsNodeType::File).unwrap(); + dir_foo.create("bar", VfsNodeType::Dir).unwrap(); + + let dir_bar = dir_foo.lookup("bar").unwrap(); + dir_bar.create("f4", VfsNodeType::File).unwrap(); + + let mut entries = ramfs.root_dir_node().get_entries(); + entries.sort(); + assert_eq!(entries, ["f1", "f2", "foo"]); + + test_ramfs_ops(&ramfs).unwrap(); + test_get_parent(&ramfs).unwrap(); + + let root = ramfs.root_dir(); + assert_eq!(root.remove("f1"), Ok(())); + assert_eq!(root.remove("//f2"), Ok(())); + assert_eq!(root.remove("f3").err(), Some(VfsError::NotFound)); + assert_eq!(root.remove("foo").err(), Some(VfsError::DirectoryNotEmpty)); + assert_eq!(root.remove("foo/..").err(), Some(VfsError::InvalidInput)); + assert_eq!( + root.remove("foo/./bar").err(), + Some(VfsError::DirectoryNotEmpty) + ); + assert_eq!(root.remove("foo/bar/f4"), Ok(())); + assert_eq!(root.remove("foo/bar"), Ok(())); + assert_eq!(root.remove("./foo//.//f3"), Ok(())); + assert_eq!(root.remove("./foo"), Ok(())); + assert!(ramfs.root_dir_node().get_entries().is_empty()); +} diff --git a/crates/axfs_vfs/Cargo.toml b/crates/axfs_vfs/Cargo.toml new file mode 100644 index 000000000..319dd8a8a --- /dev/null +++ b/crates/axfs_vfs/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "axfs_vfs" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Virtual filesystem interfaces used by ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/axfs_vfs" +documentation = "https://rcore-os.github.io/arceos/axfs_vfs/index.html" + +[features] +default = [] + +[dependencies] +log = "0.4" +bitflags = "2.2" +axerrno = { path = "../axerrno" } diff --git a/crates/axfs_vfs/src/lib.rs b/crates/axfs_vfs/src/lib.rs new file mode 100644 index 000000000..ee807a812 --- /dev/null +++ b/crates/axfs_vfs/src/lib.rs @@ -0,0 +1,177 @@ +//! Virtual filesystem interfaces used by [ArceOS](https://github.com/rcore-os/arceos). +//! +//! A filesystem is a set of files and directories (symbol links are not +//! supported currently), collectively referred to as **nodes**, which are +//! conceptually similar to [inodes] in Linux. A file system needs to implement +//! the [`VfsOps`] trait, its files and directories need to implement the +//! [`VfsNodeOps`] trait. +//! +//! The [`VfsOps`] trait provides the following operations on a filesystem: +//! +//! - [`mount()`](VfsOps::mount): Do something when the filesystem is mounted. +//! - [`umount()`](VfsOps::umount): Do something when the filesystem is unmounted. +//! - [`format()`](VfsOps::format): Format the filesystem. +//! - [`statfs()`](VfsOps::statfs): Get the attributes of the filesystem. +//! - [`root_dir()`](VfsOps::root_dir): Get root directory of the filesystem. +//! +//! The [`VfsNodeOps`] trait provides the following operations on a file or a +//! directory: +//! +//! | Operation | Description | file/directory | +//! | --- | --- | --- | +//! | [`open()`](VfsNodeOps::open) | Do something when the node is opened | both | +//! | [`release()`](VfsNodeOps::release) | Do something when the node is closed | both | +//! | [`get_attr()`](VfsNodeOps::get_attr) | Get the attributes of the node | both | +//! | [`read_at()`](VfsNodeOps::read_at) | Read data from the file | file | +//! | [`write_at()`](VfsNodeOps::write_at) | Write data to the file | file | +//! | [`fsync()`](VfsNodeOps::fsync) | Synchronize the file data to disk | file | +//! | [`truncate()`](VfsNodeOps::truncate) | Truncate the file | file | +//! | [`parent()`](VfsNodeOps::parent) | Get the parent directory | directory | +//! | [`lookup()`](VfsNodeOps::lookup) | Lookup the node with the given path | directory | +//! | [`create()`](VfsNodeOps::create) | Create a new node with the given path | directory | +//! | [`remove()`](VfsNodeOps::remove) | Remove the node with the given path | directory | +//! | [`read_dir()`](VfsNodeOps::read_dir) | Read directory entries | directory | +//! +//! [inodes]: https://en.wikipedia.org/wiki/Inode + +#![no_std] + +extern crate alloc; + +mod macros; +mod structs; + +pub mod path; + +use alloc::sync::Arc; +use axerrno::{ax_err, AxError, AxResult}; + +pub use self::structs::{FileSystemInfo, VfsDirEntry, VfsNodeAttr, VfsNodePerm, VfsNodeType}; + +/// A wrapper of [`Arc`]. +pub type VfsNodeRef = Arc; + +/// Alias of [`AxError`]. +pub type VfsError = AxError; + +/// Alias of [`AxResult`]. +pub type VfsResult = AxResult; + +/// Filesystem operations. +pub trait VfsOps: Send + Sync { + /// Do something when the filesystem is mounted. + fn mount(&self, _path: &str, _mount_point: VfsNodeRef) -> VfsResult { + Ok(()) + } + + /// Do something when the filesystem is unmounted. + fn umount(&self) -> VfsResult { + Ok(()) + } + + /// Format the filesystem. + fn format(&self) -> VfsResult { + ax_err!(Unsupported) + } + + /// Get the attributes of the filesystem. + fn statfs(&self) -> VfsResult { + ax_err!(Unsupported) + } + + /// Get the root directory of the filesystem. + fn root_dir(&self) -> VfsNodeRef; +} + +/// Node (file/directory) operations. +pub trait VfsNodeOps: Send + Sync { + /// Do something when the node is opened. + fn open(&self) -> VfsResult { + Ok(()) + } + + /// Do something when the node is closed. + fn release(&self) -> VfsResult { + Ok(()) + } + + /// Get the attributes of the node. + fn get_attr(&self) -> VfsResult { + ax_err!(Unsupported) + } + + // file operations: + + /// Read data from the file at the given offset. + fn read_at(&self, _offset: u64, _buf: &mut [u8]) -> VfsResult { + ax_err!(InvalidInput) + } + + /// Write data to the file at the given offset. + fn write_at(&self, _offset: u64, _buf: &[u8]) -> VfsResult { + ax_err!(InvalidInput) + } + + /// Flush the file, synchronize the data to disk. + fn fsync(&self) -> VfsResult { + ax_err!(InvalidInput) + } + + /// Truncate the file to the given size. + fn truncate(&self, _size: u64) -> VfsResult { + ax_err!(InvalidInput) + } + + // directory operations: + + /// Get the parent directory of this directory. + /// + /// Return `None` if the node is a file. + fn parent(&self) -> Option { + None + } + + /// Lookup the node with given `path` in the directory. + /// + /// Return the node if found. + fn lookup(self: Arc, _path: &str) -> VfsResult { + ax_err!(Unsupported) + } + + /// Create a new node with the given `path` in the directory + /// + /// Return [`Ok(())`](Ok) if it already exists. + fn create(&self, _path: &str, _ty: VfsNodeType) -> VfsResult { + ax_err!(Unsupported) + } + + /// Remove the node with the given `path` in the directory. + fn remove(&self, _path: &str) -> VfsResult { + ax_err!(Unsupported) + } + + /// Read directory entries into `dirents`, starting from `start_idx`. + fn read_dir(&self, _start_idx: usize, _dirents: &mut [VfsDirEntry]) -> VfsResult { + ax_err!(Unsupported) + } + + /// Renames or moves existing file or directory. + fn rename(&self, _src_path: &str, _dst_path: &str) -> VfsResult { + ax_err!(Unsupported) + } + + /// Convert `&self` to [`&dyn Any`][1] that can use + /// [`Any::downcast_ref`][2]. + /// + /// [1]: core::any::Any + /// [2]: core::any::Any#method.downcast_ref + fn as_any(&self) -> &dyn core::any::Any { + unimplemented!() + } +} + +#[doc(hidden)] +pub mod __priv { + pub use alloc::sync::Arc; + pub use axerrno::ax_err; +} diff --git a/crates/axfs_vfs/src/macros.rs b/crates/axfs_vfs/src/macros.rs new file mode 100644 index 000000000..4cfd24da2 --- /dev/null +++ b/crates/axfs_vfs/src/macros.rs @@ -0,0 +1,66 @@ +/// When implement [`VfsNodeOps`] on a directory node, add dummy file operations +/// that just return an error. +/// +/// [`VfsNodeOps`]: crate::VfsNodeOps +#[macro_export] +macro_rules! impl_vfs_dir_default { + () => { + fn read_at(&self, _offset: u64, _buf: &mut [u8]) -> $crate::VfsResult { + $crate::__priv::ax_err!(IsADirectory) + } + + fn write_at(&self, _offset: u64, _buf: &[u8]) -> $crate::VfsResult { + $crate::__priv::ax_err!(IsADirectory) + } + + fn fsync(&self) -> $crate::VfsResult { + $crate::__priv::ax_err!(IsADirectory) + } + + fn truncate(&self, _size: u64) -> $crate::VfsResult { + $crate::__priv::ax_err!(IsADirectory) + } + + #[inline] + fn as_any(&self) -> &dyn core::any::Any { + self + } + }; +} + +/// When implement [`VfsNodeOps`] on a non-directory node, add dummy directory +/// operations that just return an error. +/// +/// [`VfsNodeOps`]: crate::VfsNodeOps +#[macro_export] +macro_rules! impl_vfs_non_dir_default { + () => { + fn lookup( + self: $crate::__priv::Arc, + _path: &str, + ) -> $crate::VfsResult<$crate::VfsNodeRef> { + $crate::__priv::ax_err!(NotADirectory) + } + + fn create(&self, _path: &str, _ty: $crate::VfsNodeType) -> $crate::VfsResult { + $crate::__priv::ax_err!(NotADirectory) + } + + fn remove(&self, _path: &str) -> $crate::VfsResult { + $crate::__priv::ax_err!(NotADirectory) + } + + fn read_dir( + &self, + _start_idx: usize, + _dirents: &mut [$crate::VfsDirEntry], + ) -> $crate::VfsResult { + $crate::__priv::ax_err!(NotADirectory) + } + + #[inline] + fn as_any(&self) -> &dyn core::any::Any { + self + } + }; +} diff --git a/crates/axfs_vfs/src/path.rs b/crates/axfs_vfs/src/path.rs new file mode 100644 index 000000000..273c5b00c --- /dev/null +++ b/crates/axfs_vfs/src/path.rs @@ -0,0 +1,97 @@ +//! Utilities for path manipulation. + +use alloc::string::String; + +/// Returns the canonical form of the path with all intermediate components +/// normalized. +/// +/// It won't force convert the path to an absolute form. +/// +/// # Examples +/// +/// ``` +/// use axfs_vfs::path::canonicalize; +/// +/// assert_eq!(canonicalize("/path/./to//foo"), "/path/to/foo"); +/// assert_eq!(canonicalize("/./path/to/../bar.rs"), "/path/bar.rs"); +/// assert_eq!(canonicalize("./foo/./bar"), "foo/bar"); +/// ``` +pub fn canonicalize(path: &str) -> String { + let mut buf = String::new(); + let is_absolute = path.starts_with('/'); + for part in path.split('/') { + match part { + "" | "." => continue, + ".." => { + while !buf.is_empty() { + if buf == "/" { + break; + } + let c = buf.pop().unwrap(); + if c == '/' { + break; + } + } + } + _ => { + if buf.is_empty() { + if is_absolute { + buf.push('/'); + } + } else if &buf[buf.len() - 1..] != "/" { + buf.push('/'); + } + buf.push_str(part); + } + } + } + if is_absolute && buf.is_empty() { + buf.push('/'); + } + buf +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_path_canonicalize() { + assert_eq!(canonicalize(""), ""); + assert_eq!(canonicalize("///"), "/"); + assert_eq!(canonicalize("//a//.//b///c//"), "/a/b/c"); + assert_eq!(canonicalize("/a/../"), "/"); + assert_eq!(canonicalize("/a/../..///"), "/"); + assert_eq!(canonicalize("a/../"), ""); + assert_eq!(canonicalize("a/..//.."), ""); + assert_eq!(canonicalize("././a"), "a"); + assert_eq!(canonicalize(".././a"), "a"); + assert_eq!(canonicalize("/././a"), "/a"); + assert_eq!(canonicalize("/abc/../abc"), "/abc"); + assert_eq!(canonicalize("/test"), "/test"); + assert_eq!(canonicalize("/test/"), "/test"); + assert_eq!(canonicalize("test/"), "test"); + assert_eq!(canonicalize("test"), "test"); + assert_eq!(canonicalize("/test//"), "/test"); + assert_eq!(canonicalize("/test/foo"), "/test/foo"); + assert_eq!(canonicalize("/test/foo/"), "/test/foo"); + assert_eq!(canonicalize("/test/foo/bar"), "/test/foo/bar"); + assert_eq!(canonicalize("/test/foo/bar//"), "/test/foo/bar"); + assert_eq!(canonicalize("/test//foo/bar//"), "/test/foo/bar"); + assert_eq!(canonicalize("/test//./foo/bar//"), "/test/foo/bar"); + assert_eq!(canonicalize("/test//./.foo/bar//"), "/test/.foo/bar"); + assert_eq!(canonicalize("/test//./..foo/bar//"), "/test/..foo/bar"); + assert_eq!(canonicalize("/test//./../foo/bar//"), "/foo/bar"); + assert_eq!(canonicalize("/test/../foo"), "/foo"); + assert_eq!(canonicalize("/test/bar/../foo"), "/test/foo"); + assert_eq!(canonicalize("../foo"), "foo"); + assert_eq!(canonicalize("../foo/"), "foo"); + assert_eq!(canonicalize("/../foo"), "/foo"); + assert_eq!(canonicalize("/../foo/"), "/foo"); + assert_eq!(canonicalize("/../../foo"), "/foo"); + assert_eq!(canonicalize("/bleh/../../foo"), "/foo"); + assert_eq!(canonicalize("/bleh/bar/../../foo"), "/foo"); + assert_eq!(canonicalize("/bleh/bar/../../foo/.."), "/"); + assert_eq!(canonicalize("/bleh/bar/../../foo/../meh"), "/meh"); + } +} diff --git a/crates/axfs_vfs/src/structs.rs b/crates/axfs_vfs/src/structs.rs new file mode 100644 index 000000000..9d0dbfaba --- /dev/null +++ b/crates/axfs_vfs/src/structs.rs @@ -0,0 +1,305 @@ +/// Filesystem attributes. +/// +/// Currently not used. +#[non_exhaustive] +pub struct FileSystemInfo; + +/// Node (file/directory) attributes. +#[allow(dead_code)] +#[derive(Debug, Clone, Copy)] +pub struct VfsNodeAttr { + /// File permission mode. + mode: VfsNodePerm, + /// File type. + ty: VfsNodeType, + /// Total size, in bytes. + size: u64, + /// Number of 512B blocks allocated. + blocks: u64, +} + +bitflags::bitflags! { + /// Node (file/directory) permission mode. + #[derive(Debug, Clone, Copy)] + pub struct VfsNodePerm: u16 { + /// Owner has read permission. + const OWNER_READ = 0o400; + /// Owner has write permission. + const OWNER_WRITE = 0o200; + /// Owner has execute permission. + const OWNER_EXEC = 0o100; + + /// Group has read permission. + const GROUP_READ = 0o40; + /// Group has write permission. + const GROUP_WRITE = 0o20; + /// Group has execute permission. + const GROUP_EXEC = 0o10; + + /// Others have read permission. + const OTHER_READ = 0o4; + /// Others have write permission. + const OTHER_WRITE = 0o2; + /// Others have execute permission. + const OTHER_EXEC = 0o1; + } +} + +/// Node (file/directory) type. +#[repr(u8)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum VfsNodeType { + /// FIFO (named pipe) + Fifo = 0o1, + /// Character device + CharDevice = 0o2, + /// Directory + Dir = 0o4, + /// Block device + BlockDevice = 0o6, + /// Regular file + File = 0o10, + /// Symbolic link + SymLink = 0o12, + /// Socket + Socket = 0o14, +} + +/// Directory entry. +pub struct VfsDirEntry { + d_type: VfsNodeType, + d_name: [u8; 63], +} + +impl VfsNodePerm { + /// Returns the default permission for a file. + /// + /// The default permission is `0o666` (owner/group/others can read and write). + pub const fn default_file() -> Self { + Self::from_bits_truncate(0o666) + } + + /// Returns the default permission for a directory. + /// + /// The default permission is `0o755` (owner can read, write and execute, + /// group/others can read and execute). + pub const fn default_dir() -> Self { + Self::from_bits_truncate(0o755) + } + + /// Returns the underlying raw `st_mode` bits that contain the standard + /// Unix permissions for this file. + pub const fn mode(&self) -> u32 { + self.bits() as u32 + } + + /// Returns a 9-bytes string representation of the permission. + /// + /// For example, `0o755` is represented as `rwxr-xr-x`. + pub const fn rwx_buf(&self) -> [u8; 9] { + let mut perm = [b'-'; 9]; + if self.contains(Self::OWNER_READ) { + perm[0] = b'r'; + } + if self.contains(Self::OWNER_WRITE) { + perm[1] = b'w'; + } + if self.contains(Self::OWNER_EXEC) { + perm[2] = b'x'; + } + if self.contains(Self::GROUP_READ) { + perm[3] = b'r'; + } + if self.contains(Self::GROUP_WRITE) { + perm[4] = b'w'; + } + if self.contains(Self::GROUP_EXEC) { + perm[5] = b'x'; + } + if self.contains(Self::OTHER_READ) { + perm[6] = b'r'; + } + if self.contains(Self::OTHER_WRITE) { + perm[7] = b'w'; + } + if self.contains(Self::OTHER_EXEC) { + perm[8] = b'x'; + } + perm + } + + /// Whether the owner has read permission. + pub const fn owner_readable(&self) -> bool { + self.contains(Self::OWNER_READ) + } + + /// Whether the owner has write permission. + pub const fn owner_writable(&self) -> bool { + self.contains(Self::OWNER_WRITE) + } + + /// Whether the owner has execute permission. + pub const fn owner_executable(&self) -> bool { + self.contains(Self::OWNER_EXEC) + } +} + +impl VfsNodeType { + /// Tests whether this node type represents a regular file. + pub const fn is_file(self) -> bool { + matches!(self, Self::File) + } + + /// Tests whether this node type represents a directory. + pub const fn is_dir(self) -> bool { + matches!(self, Self::Dir) + } + + /// Tests whether this node type represents a symbolic link. + pub const fn is_symlink(self) -> bool { + matches!(self, Self::SymLink) + } + + /// Returns `true` if this node type is a block device. + pub const fn is_block_device(self) -> bool { + matches!(self, Self::BlockDevice) + } + + /// Returns `true` if this node type is a char device. + pub const fn is_char_device(self) -> bool { + matches!(self, Self::CharDevice) + } + + /// Returns `true` if this node type is a fifo. + pub const fn is_fifo(self) -> bool { + matches!(self, Self::Fifo) + } + + /// Returns `true` if this node type is a socket. + pub const fn is_socket(self) -> bool { + matches!(self, Self::Socket) + } + + /// Returns a character representation of the node type. + /// + /// For example, `d` for directory, `-` for regular file, etc. + pub const fn as_char(self) -> char { + match self { + Self::Fifo => 'p', + Self::CharDevice => 'c', + Self::Dir => 'd', + Self::BlockDevice => 'b', + Self::File => '-', + Self::SymLink => 'l', + Self::Socket => 's', + } + } +} + +impl VfsNodeAttr { + /// Creates a new `VfsNodeAttr` with the given permission mode, type, size + /// and number of blocks. + pub const fn new(mode: VfsNodePerm, ty: VfsNodeType, size: u64, blocks: u64) -> Self { + Self { + mode, + ty, + size, + blocks, + } + } + + /// Creates a new `VfsNodeAttr` for a file, with the default file permission. + pub const fn new_file(size: u64, blocks: u64) -> Self { + Self { + mode: VfsNodePerm::default_file(), + ty: VfsNodeType::File, + size, + blocks, + } + } + + /// Creates a new `VfsNodeAttr` for a directory, with the default directory + /// permission. + pub const fn new_dir(size: u64, blocks: u64) -> Self { + Self { + mode: VfsNodePerm::default_dir(), + ty: VfsNodeType::Dir, + size, + blocks, + } + } + + /// Returns the size of the node. + pub const fn size(&self) -> u64 { + self.size + } + + /// Returns the number of blocks the node occupies on the disk. + pub const fn blocks(&self) -> u64 { + self.blocks + } + + /// Returns the permission of the node. + pub const fn perm(&self) -> VfsNodePerm { + self.mode + } + + /// Sets the permission of the node. + pub fn set_perm(&mut self, perm: VfsNodePerm) { + self.mode = perm + } + + /// Returns the type of the node. + pub const fn file_type(&self) -> VfsNodeType { + self.ty + } + + /// Whether the node is a file. + pub const fn is_file(&self) -> bool { + self.ty.is_file() + } + + /// Whether the node is a directory. + pub const fn is_dir(&self) -> bool { + self.ty.is_dir() + } +} + +impl VfsDirEntry { + /// Creates an empty `VfsDirEntry`. + pub const fn default() -> Self { + Self { + d_type: VfsNodeType::File, + d_name: [0; 63], + } + } + + /// Creates a new `VfsDirEntry` with the given name and type. + pub fn new(name: &str, ty: VfsNodeType) -> Self { + let mut d_name = [0; 63]; + if name.len() > d_name.len() { + log::warn!( + "directory entry name too long: {} > {}", + name.len(), + d_name.len() + ); + } + d_name[..name.len()].copy_from_slice(name.as_bytes()); + Self { d_type: ty, d_name } + } + + /// Returns the type of the entry. + pub fn entry_type(&self) -> VfsNodeType { + self.d_type + } + + /// Converts the name of the entry to a byte slice. + pub fn name_as_bytes(&self) -> &[u8] { + let len = self + .d_name + .iter() + .position(|&c| c == 0) + .unwrap_or(self.d_name.len()); + &self.d_name[..len] + } +} diff --git a/crates/axio/Cargo.toml b/crates/axio/Cargo.toml new file mode 100644 index 000000000..02ea0d1d5 --- /dev/null +++ b/crates/axio/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "axio" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "`std::io`-like I/O traits for `no_std` environment" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/axio" +documentation = "https://rcore-os.github.io/arceos/axio/index.html" + +[features] +alloc = [] +default = [] + +[dependencies] +axerrno = { path = "../axerrno" } diff --git a/crates/axio/src/buffered/bufreader.rs b/crates/axio/src/buffered/bufreader.rs new file mode 100644 index 000000000..b466c1e89 --- /dev/null +++ b/crates/axio/src/buffered/bufreader.rs @@ -0,0 +1,158 @@ +use crate::{BufRead, Read, Result}; + +#[cfg(feature = "alloc")] +use alloc::{string::String, vec::Vec}; + +const DEFAULT_BUF_SIZE: usize = 1024; + +/// The `BufReader` struct adds buffering to any reader. +pub struct BufReader { + inner: R, + pos: usize, + filled: usize, + buf: [u8; DEFAULT_BUF_SIZE], +} + +impl BufReader { + /// Creates a new `BufReader` with a default buffer capacity (1 KB). + pub const fn new(inner: R) -> BufReader { + Self { + inner, + pos: 0, + filled: 0, + buf: [0; DEFAULT_BUF_SIZE], + } + } +} + +impl BufReader { + /// Gets a reference to the underlying reader. + pub const fn get_ref(&self) -> &R { + &self.inner + } + + /// Gets a mutable reference to the underlying reader. + pub fn get_mut(&mut self) -> &mut R { + &mut self.inner + } + + /// Returns a reference to the internally buffered data. + /// + /// Unlike [`fill_buf`], this will not attempt to fill the buffer if it is empty. + /// + /// [`fill_buf`]: BufRead::fill_buf + pub fn buffer(&self) -> &[u8] { + &self.buf[self.pos..self.filled] + } + + /// Returns the number of bytes the internal buffer can hold at once. + pub const fn capacity(&self) -> usize { + DEFAULT_BUF_SIZE + } + + /// Unwraps this `BufReader`, returning the underlying reader. + pub fn into_inner(self) -> R { + self.inner + } + + fn discard_buffer(&mut self) { + self.pos = 0; + self.filled = 0; + } + + const fn is_empty(&self) -> bool { + self.pos >= self.filled + } +} + +impl Read for BufReader { + fn read(&mut self, buf: &mut [u8]) -> Result { + // If we don't have any buffered data and we're doing a massive read + // (larger than our internal buffer), bypass our internal buffer + // entirely. + if self.is_empty() && buf.len() >= self.capacity() { + self.discard_buffer(); + return self.inner.read(buf); + } + let nread = { + let mut rem = self.fill_buf()?; + rem.read(buf)? + }; + self.consume(nread); + Ok(nread) + } + + // Small read_exacts from a BufReader are extremely common when used with a deserializer. + // The default implementation calls read in a loop, which results in surprisingly poor code + // generation for the common path where the buffer has enough bytes to fill the passed-in + // buffer. + fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { + let amt = buf.len(); + if let Some(claimed) = self.buffer().get(..amt) { + buf.copy_from_slice(claimed); + self.pos += amt; + return Ok(()); + } + self.inner.read_exact(buf) + } + + // The inner reader might have an optimized `read_to_end`. Drain our buffer and then + // delegate to the inner implementation. + #[cfg(feature = "alloc")] + fn read_to_end(&mut self, buf: &mut Vec) -> Result { + let inner_buf = self.buffer(); + buf.extend_from_slice(inner_buf); + let nread = inner_buf.len(); + self.discard_buffer(); + Ok(nread + self.inner.read_to_end(buf)?) + } + + // The inner reader might have an optimized `read_to_end`. Drain our buffer and then + // delegate to the inner implementation. + #[cfg(feature = "alloc")] + fn read_to_string(&mut self, buf: &mut String) -> Result { + // In the general `else` case below we must read bytes into a side buffer, check + // that they are valid UTF-8, and then append them to `buf`. This requires a + // potentially large memcpy. + // + // If `buf` is empty--the most common case--we can leverage `append_to_string` + // to read directly into `buf`'s internal byte buffer, saving an allocation and + // a memcpy. + if buf.is_empty() { + // `append_to_string`'s safety relies on the buffer only being appended to since + // it only checks the UTF-8 validity of new data. If there were existing content in + // `buf` then an untrustworthy reader (i.e. `self.inner`) could not only append + // bytes but also modify existing bytes and render them invalid. On the other hand, + // if `buf` is empty then by definition any writes must be appends and + // `append_to_string` will validate all of the new bytes. + unsafe { crate::append_to_string(buf, |b| self.read_to_end(b)) } + } else { + // We cannot append our byte buffer directly onto the `buf` String as there could + // be an incomplete UTF-8 sequence that has only been partially read. We must read + // everything into a side buffer first and then call `from_utf8` on the complete + // buffer. + let mut bytes = Vec::new(); + self.read_to_end(&mut bytes)?; + let string = core::str::from_utf8(&bytes).map_err(|_| { + axerrno::ax_err_type!(InvalidData, "stream did not contain valid UTF-8") + })?; + *buf += string; + Ok(string.len()) + } + } +} + +impl BufRead for BufReader { + fn fill_buf(&mut self) -> Result<&[u8]> { + if self.is_empty() { + let read_len = self.inner.read(&mut self.buf)?; + self.pos = 0; + self.filled = read_len; + } + Ok(self.buffer()) + } + + fn consume(&mut self, amt: usize) { + self.pos = core::cmp::min(self.pos + amt, self.filled); + } +} diff --git a/crates/axio/src/buffered/mod.rs b/crates/axio/src/buffered/mod.rs new file mode 100644 index 000000000..c7dfe1ea6 --- /dev/null +++ b/crates/axio/src/buffered/mod.rs @@ -0,0 +1,3 @@ +mod bufreader; + +pub use self::bufreader::BufReader; diff --git a/crates/axio/src/error.rs b/crates/axio/src/error.rs new file mode 100644 index 000000000..ee2d716b1 --- /dev/null +++ b/crates/axio/src/error.rs @@ -0,0 +1,2 @@ +pub use axerrno::AxError as Error; +pub use axerrno::AxResult as Result; diff --git a/crates/axio/src/impls.rs b/crates/axio/src/impls.rs new file mode 100644 index 000000000..96f83db0f --- /dev/null +++ b/crates/axio/src/impls.rs @@ -0,0 +1,54 @@ +use crate::{prelude::*, Result}; +use core::cmp; + +impl Read for &[u8] { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result { + let amt = cmp::min(buf.len(), self.len()); + let a = &self[..amt]; + let b = &self[amt..]; + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + *self = b; + Ok(amt) + } + + #[inline] + fn read_exact(&mut self, buf: &mut [u8]) -> Result<()> { + if buf.len() > self.len() { + return axerrno::ax_err!(UnexpectedEof, "failed to fill whole buffer"); + } + let amt = buf.len(); + let a = &self[..amt]; + let b = &self[amt..]; + + // First check if the amount of bytes we want to read is small: + // `copy_from_slice` will generally expand to a call to `memcpy`, and + // for a single byte the overhead is significant. + if amt == 1 { + buf[0] = a[0]; + } else { + buf[..amt].copy_from_slice(a); + } + + *self = b; + Ok(()) + } + + #[inline] + #[cfg(feature = "alloc")] + fn read_to_end(&mut self, buf: &mut alloc::vec::Vec) -> Result { + buf.extend_from_slice(self); + let len = self.len(); + *self = &self[len..]; + Ok(len) + } +} diff --git a/crates/axio/src/lib.rs b/crates/axio/src/lib.rs new file mode 100644 index 000000000..b15c80ae7 --- /dev/null +++ b/crates/axio/src/lib.rs @@ -0,0 +1,260 @@ +//! [`std::io`]-like I/O traits for `no_std` environment. + +#![cfg_attr(not(doc), no_std)] +#![feature(doc_auto_cfg)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +use core::fmt; + +mod buffered; +mod error; +mod impls; + +pub mod prelude; + +pub use self::buffered::BufReader; +pub use self::error::{Error, Result}; + +#[cfg(feature = "alloc")] +use alloc::{string::String, vec::Vec}; + +use axerrno::ax_err; + +/// The `Read` trait allows for reading bytes from a source. +pub trait Read { + /// Pull some bytes from this source into the specified buffer, returning + /// how many bytes were read. + fn read(&mut self, buf: &mut [u8]) -> Result; + + /// Read all bytes until EOF in this source, placing them into `buf`. + #[cfg(feature = "alloc")] + fn read_to_end(&mut self, buf: &mut Vec) -> Result { + let start_len = buf.len(); + let mut probe = [0u8; 32]; + loop { + match self.read(&mut probe) { + Ok(0) => return Ok(buf.len() - start_len), + Ok(n) => buf.extend_from_slice(&probe[..n]), + Err(e) => return Err(e), + } + } + } + + /// Read all bytes until EOF in this source, appending them to `buf`. + #[cfg(feature = "alloc")] + fn read_to_string(&mut self, buf: &mut String) -> Result { + unsafe { append_to_string(buf, |b| self.read_to_end(b)) } + } + + /// Read the exact number of bytes required to fill `buf`. + fn read_exact(&mut self, mut buf: &mut [u8]) -> Result { + while !buf.is_empty() { + match self.read(buf) { + Ok(0) => break, + Ok(n) => { + let tmp = buf; + buf = &mut tmp[n..]; + } + Err(e) => return Err(e), + } + } + if !buf.is_empty() { + ax_err!(UnexpectedEof, "failed to fill whole buffer") + } else { + Ok(()) + } + } +} + +/// A trait for objects which are byte-oriented sinks. +pub trait Write { + /// Write a buffer into this writer, returning how many bytes were written. + fn write(&mut self, buf: &[u8]) -> Result; + + /// Flush this output stream, ensuring that all intermediately buffered + /// contents reach their destination. + fn flush(&mut self) -> Result; + + /// Attempts to write an entire buffer into this writer. + fn write_all(&mut self, mut buf: &[u8]) -> Result { + while !buf.is_empty() { + match self.write(buf) { + Ok(0) => return ax_err!(WriteZero, "failed to write whole buffer"), + Ok(n) => buf = &buf[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } + + /// Writes a formatted string into this writer, returning any error + /// encountered. + fn write_fmt(&mut self, fmt: fmt::Arguments<'_>) -> Result<()> { + // Create a shim which translates a Write to a fmt::Write and saves + // off I/O errors. instead of discarding them + struct Adapter<'a, T: ?Sized + 'a> { + inner: &'a mut T, + error: Result<()>, + } + + impl fmt::Write for Adapter<'_, T> { + fn write_str(&mut self, s: &str) -> fmt::Result { + match self.inner.write_all(s.as_bytes()) { + Ok(()) => Ok(()), + Err(e) => { + self.error = Err(e); + Err(fmt::Error) + } + } + } + } + + let mut output = Adapter { + inner: self, + error: Ok(()), + }; + match fmt::write(&mut output, fmt) { + Ok(()) => Ok(()), + Err(..) => { + // check if the error came from the underlying `Write` or not + if output.error.is_err() { + output.error + } else { + ax_err!(InvalidData, "formatter error") + } + } + } + } +} + +/// The `Seek` trait provides a cursor which can be moved within a stream of +/// bytes. +pub trait Seek { + /// Seek to an offset, in bytes, in a stream. + /// + /// A seek beyond the end of a stream is allowed, but behavior is defined + /// by the implementation. + /// + /// If the seek operation completed successfully, + /// this method returns the new position from the start of the stream. + /// That position can be used later with [`SeekFrom::Start`]. + fn seek(&mut self, pos: SeekFrom) -> Result; + + /// Rewind to the beginning of a stream. + /// + /// This is a convenience method, equivalent to `seek(SeekFrom::Start(0))`. + fn rewind(&mut self) -> Result<()> { + self.seek(SeekFrom::Start(0))?; + Ok(()) + } + + /// Returns the current seek position from the start of the stream. + /// + /// This is equivalent to `self.seek(SeekFrom::Current(0))`. + fn stream_position(&mut self) -> Result { + self.seek(SeekFrom::Current(0)) + } +} + +/// Enumeration of possible methods to seek within an I/O object. +/// +/// It is used by the [`Seek`] trait. +#[derive(Copy, PartialEq, Eq, Clone, Debug)] +pub enum SeekFrom { + /// Sets the offset to the provided number of bytes. + Start(u64), + + /// Sets the offset to the size of this object plus the specified number of + /// bytes. + /// + /// It is possible to seek beyond the end of an object, but it's an error to + /// seek before byte 0. + End(i64), + + /// Sets the offset to the current position plus the specified number of + /// bytes. + /// + /// It is possible to seek beyond the end of an object, but it's an error to + /// seek before byte 0. + Current(i64), +} + +/// A `BufRead` is a type of `Read`er which has an internal buffer, allowing it +/// to perform extra ways of reading. +pub trait BufRead: Read { + /// Returns the contents of the internal buffer, filling it with more data + /// from the inner reader if it is empty. + fn fill_buf(&mut self) -> Result<&[u8]>; + + /// Tells this buffer that `amt` bytes have been consumed from the buffer, + /// so they should no longer be returned in calls to `read`. + fn consume(&mut self, amt: usize); + + /// Check if the underlying `Read` has any data left to be read. + fn has_data_left(&mut self) -> Result { + self.fill_buf().map(|b| !b.is_empty()) + } + + /// Read all bytes into `buf` until the delimiter `byte` or EOF is reached. + #[cfg(feature = "alloc")] + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> Result { + let mut read = 0; + loop { + let (done, used) = { + let available = match self.fill_buf() { + Ok(n) => n, + Err(Error::WouldBlock) => continue, + Err(e) => return Err(e), + }; + match available.iter().position(|&b| b == byte) { + Some(i) => { + buf.extend_from_slice(&available[..=i]); + (true, i + 1) + } + None => { + buf.extend_from_slice(available); + (false, available.len()) + } + } + }; + self.consume(used); + read += used; + if done || used == 0 { + return Ok(read); + } + } + } + + /// Read all bytes until a newline (the `0xA` byte) is reached, and append + /// them to the provided `String` buffer. + #[cfg(feature = "alloc")] + fn read_line(&mut self, buf: &mut String) -> Result { + unsafe { append_to_string(buf, |b| self.read_until(b'\n', b)) } + } +} + +#[cfg(feature = "alloc")] +unsafe fn append_to_string(buf: &mut String, f: F) -> Result +where + F: FnOnce(&mut Vec) -> Result, +{ + let old_len = buf.len(); + let buf = unsafe { buf.as_mut_vec() }; + let ret = f(buf)?; + if core::str::from_utf8(&buf[old_len..]).is_err() { + ax_err!(InvalidData, "stream did not contain valid UTF-8") + } else { + Ok(ret) + } +} + +/// I/O poll results. +#[derive(Debug, Default, Clone, Copy)] +pub struct PollState { + /// Object can be read now. + pub readable: bool, + /// Object can be writen now. + pub writable: bool, +} diff --git a/crates/axio/src/prelude.rs b/crates/axio/src/prelude.rs new file mode 100644 index 000000000..9f8020c3d --- /dev/null +++ b/crates/axio/src/prelude.rs @@ -0,0 +1,11 @@ +//! The I/O Prelude. +//! +//! The purpose of this module is to alleviate imports of many common I/O traits +//! by adding a glob import to the top of I/O heavy modules: +//! +//! ``` +//! # #![allow(unused_imports)] +//! use std::io::prelude::*; +//! ``` + +pub use super::{BufRead, Read, Seek, Write}; diff --git a/crates/capability/Cargo.toml b/crates/capability/Cargo.toml new file mode 100644 index 000000000..274f30863 --- /dev/null +++ b/crates/capability/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "capability" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Provide basic capability-based security" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/capability" +documentation = "https://rcore-os.github.io/arceos/capability/index.html" + +[dependencies] +bitflags = "2.2" +axerrno = { path = "../axerrno" } diff --git a/crates/capability/src/lib.rs b/crates/capability/src/lib.rs new file mode 100644 index 000000000..6ac23247e --- /dev/null +++ b/crates/capability/src/lib.rs @@ -0,0 +1,139 @@ +//! Provide basic [capability-based security]. +//! +//! The wrapper type [`WithCap`] associates a **capability** to an object, that +//! is a set of access rights. When accessing the object, we must explicitly +//! specify the access capability, and it must not violate the capability +//! associated with the object at initialization. +//! +//! # Examples +//! +//! ``` +//! use capability::{Cap, WithCap}; +//! +//! let data = WithCap::new(42, Cap::READ | Cap::WRITE); +//! +//! // Access with the correct capability. +//! assert_eq!(data.access(Cap::READ).unwrap(), &42); +//! assert_eq!(data.access(Cap::WRITE).unwrap(), &42); +//! assert_eq!(data.access(Cap::READ | Cap::WRITE).unwrap(), &42); +//! +//! // Access with the incorrect capability. +//! assert!(data.access(Cap::EXECUTE).is_err()); +//! assert!(data.access(Cap::READ | Cap::EXECUTE).is_err()); +//! ``` +//! +//! [capability-based security]: +//! https://en.wikipedia.org/wiki/Capability-based_security +//! + +#![no_std] + +bitflags::bitflags! { + /// Capabilities (access rights). + #[derive(Default, Debug, Clone, Copy)] + pub struct Cap: u32 { + /// Readable access. + const READ = 1 << 0; + /// Writable access. + const WRITE = 1 << 1; + /// Executable access. + const EXECUTE = 1 << 2; + } +} + +/// Error type for capability violation. +#[derive(Debug, Default, Eq, PartialEq)] +#[non_exhaustive] +pub struct CapError; + +/// A wrapper that holds a type with a capability. +pub struct WithCap { + inner: T, + cap: Cap, +} + +impl WithCap { + /// Create a new instance with the given capability. + pub fn new(inner: T, cap: Cap) -> Self { + Self { inner, cap } + } + + /// Get the capability. + pub const fn cap(&self) -> Cap { + self.cap + } + + /// Check if the inner data can be accessed with the given capability. + /// + /// # Examples + /// + /// ``` + /// use capability::{Cap, WithCap}; + /// + /// let data = WithCap::new(42, Cap::READ); + /// + /// assert!(data.can_access(Cap::READ)); + /// assert!(!data.can_access(Cap::WRITE)); + /// ``` + pub const fn can_access(&self, cap: Cap) -> bool { + self.cap.contains(cap) + } + + /// Access the inner value without capability check. + /// + /// # Safety + /// + /// Caller must ensure not to violate the capability. + pub unsafe fn access_unchecked(&self) -> &T { + &self.inner + } + + /// Access the inner value with the given capability, or return `CapError` + /// if cannot access. + /// + /// # Examples + /// + /// ``` + /// use capability::{Cap, CapError, WithCap}; + /// + /// let data = WithCap::new(42, Cap::READ); + /// + /// assert_eq!(data.access(Cap::READ).unwrap(), &42); + /// assert_eq!(data.access(Cap::WRITE).err(), Some(CapError::default())); + /// ``` + pub const fn access(&self, cap: Cap) -> Result<&T, CapError> { + if self.can_access(cap) { + Ok(&self.inner) + } else { + Err(CapError) + } + } + + /// Access the inner value with the given capability, or return the given + /// `err` if cannot access. + /// + /// # Examples + /// + /// ``` + /// use capability::{Cap, WithCap}; + /// + /// let data = WithCap::new(42, Cap::READ); + /// + /// assert_eq!(data.access_or_err(Cap::READ, "cannot read").unwrap(), &42); + /// assert_eq!(data.access_or_err(Cap::WRITE, "cannot write").err(), Some("cannot write")); + /// ``` + pub fn access_or_err(&self, cap: Cap, err: E) -> Result<&T, E> { + if self.can_access(cap) { + Ok(&self.inner) + } else { + Err(err) + } + } +} + +impl From for axerrno::AxError { + #[inline] + fn from(_: CapError) -> Self { + Self::PermissionDenied + } +} diff --git a/crates/crate_interface/Cargo.toml b/crates/crate_interface/Cargo.toml new file mode 100644 index 000000000..7608daea3 --- /dev/null +++ b/crates/crate_interface/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "crate_interface" +version = "0.1.1" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Provides a way to define an interface (trait) in a crate, but can implement or use it in any crate." +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/crate_interface" +documentation = "https://rcore-os.github.io/arceos/crate_interface/index.html" +keywords = ["arceos", "api", "macro"] +categories = ["development-tools::procedural-macro-helpers", "no-std"] + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full"] } + +[lib] +proc-macro = true diff --git a/crates/crate_interface/README.md b/crates/crate_interface/README.md new file mode 100644 index 000000000..ca910ca7c --- /dev/null +++ b/crates/crate_interface/README.md @@ -0,0 +1,38 @@ +# crate_interface + +[![Crates.io](https://img.shields.io/crates/v/crate_interface)](https://crates.io/crates/crate_interface) + +Provides a way to **define** an interface (trait) in a crate, but can +**implement** or **use** it in any crate. It 's usually used to solve +the problem of *circular dependencies* between crates. + +## Example + +```rust +// Define the interface +#[crate_interface::def_interface] +pub trait HelloIf { + fn hello(&self, name: &str, id: usize) -> String; +} + +// Implement the interface in any crate +struct HelloIfImpl; + +#[crate_interface::impl_interface] +impl HelloIf for HelloIfImpl { + fn hello(&self, name: &str, id: usize) -> String { + format!("Hello, {} {}!", name, id) + } +} + +// Call `HelloIfImpl::hello` in any crate +use crate_interface::call_interface; +assert_eq!( + call_interface!(HelloIf::hello("world", 123)), + "Hello, world 123!" +); +assert_eq!( + call_interface!(HelloIf::hello, "rust", 456), // another calling style + "Hello, rust 456!" +); +``` diff --git a/crates/crate_interface/src/lib.rs b/crates/crate_interface/src/lib.rs new file mode 100644 index 000000000..adc2621de --- /dev/null +++ b/crates/crate_interface/src/lib.rs @@ -0,0 +1,187 @@ +#![doc = include_str!("../README.md")] +#![feature(iter_next_chunk)] + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{format_ident, quote}; +use syn::{Error, FnArg, ImplItem, ImplItemFn, ItemImpl, ItemTrait, TraitItem, Type}; + +fn compiler_error(err: Error) -> TokenStream { + err.to_compile_error().into() +} + +/// Define an interface. +/// +/// This attribute should be added above the definition of a trait. All traits +/// that use the attribute cannot have the same name. +/// +/// It is not necessary to define it in the same crate as the implementation, +/// but it is required that these crates are linked together. +/// +/// See the [crate-level documentation](crate) for more details. +#[proc_macro_attribute] +pub fn def_interface(attr: TokenStream, item: TokenStream) -> TokenStream { + if !attr.is_empty() { + return compiler_error(Error::new( + Span::call_site(), + "expect an empty attribute: `#[crate_interface_def]`", + )); + } + + let ast = syn::parse_macro_input!(item as ItemTrait); + let trait_name = &ast.ident; + + let mut extern_fn_list = vec![]; + for item in &ast.items { + if let TraitItem::Fn(method) = item { + let mut sig = method.sig.clone(); + let fn_name = &sig.ident; + sig.ident = format_ident!("__{}_{}", trait_name, fn_name); + sig.inputs = syn::punctuated::Punctuated::new(); + + for arg in &method.sig.inputs { + if let FnArg::Typed(_) = arg { + sig.inputs.push(arg.clone()); + } + } + + let extern_fn = quote! { + #sig; + }; + extern_fn_list.push(extern_fn); + } + } + + quote! { + #ast + extern "Rust" { + #(#extern_fn_list)* + } + } + .into() +} + +/// Implement the interface for a struct. +/// +/// This attribute should be added above the implementation of a trait for a +/// struct, and the trait must be defined with +/// [`#[def_interface]`](macro@crate::def_interface). +/// +/// It is not necessary to implement it in the same crate as the definition, but +/// it is required that these crates are linked together. +/// +/// See the [crate-level documentation](crate) for more details. +#[proc_macro_attribute] +pub fn impl_interface(attr: TokenStream, item: TokenStream) -> TokenStream { + if !attr.is_empty() { + return compiler_error(Error::new( + Span::call_site(), + "expect an empty attribute: `#[crate_interface_impl]`", + )); + } + + let mut ast = syn::parse_macro_input!(item as ItemImpl); + let trait_name = if let Some((_, path, _)) = &ast.trait_ { + &path.segments.last().unwrap().ident + } else { + return compiler_error(Error::new_spanned(ast, "expect a trait implementation")); + }; + let impl_name = if let Type::Path(path) = &ast.self_ty.as_ref() { + path.path.get_ident().unwrap() + } else { + return compiler_error(Error::new_spanned(ast, "expect a trait implementation")); + }; + + for item in &mut ast.items { + if let ImplItem::Fn(method) = item { + let (attrs, vis, sig, stmts) = + (&method.attrs, &method.vis, &method.sig, &method.block.stmts); + let fn_name = &sig.ident; + let extern_fn_name = format_ident!("__{}_{}", trait_name, fn_name).to_string(); + + let mut new_sig = sig.clone(); + new_sig.ident = format_ident!("{}", extern_fn_name); + new_sig.inputs = syn::punctuated::Punctuated::new(); + + let mut args = vec![]; + let mut has_self = false; + for arg in &sig.inputs { + match arg { + FnArg::Receiver(_) => has_self = true, + FnArg::Typed(ty) => { + args.push(ty.pat.clone()); + new_sig.inputs.push(arg.clone()); + } + } + } + + let call_impl = if has_self { + quote! { + let IMPL: #impl_name = #impl_name; + IMPL.#fn_name( #(#args),* ) + } + } else { + quote! { #impl_name::#fn_name( #(#args),* ) } + }; + + let item = quote! { + #(#attrs)* + #vis + #sig + { + { + #[export_name = #extern_fn_name] + extern "Rust" #new_sig { + #call_impl + } + } + #(#stmts)* + } + } + .into(); + *method = syn::parse_macro_input!(item as ImplItemFn); + } + } + + quote! { #ast }.into() +} + +/// Call a function in the interface. +/// +/// It is not necessary to call it in the same crate as the implementation, but +/// it is required that these crates are linked together. +/// +/// See the [crate-level documentation](crate) for more details. +#[proc_macro] +pub fn call_interface(item: TokenStream) -> TokenStream { + parse_call_interface(item) + .unwrap_or_else(|msg| compiler_error(Error::new(Span::call_site(), msg))) +} + +fn parse_call_interface(item: TokenStream) -> Result { + let mut iter = item.into_iter(); + let tt = iter + .next_chunk::<4>() + .or(Err("expect `Trait::func`"))? + .map(|t| t.to_string()); + + let trait_name = &tt[0]; + if tt[1] != ":" || tt[2] != ":" { + return Err("missing `::`".into()); + } + let fn_name = &tt[3]; + let extern_fn_name = format!("__{}_{}", trait_name, fn_name); + + let mut args = iter.map(|x| x.to_string()).collect::>().join(""); + if args.starts_with(',') { + args.remove(0); + } else if args.starts_with('(') && args.ends_with(')') { + args.remove(0); + args.pop(); + } + + let call = format!("unsafe {{ {}( {} ) }}", extern_fn_name, args); + Ok(call + .parse::() + .or(Err("expect a correct argument list"))?) +} diff --git a/crates/crate_interface/tests/test_crate_interface.rs b/crates/crate_interface/tests/test_crate_interface.rs new file mode 100644 index 000000000..2c616eafd --- /dev/null +++ b/crates/crate_interface/tests/test_crate_interface.rs @@ -0,0 +1,34 @@ +use crate_interface::*; + +#[def_interface] +trait SimpleIf { + fn foo() -> u32 { + 123 + } + + /// Test comments + fn bar(&self, a: u16, b: &[u8], c: &str); +} + +struct SimpleIfImpl; + +#[impl_interface] +impl SimpleIf for SimpleIfImpl { + #[inline] + fn foo() -> u32 { + 456 + } + + /// Test comments2 + fn bar(&self, a: u16, b: &[u8], c: &str) { + println!("{} {:?} {}", a, b, c); + assert_eq!(b[1], 3); + } +} + +#[test] +fn test_crate_interface_call() { + call_interface!(SimpleIf::bar, 123, &[2, 3, 5, 7, 11], "test"); + call_interface!(SimpleIf::bar(123, &[2, 3, 5, 7, 11], "test")); + assert_eq!(call_interface!(SimpleIf::foo), 456); +} diff --git a/crates/driver_block/Cargo.toml b/crates/driver_block/Cargo.toml new file mode 100644 index 000000000..c81c00001 --- /dev/null +++ b/crates/driver_block/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "driver_block" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Common traits and types for block storage drivers" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/driver_common" +documentation = "https://rcore-os.github.io/arceos/driver_common/index.html" + +[features] +ramdisk = [] +bcm2835-sdhci = ["dep:bcm2835-sdhci"] +default = [] + +[dependencies] +log = "0.4" +driver_common = { path = "../driver_common" } +bcm2835-sdhci = { git = "https://github.com/lhw2002426/bcm2835-sdhci.git", rev = "e974f16", optional = true } diff --git a/crates/driver_block/src/bcm2835sdhci.rs b/crates/driver_block/src/bcm2835sdhci.rs new file mode 100644 index 000000000..412a800f0 --- /dev/null +++ b/crates/driver_block/src/bcm2835sdhci.rs @@ -0,0 +1,88 @@ +//! SD card driver for raspi4 + +extern crate alloc; +use crate::BlockDriverOps; +use bcm2835_sdhci::Bcm2835SDhci::{EmmcCtl, BLOCK_SIZE}; +use bcm2835_sdhci::SDHCIError; +use driver_common::{BaseDriverOps, DevError, DevResult, DeviceType}; + +/// BCM2835 SDHCI driver (Raspberry Pi SD card). +pub struct SDHCIDriver(EmmcCtl); + +impl SDHCIDriver { + /// Initialize the SDHCI driver, returns `Ok` if successful. + pub fn try_new() -> DevResult { + let mut ctrl = EmmcCtl::new(); + if ctrl.init() == 0 { + log::info!("BCM2835 sdhci: successfully initialized"); + Ok(SDHCIDriver(ctrl)) + } else { + log::warn!("BCM2835 sdhci: init failed"); + Err(DevError::Io) + } + } +} + +fn deal_sdhci_err(err: SDHCIError) -> DevError { + match err { + SDHCIError::Io => DevError::Io, + SDHCIError::AlreadyExists => DevError::AlreadyExists, + SDHCIError::Again => DevError::Again, + SDHCIError::BadState => DevError::BadState, + SDHCIError::InvalidParam => DevError::InvalidParam, + SDHCIError::NoMemory => DevError::NoMemory, + SDHCIError::ResourceBusy => DevError::ResourceBusy, + SDHCIError::Unsupported => DevError::Unsupported, + } +} + +impl BaseDriverOps for SDHCIDriver { + fn device_type(&self) -> DeviceType { + DeviceType::Block + } + + fn device_name(&self) -> &str { + "bcm2835_sdhci" + } +} + +impl BlockDriverOps for SDHCIDriver { + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { + if buf.len() < BLOCK_SIZE { + return Err(DevError::InvalidParam); + } + let (prefix, aligned_buf, suffix) = unsafe { buf.align_to_mut::() }; + if !prefix.is_empty() || !suffix.is_empty() { + return Err(DevError::InvalidParam); + } + self.0 + .read_block(block_id as u32, 1, aligned_buf) + .map_err(deal_sdhci_err) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { + if buf.len() < BLOCK_SIZE { + return Err(DevError::Io); + } + let (prefix, aligned_buf, suffix) = unsafe { buf.align_to::() }; + if !prefix.is_empty() || !suffix.is_empty() { + return Err(DevError::InvalidParam); + } + self.0 + .write_block(block_id as u32, 1, aligned_buf) + .map_err(deal_sdhci_err) + } + fn flush(&mut self) -> DevResult { + Ok(()) + } + + #[inline] + fn num_blocks(&self) -> u64 { + self.0.get_block_num() + } + + #[inline] + fn block_size(&self) -> usize { + self.0.get_block_size() + } +} diff --git a/crates/driver_block/src/lib.rs b/crates/driver_block/src/lib.rs new file mode 100644 index 000000000..f2ae63cec --- /dev/null +++ b/crates/driver_block/src/lib.rs @@ -0,0 +1,39 @@ +//! Common traits and types for block storage device drivers (i.e. disk). + +#![no_std] +#![feature(doc_auto_cfg)] +#![feature(const_trait_impl)] + +#[cfg(feature = "ramdisk")] +pub mod ramdisk; + +#[cfg(feature = "bcm2835-sdhci")] +pub mod bcm2835sdhci; + +#[doc(no_inline)] +pub use driver_common::{BaseDriverOps, DevError, DevResult, DeviceType}; + +/// Operations that require a block storage device driver to implement. +pub trait BlockDriverOps: BaseDriverOps { + /// The number of blocks in this storage device. + /// + /// The total size of the device is `num_blocks() * block_size()`. + fn num_blocks(&self) -> u64; + /// The size of each block in bytes. + fn block_size(&self) -> usize; + + /// Reads blocked data from the given block. + /// + /// The size of the buffer may exceed the block size, in which case multiple + /// contiguous blocks will be read. + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult; + + /// Writes blocked data to the given block. + /// + /// The size of the buffer may exceed the block size, in which case multiple + /// contiguous blocks will be written. + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult; + + /// Flushes the device to write all pending data to the storage. + fn flush(&mut self) -> DevResult; +} diff --git a/crates/driver_block/src/ramdisk.rs b/crates/driver_block/src/ramdisk.rs new file mode 100644 index 000000000..2e240c787 --- /dev/null +++ b/crates/driver_block/src/ramdisk.rs @@ -0,0 +1,100 @@ +//! Mock block devices that store data in RAM. + +extern crate alloc; + +use crate::BlockDriverOps; +use alloc::{vec, vec::Vec}; +use driver_common::{BaseDriverOps, DevError, DevResult, DeviceType}; + +const BLOCK_SIZE: usize = 512; + +/// A RAM disk that stores data in a vector. +#[derive(Default)] +pub struct RamDisk { + size: usize, + data: Vec, +} + +impl RamDisk { + /// Creates a new RAM disk with the given size hint. + /// + /// The actual size of the RAM disk will be aligned upwards to the block + /// size (512 bytes). + pub fn new(size_hint: usize) -> Self { + let size = align_up(size_hint); + Self { + size, + data: vec![0; size], + } + } + + /// Creates a new RAM disk from the exiting data. + /// + /// The actual size of the RAM disk will be aligned upwards to the block + /// size (512 bytes). + pub fn from(buf: &[u8]) -> Self { + let size = align_up(buf.len()); + let mut data = vec![0; size]; + data[..buf.len()].copy_from_slice(buf); + Self { size, data } + } + + /// Returns the size of the RAM disk in bytes. + pub const fn size(&self) -> usize { + self.size + } +} + +impl const BaseDriverOps for RamDisk { + fn device_type(&self) -> DeviceType { + DeviceType::Block + } + + fn device_name(&self) -> &str { + "ramdisk" + } +} + +impl BlockDriverOps for RamDisk { + #[inline] + fn num_blocks(&self) -> u64 { + (self.size / BLOCK_SIZE) as u64 + } + + #[inline] + fn block_size(&self) -> usize { + BLOCK_SIZE + } + + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { + let offset = block_id as usize * BLOCK_SIZE; + if offset + buf.len() > self.size { + return Err(DevError::Io); + } + if buf.len() % BLOCK_SIZE != 0 { + return Err(DevError::InvalidParam); + } + buf.copy_from_slice(&self.data[offset..offset + buf.len()]); + Ok(()) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { + let offset = block_id as usize * BLOCK_SIZE; + if offset + buf.len() > self.size { + return Err(DevError::Io); + } + if buf.len() % BLOCK_SIZE != 0 { + return Err(DevError::InvalidParam); + } + self.data[offset..offset + buf.len()].copy_from_slice(buf); + Ok(()) + } + + fn flush(&mut self) -> DevResult { + Ok(()) + } +} + +const fn align_up(val: usize) -> usize { + (val + BLOCK_SIZE - 1) & !(BLOCK_SIZE - 1) +} diff --git a/crates/driver_common/Cargo.toml b/crates/driver_common/Cargo.toml new file mode 100644 index 000000000..b3bfe7add --- /dev/null +++ b/crates/driver_common/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "driver_common" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Device driver interfaces used by ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/driver_common" +documentation = "https://rcore-os.github.io/arceos/driver_common/index.html" + +[dependencies] diff --git a/crates/driver_common/src/lib.rs b/crates/driver_common/src/lib.rs new file mode 100644 index 000000000..c249b9883 --- /dev/null +++ b/crates/driver_common/src/lib.rs @@ -0,0 +1,64 @@ +//! Device driver interfaces used by [ArceOS][1]. It provides common traits and +//! types for implementing a device driver. +//! +//! You have to use this crate with the following crates for corresponding +//! device types: +//! +//! - [`driver_block`][2]: Common traits for block storage drivers. +//! - [`driver_display`][3]: Common traits and types for graphics display drivers. +//! - [`driver_net`][4]: Common traits and types for network (NIC) drivers. +//! +//! [1]: https://github.com/rcore-os/arceos +//! [2]: ../driver_block/index.html +//! [3]: ../driver_display/index.html +//! [4]: ../driver_net/index.html + +#![no_std] +#![feature(const_trait_impl)] + +/// All supported device types. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum DeviceType { + /// Block storage device (e.g., disk). + Block, + /// Character device (e.g., serial port). + Char, + /// Network device (e.g., ethernet card). + Net, + /// Graphic display device (e.g., GPU) + Display, +} + +/// The error type for device operation failures. +#[derive(Debug)] +pub enum DevError { + /// An entity already exists. + AlreadyExists, + /// Try again, for non-blocking APIs. + Again, + /// Bad internal state. + BadState, + /// Invalid parameter/argument. + InvalidParam, + /// Input/output error. + Io, + /// Not enough space/cannot allocate memory (DMA). + NoMemory, + /// Device or resource is busy. + ResourceBusy, + /// This operation is unsupported or unimplemented. + Unsupported, +} + +/// A specialized `Result` type for device operations. +pub type DevResult = Result; + +/// Common operations that require all device drivers to implement. +#[const_trait] +pub trait BaseDriverOps: Send + Sync { + /// The name of the device. + fn device_name(&self) -> &str; + + /// The type of the device. + fn device_type(&self) -> DeviceType; +} diff --git a/crates/driver_display/Cargo.toml b/crates/driver_display/Cargo.toml new file mode 100644 index 000000000..e199c626b --- /dev/null +++ b/crates/driver_display/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "driver_display" +version = "0.1.0" +edition = "2021" +authors = ["Shiping Yuan "] +description = "Common traits and types for graphics device drivers" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/driver_display" +documentation = "https://rcore-os.github.io/arceos/driver_display/index.html" + +[dependencies] +driver_common = { path = "../driver_common" } diff --git a/crates/driver_display/src/lib.rs b/crates/driver_display/src/lib.rs new file mode 100644 index 000000000..5ad32b4da --- /dev/null +++ b/crates/driver_display/src/lib.rs @@ -0,0 +1,59 @@ +//! Common traits and types for graphics display device drivers. + +#![no_std] + +#[doc(no_inline)] +pub use driver_common::{BaseDriverOps, DevError, DevResult, DeviceType}; + +/// The information of the graphics device. +#[derive(Debug, Clone, Copy)] +pub struct DisplayInfo { + /// The visible width. + pub width: u32, + /// The visible height. + pub height: u32, + /// The base virtual address of the framebuffer. + pub fb_base_vaddr: usize, + /// The size of the framebuffer in bytes. + pub fb_size: usize, +} + +/// The framebuffer. +/// +/// It's a special memory buffer that mapped from the device memory. +pub struct FrameBuffer<'a> { + _raw: &'a mut [u8], +} + +impl<'a> FrameBuffer<'a> { + /// Use the given raw pointer and size as the framebuffer. + /// + /// # Safety + /// + /// Caller must insure that the given memory region is valid and accessible. + pub unsafe fn from_raw_parts_mut(ptr: *mut u8, len: usize) -> Self { + Self { + _raw: core::slice::from_raw_parts_mut(ptr, len), + } + } + + /// Use the given slice as the framebuffer. + pub fn from_slice(slice: &'a mut [u8]) -> Self { + Self { _raw: slice } + } +} + +/// Operations that require a graphics device driver to implement. +pub trait DisplayDriverOps: BaseDriverOps { + /// Get the display information. + fn info(&self) -> DisplayInfo; + + /// Get the framebuffer. + fn fb(&self) -> FrameBuffer; + + /// Whether need to flush the framebuffer to the screen. + fn need_flush(&self) -> bool; + + /// Flush framebuffer to the screen. + fn flush(&mut self) -> DevResult; +} diff --git a/crates/driver_net/Cargo.toml b/crates/driver_net/Cargo.toml new file mode 100644 index 000000000..5018c4a7c --- /dev/null +++ b/crates/driver_net/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "driver_net" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia ", "ChengXiang Qi "] +description = "Common traits and types for network device (NIC) drivers" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/driver_net" +documentation = "https://rcore-os.github.io/arceos/driver_net/index.html" + +[features] +default = [] +ixgbe = ["dep:ixgbe-driver"] + +[dependencies] +spin = "0.9" +log = "0.4" +driver_common = { path = "../driver_common" } +ixgbe-driver = {git = "https://github.com/KuangjuX/ixgbe-driver.git", rev = "8e5eb74", optional = true} diff --git a/crates/driver_net/src/ixgbe.rs b/crates/driver_net/src/ixgbe.rs new file mode 100644 index 000000000..b6a2c1b3a --- /dev/null +++ b/crates/driver_net/src/ixgbe.rs @@ -0,0 +1,160 @@ +use core::convert::From; +use core::{mem::ManuallyDrop, ptr::NonNull}; + +use alloc::{collections::VecDeque, sync::Arc}; +use driver_common::{BaseDriverOps, DevError, DevResult, DeviceType}; +use ixgbe_driver::{IxgbeDevice, IxgbeError, IxgbeNetBuf, MemPool, NicDevice}; +pub use ixgbe_driver::{IxgbeHal, PhysAddr, INTEL_82599, INTEL_VEND}; + +use crate::{EthernetAddress, NetBufPtr, NetDriverOps}; + +extern crate alloc; + +const RECV_BATCH_SIZE: usize = 64; +const RX_BUFFER_SIZE: usize = 1024; +const MEM_POOL: usize = 4096; +const MEM_POOL_ENTRY_SIZE: usize = 2048; + +/// The ixgbe NIC device driver. +/// +/// `QS` is the ixgbe queue size, `QN` is the ixgbe queue num. +pub struct IxgbeNic { + inner: IxgbeDevice, + mem_pool: Arc, + rx_buffer_queue: VecDeque, +} + +unsafe impl Sync for IxgbeNic {} +unsafe impl Send for IxgbeNic {} + +impl IxgbeNic { + /// Creates a net ixgbe NIC instance and initialize, or returns a error if + /// any step fails. + pub fn init(base: usize, len: usize) -> DevResult { + let mem_pool = MemPool::allocate::(MEM_POOL, MEM_POOL_ENTRY_SIZE) + .map_err(|_| DevError::NoMemory)?; + let inner = IxgbeDevice::::init(base, len, QN, QN, &mem_pool).map_err(|err| { + log::error!("Failed to initialize ixgbe device: {:?}", err); + DevError::BadState + })?; + + let rx_buffer_queue = VecDeque::with_capacity(RX_BUFFER_SIZE); + Ok(Self { + inner, + mem_pool, + rx_buffer_queue, + }) + } +} + +impl BaseDriverOps for IxgbeNic { + fn device_name(&self) -> &str { + self.inner.get_driver_name() + } + + fn device_type(&self) -> DeviceType { + DeviceType::Net + } +} + +impl NetDriverOps for IxgbeNic { + fn mac_address(&self) -> EthernetAddress { + EthernetAddress(self.inner.get_mac_addr()) + } + + fn rx_queue_size(&self) -> usize { + QS + } + + fn tx_queue_size(&self) -> usize { + QS + } + + fn can_receive(&self) -> bool { + !self.rx_buffer_queue.is_empty() || self.inner.can_receive(0).unwrap() + } + + fn can_transmit(&self) -> bool { + // Default implementation is return true forever. + self.inner.can_send(0).unwrap() + } + + fn recycle_rx_buffer(&mut self, rx_buf: NetBufPtr) -> DevResult { + let rx_buf = ixgbe_ptr_to_buf(rx_buf, &self.mem_pool)?; + drop(rx_buf); + Ok(()) + } + + fn recycle_tx_buffers(&mut self) -> DevResult { + self.inner + .recycle_tx_buffers(0) + .map_err(|_| DevError::BadState)?; + Ok(()) + } + + fn receive(&mut self) -> DevResult { + if !self.can_receive() { + return Err(DevError::Again); + } + if !self.rx_buffer_queue.is_empty() { + // RX buffer have received packets. + Ok(self.rx_buffer_queue.pop_front().unwrap()) + } else { + // RX queue is empty, receive from ixgbe NIC. + match self.inner.receive_packets(0, RECV_BATCH_SIZE, |rx_buf| { + let rx_buf = NetBufPtr::from(rx_buf); + self.rx_buffer_queue.push_back(rx_buf); + }) { + Ok(recv_nums) => { + if recv_nums == 0 { + // No packet is received, it is impossible things. + panic!("Error: No receive packets.") + } else { + Ok(self.rx_buffer_queue.pop_front().unwrap()) + } + } + Err(e) => match e { + IxgbeError::NotReady => Err(DevError::Again), + _ => Err(DevError::BadState), + }, + } + } + } + + fn transmit(&mut self, tx_buf: NetBufPtr) -> DevResult { + let tx_buf = ixgbe_ptr_to_buf(tx_buf, &self.mem_pool)?; + match self.inner.send(0, tx_buf) { + Ok(_) => Ok(()), + Err(err) => match err { + IxgbeError::QueueFull => Err(DevError::Again), + _ => panic!("Unexpected err: {:?}", err), + }, + } + } + + fn alloc_tx_buffer(&mut self, size: usize) -> DevResult { + let tx_buf = IxgbeNetBuf::alloc(&self.mem_pool, size).map_err(|_| DevError::NoMemory)?; + Ok(NetBufPtr::from(tx_buf)) + } +} + +impl From for NetBufPtr { + fn from(buf: IxgbeNetBuf) -> Self { + // Use `ManuallyDrop` to avoid drop `tx_buf`. + let mut buf = ManuallyDrop::new(buf); + // In ixgbe, `raw_ptr` is the pool entry, `buf_ptr` is the packet ptr, `len` is packet len + // to avoid too many dynamic memory allocation. + let buf_ptr = buf.packet_mut().as_mut_ptr(); + Self::new( + NonNull::new(buf.pool_entry() as *mut u8).unwrap(), + NonNull::new(buf_ptr).unwrap(), + buf.packet_len(), + ) + } +} + +// Converts a `NetBufPtr` to `IxgbeNetBuf`. +fn ixgbe_ptr_to_buf(ptr: NetBufPtr, pool: &Arc) -> DevResult { + IxgbeNetBuf::construct(ptr.raw_ptr.as_ptr() as usize, pool, ptr.len) + .map_err(|_| DevError::BadState) +} diff --git a/crates/driver_net/src/lib.rs b/crates/driver_net/src/lib.rs new file mode 100644 index 000000000..0f9c9ed36 --- /dev/null +++ b/crates/driver_net/src/lib.rs @@ -0,0 +1,107 @@ +//! Common traits and types for network device (NIC) drivers. + +#![no_std] +#![feature(const_mut_refs)] +#![feature(const_slice_from_raw_parts_mut)] +#![feature(box_into_inner)] + +#[cfg(feature = "ixgbe")] +/// ixgbe NIC device driver. +pub mod ixgbe; +mod net_buf; + +use core::ptr::NonNull; + +#[doc(no_inline)] +pub use driver_common::{BaseDriverOps, DevError, DevResult, DeviceType}; + +pub use self::net_buf::{NetBuf, NetBufBox, NetBufPool}; + +/// The ethernet address of the NIC (MAC address). +pub struct EthernetAddress(pub [u8; 6]); + +/// Operations that require a network device (NIC) driver to implement. +pub trait NetDriverOps: BaseDriverOps { + /// The ethernet address of the NIC. + fn mac_address(&self) -> EthernetAddress; + + /// Whether can transmit packets. + fn can_transmit(&self) -> bool; + + /// Whether can receive packets. + fn can_receive(&self) -> bool; + + /// Size of the receive queue. + fn rx_queue_size(&self) -> usize; + + /// Size of the transmit queue. + fn tx_queue_size(&self) -> usize; + + /// Gives back the `rx_buf` to the receive queue for later receiving. + /// + /// `rx_buf` should be the same as the one returned by + /// [`NetDriverOps::receive`]. + fn recycle_rx_buffer(&mut self, rx_buf: NetBufPtr) -> DevResult; + + /// Poll the transmit queue and gives back the buffers for previous transmiting. + /// returns [`DevResult`]. + fn recycle_tx_buffers(&mut self) -> DevResult; + + /// Transmits a packet in the buffer to the network, without blocking, + /// returns [`DevResult`]. + fn transmit(&mut self, tx_buf: NetBufPtr) -> DevResult; + + /// Receives a packet from the network and store it in the [`NetBuf`], + /// returns the buffer. + /// + /// Before receiving, the driver should have already populated some buffers + /// in the receive queue by [`NetDriverOps::recycle_rx_buffer`]. + /// + /// If currently no incomming packets, returns an error with type + /// [`DevError::Again`]. + fn receive(&mut self) -> DevResult; + + /// Allocate a memory buffer of a specified size for network transmission, + /// returns [`DevResult`] + fn alloc_tx_buffer(&mut self, size: usize) -> DevResult; +} + +/// A raw buffer struct for network device. +pub struct NetBufPtr { + // The raw pointer of the original object. + raw_ptr: NonNull, + // The pointer to the net buffer. + buf_ptr: NonNull, + len: usize, +} + +impl NetBufPtr { + /// Create a new [`NetBufPtr`]. + pub fn new(raw_ptr: NonNull, buf_ptr: NonNull, len: usize) -> Self { + Self { + raw_ptr, + buf_ptr, + len, + } + } + + /// Return raw pointer of the original object. + pub fn raw_ptr(&self) -> *mut T { + self.raw_ptr.as_ptr() as *mut T + } + + /// Return [`NetBufPtr`] buffer len. + pub fn packet_len(&self) -> usize { + self.len + } + + /// Return [`NetBufPtr`] buffer as &[u8]. + pub fn packet(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.buf_ptr.as_ptr() as *const u8, self.len) } + } + + /// Return [`NetBufPtr`] buffer as &mut [u8]. + pub fn packet_mut(&mut self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.buf_ptr.as_ptr(), self.len) } + } +} diff --git a/crates/driver_net/src/net_buf.rs b/crates/driver_net/src/net_buf.rs new file mode 100644 index 000000000..657674938 --- /dev/null +++ b/crates/driver_net/src/net_buf.rs @@ -0,0 +1,208 @@ +extern crate alloc; + +use crate::{DevError, DevResult, NetBufPtr}; +use alloc::{boxed::Box, sync::Arc, vec, vec::Vec}; +use core::ptr::NonNull; +use spin::Mutex; + +const MIN_BUFFER_LEN: usize = 1526; +const MAX_BUFFER_LEN: usize = 65535; + +/// A RAII network buffer wrapped in a [`Box`]. +pub type NetBufBox = Box; + +/// A RAII network buffer. +/// +/// It should be allocated from the [`NetBufPool`], and it will be +/// deallocated into the pool automatically when dropped. +/// +/// The layout of the buffer is: +/// +/// ```text +/// ______________________ capacity ______________________ +/// / \ +/// +------------------+------------------+------------------+ +/// | Header | Packet | Unused | +/// +------------------+------------------+------------------+ +/// |\__ header_len __/ \__ packet_len __/ +/// | +/// buf_ptr +/// ``` +pub struct NetBuf { + header_len: usize, + packet_len: usize, + capacity: usize, + buf_ptr: NonNull, + pool_offset: usize, + pool: Arc, +} + +unsafe impl Send for NetBuf {} +unsafe impl Sync for NetBuf {} + +impl NetBuf { + const unsafe fn get_slice(&self, start: usize, len: usize) -> &[u8] { + core::slice::from_raw_parts(self.buf_ptr.as_ptr().add(start), len) + } + + const unsafe fn get_slice_mut(&mut self, start: usize, len: usize) -> &mut [u8] { + core::slice::from_raw_parts_mut(self.buf_ptr.as_ptr().add(start), len) + } + + /// Returns the capacity of the buffer. + pub const fn capacity(&self) -> usize { + self.capacity + } + + /// Returns the length of the header part. + pub const fn header_len(&self) -> usize { + self.header_len + } + + /// Returns the header part of the buffer. + pub const fn header(&self) -> &[u8] { + unsafe { self.get_slice(0, self.header_len) } + } + + /// Returns the packet part of the buffer. + pub const fn packet(&self) -> &[u8] { + unsafe { self.get_slice(self.header_len, self.packet_len) } + } + + /// Returns the mutable reference to the packet part. + pub const fn packet_mut(&mut self) -> &mut [u8] { + unsafe { self.get_slice_mut(self.header_len, self.packet_len) } + } + + /// Returns both the header and the packet parts, as a contiguous slice. + pub const fn packet_with_header(&self) -> &[u8] { + unsafe { self.get_slice(0, self.header_len + self.packet_len) } + } + + /// Returns the entire buffer. + pub const fn raw_buf(&self) -> &[u8] { + unsafe { self.get_slice(0, self.capacity) } + } + + /// Returns the mutable reference to the entire buffer. + pub const fn raw_buf_mut(&mut self) -> &mut [u8] { + unsafe { self.get_slice_mut(0, self.capacity) } + } + + /// Set the length of the header part. + pub fn set_header_len(&mut self, header_len: usize) { + debug_assert!(header_len + self.packet_len <= self.capacity); + self.header_len = header_len; + } + + /// Set the length of the packet part. + pub fn set_packet_len(&mut self, packet_len: usize) { + debug_assert!(self.header_len + packet_len <= self.capacity); + self.packet_len = packet_len; + } + + /// Converts the buffer into a [`NetBufPtr`]. + pub fn into_buf_ptr(mut self: Box) -> NetBufPtr { + let buf_ptr = self.packet_mut().as_mut_ptr(); + let len = self.packet_len; + NetBufPtr::new( + NonNull::new(Box::into_raw(self) as *mut u8).unwrap(), + NonNull::new(buf_ptr).unwrap(), + len, + ) + } + + /// Restore [`NetBuf`] struct from a raw pointer. + /// + /// # Safety + /// + /// This function is unsafe because it may cause some memory issues, + /// so we must ensure that it is called after calling `into_buf_ptr`. + pub unsafe fn from_buf_ptr(ptr: NetBufPtr) -> Box { + Box::from_raw(ptr.raw_ptr::()) + } +} + +impl Drop for NetBuf { + /// Deallocates the buffer into the [`NetBufPool`]. + fn drop(&mut self) { + self.pool.dealloc(self.pool_offset); + } +} + +/// A pool of [`NetBuf`]s to speed up buffer allocation. +/// +/// It divides a large memory into several equal parts for each buffer. +pub struct NetBufPool { + capacity: usize, + buf_len: usize, + pool: Vec, + free_list: Mutex>, +} + +impl NetBufPool { + /// Creates a new pool with the given `capacity`, and all buffer lengths are + /// set to `buf_len`. + pub fn new(capacity: usize, buf_len: usize) -> DevResult> { + if capacity == 0 { + return Err(DevError::InvalidParam); + } + if !(MIN_BUFFER_LEN..=MAX_BUFFER_LEN).contains(&buf_len) { + return Err(DevError::InvalidParam); + } + + let pool = vec![0; capacity * buf_len]; + let mut free_list = Vec::with_capacity(capacity); + for i in 0..capacity { + free_list.push(i * buf_len); + } + Ok(Arc::new(Self { + capacity, + buf_len, + pool, + free_list: Mutex::new(free_list), + })) + } + + /// Returns the capacity of the pool. + pub const fn capacity(&self) -> usize { + self.capacity + } + + /// Returns the length of each buffer. + pub const fn buffer_len(&self) -> usize { + self.buf_len + } + + /// Allocates a buffer from the pool. + /// + /// Returns `None` if no buffer is available. + pub fn alloc(self: &Arc) -> Option { + let pool_offset = self.free_list.lock().pop()?; + let buf_ptr = + unsafe { NonNull::new(self.pool.as_ptr().add(pool_offset) as *mut u8).unwrap() }; + Some(NetBuf { + header_len: 0, + packet_len: 0, + capacity: self.buf_len, + buf_ptr, + pool_offset, + pool: Arc::clone(self), + }) + } + + /// Allocates a buffer wrapped in a [`Box`] from the pool. + /// + /// Returns `None` if no buffer is available. + pub fn alloc_boxed(self: &Arc) -> Option { + Some(Box::new(self.alloc()?)) + } + + /// Deallocates a buffer at the given offset. + /// + /// `pool_offset` must be a multiple of `buf_len`. + fn dealloc(&self, pool_offset: usize) { + debug_assert_eq!(pool_offset % self.buf_len, 0); + self.free_list.lock().push(pool_offset); + } +} diff --git a/crates/driver_pci/Cargo.toml b/crates/driver_pci/Cargo.toml new file mode 100644 index 000000000..062cbd0a6 --- /dev/null +++ b/crates/driver_pci/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "driver_pci" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Structures and functions for PCI bus operations" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/driver_pci" +documentation = "https://rcore-os.github.io/arceos/driver_pci/index.html" + +[dependencies] +virtio-drivers = { git = "https://github.com/rcore-os/virtio-drivers.git", rev = "409ee72" } diff --git a/crates/driver_pci/src/lib.rs b/crates/driver_pci/src/lib.rs new file mode 100644 index 000000000..4ae0b9b97 --- /dev/null +++ b/crates/driver_pci/src/lib.rs @@ -0,0 +1,53 @@ +//! Structures and functions for PCI bus operations. +//! +//! Currently, it just re-exports structures from the crate [virtio-drivers][1] +//! and its module [`virtio_drivers::transport::pci::bus`][2]. +//! +//! [1]: https://docs.rs/virtio-drivers/latest/virtio_drivers/ +//! [2]: https://docs.rs/virtio-drivers/latest/virtio_drivers/transport/pci/bus/index.html + +#![no_std] + +pub use virtio_drivers::transport::pci::bus::{BarInfo, Cam, HeaderType, MemoryBarType, PciError}; +pub use virtio_drivers::transport::pci::bus::{ + CapabilityInfo, Command, DeviceFunction, DeviceFunctionInfo, PciRoot, Status, +}; + +/// Used to allocate MMIO regions for PCI BARs. +pub struct PciRangeAllocator { + _start: u64, + end: u64, + current: u64, +} + +impl PciRangeAllocator { + /// Creates a new allocator from a memory range. + pub const fn new(base: u64, size: u64) -> Self { + Self { + _start: base, + end: base + size, + current: base, + } + } + + /// Allocates a memory region with the given size. + /// + /// The `size` should be a power of 2, and the returned value is also a + /// multiple of `size`. + pub fn alloc(&mut self, size: u64) -> Option { + if !size.is_power_of_two() { + return None; + } + let ret = align_up(self.current, size); + if ret + size > self.end { + return None; + } + + self.current = ret + size; + Some(ret) + } +} + +const fn align_up(addr: u64, align: u64) -> u64 { + (addr + align - 1) & !(align - 1) +} diff --git a/crates/driver_virtio/Cargo.toml b/crates/driver_virtio/Cargo.toml new file mode 100644 index 000000000..6131c34db --- /dev/null +++ b/crates/driver_virtio/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "driver_virtio" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia ", "ChengXiang Qi "] +description = "Wrappers of some devices in the `virtio-drivers` crate, that implement traits in the `driver_common` series crates" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/driver_virtio" +documentation = "https://rcore-os.github.io/arceos/driver_virtio/index.html" + +[features] +block = ["driver_block"] +net = ["driver_net"] +gpu = ["driver_display"] + +[dependencies] +driver_common = { path = "../driver_common" } +driver_block = { path = "../driver_block", optional = true } +driver_net = { path = "../driver_net", optional = true } +driver_display = { path = "../driver_display", optional = true} +virtio-drivers = { git = "https://github.com/rcore-os/virtio-drivers.git", rev = "409ee72" } diff --git a/crates/driver_virtio/src/blk.rs b/crates/driver_virtio/src/blk.rs new file mode 100644 index 000000000..3edaa1a08 --- /dev/null +++ b/crates/driver_virtio/src/blk.rs @@ -0,0 +1,60 @@ +use crate::as_dev_err; +use driver_block::BlockDriverOps; +use driver_common::{BaseDriverOps, DevResult, DeviceType}; +use virtio_drivers::{device::blk::VirtIOBlk as InnerDev, transport::Transport, Hal}; + +/// The VirtIO block device driver. +pub struct VirtIoBlkDev { + inner: InnerDev, +} + +unsafe impl Send for VirtIoBlkDev {} +unsafe impl Sync for VirtIoBlkDev {} + +impl VirtIoBlkDev { + /// Creates a new driver instance and initializes the device, or returns + /// an error if any step fails. + pub fn try_new(transport: T) -> DevResult { + Ok(Self { + inner: InnerDev::new(transport).map_err(as_dev_err)?, + }) + } +} + +impl const BaseDriverOps for VirtIoBlkDev { + fn device_name(&self) -> &str { + "virtio-blk" + } + + fn device_type(&self) -> DeviceType { + DeviceType::Block + } +} + +impl BlockDriverOps for VirtIoBlkDev { + #[inline] + fn num_blocks(&self) -> u64 { + self.inner.capacity() + } + + #[inline] + fn block_size(&self) -> usize { + virtio_drivers::device::blk::SECTOR_SIZE + } + + fn read_block(&mut self, block_id: u64, buf: &mut [u8]) -> DevResult { + self.inner + .read_block(block_id as _, buf) + .map_err(as_dev_err) + } + + fn write_block(&mut self, block_id: u64, buf: &[u8]) -> DevResult { + self.inner + .write_block(block_id as _, buf) + .map_err(as_dev_err) + } + + fn flush(&mut self) -> DevResult { + Ok(()) + } +} diff --git a/crates/driver_virtio/src/gpu.rs b/crates/driver_virtio/src/gpu.rs new file mode 100644 index 000000000..1acc68841 --- /dev/null +++ b/crates/driver_virtio/src/gpu.rs @@ -0,0 +1,70 @@ +extern crate alloc; +use crate::as_dev_err; + +use driver_common::{BaseDriverOps, DevResult, DeviceType}; +use driver_display::{DisplayDriverOps, DisplayInfo, FrameBuffer}; +use virtio_drivers::{device::gpu::VirtIOGpu as InnerDev, transport::Transport, Hal}; + +/// The VirtIO GPU device driver. +pub struct VirtIoGpuDev { + inner: InnerDev<'static, H, T>, + info: DisplayInfo, +} + +unsafe impl Send for VirtIoGpuDev {} +unsafe impl Sync for VirtIoGpuDev {} + +impl VirtIoGpuDev { + /// Creates a new driver instance and initializes the device, or returns + /// an error if any step fails. + pub fn try_new(transport: T) -> DevResult { + let mut virtio = InnerDev::new(transport).unwrap(); + + // get framebuffer + let fbuffer = virtio.setup_framebuffer().unwrap(); + let fb_base_vaddr = fbuffer.as_mut_ptr() as usize; + let fb_size = fbuffer.len(); + let (width, height) = virtio.resolution().unwrap(); + let info = DisplayInfo { + width, + height, + fb_base_vaddr, + fb_size, + }; + + Ok(Self { + inner: virtio, + info, + }) + } +} + +impl const BaseDriverOps for VirtIoGpuDev { + fn device_name(&self) -> &str { + "virtio-gpu" + } + + fn device_type(&self) -> DeviceType { + DeviceType::Display + } +} + +impl DisplayDriverOps for VirtIoGpuDev { + fn info(&self) -> DisplayInfo { + self.info + } + + fn fb(&self) -> FrameBuffer { + unsafe { + FrameBuffer::from_raw_parts_mut(self.info.fb_base_vaddr as *mut u8, self.info.fb_size) + } + } + + fn need_flush(&self) -> bool { + true + } + + fn flush(&mut self) -> DevResult { + self.inner.flush().map_err(as_dev_err) + } +} diff --git a/crates/driver_virtio/src/lib.rs b/crates/driver_virtio/src/lib.rs new file mode 100644 index 000000000..98bb5f30c --- /dev/null +++ b/crates/driver_virtio/src/lib.rs @@ -0,0 +1,98 @@ +//! Wrappers of some devices in the [`virtio-drivers`][1] crate, that implement +//! traits in the [`driver_common`][2] series crates. +//! +//! Like the [`virtio-drivers`][1] crate, you must implement the [`VirtIoHal`] +//! trait (alias of [`virtio-drivers::Hal`][3]), to allocate DMA regions and +//! translate between physical addresses (as seen by devices) and virtual +//! addresses (as seen by your program). +//! +//! [1]: https://docs.rs/virtio-drivers/latest/virtio_drivers/ +//! [2]: ../driver_common/index.html +//! [3]: https://docs.rs/virtio-drivers/latest/virtio_drivers/trait.Hal.html + +#![no_std] +#![feature(const_trait_impl)] +#![feature(doc_auto_cfg)] + +#[cfg(feature = "block")] +mod blk; +#[cfg(feature = "gpu")] +mod gpu; +#[cfg(feature = "net")] +mod net; + +#[cfg(feature = "block")] +pub use self::blk::VirtIoBlkDev; +#[cfg(feature = "gpu")] +pub use self::gpu::VirtIoGpuDev; +#[cfg(feature = "net")] +pub use self::net::VirtIoNetDev; + +pub use virtio_drivers::transport::pci::bus as pci; +pub use virtio_drivers::transport::{mmio::MmioTransport, pci::PciTransport, Transport}; +pub use virtio_drivers::{BufferDirection, Hal as VirtIoHal, PhysAddr}; + +use self::pci::{DeviceFunction, DeviceFunctionInfo, PciRoot}; +use driver_common::{DevError, DeviceType}; +use virtio_drivers::transport::DeviceType as VirtIoDevType; + +/// Try to probe a VirtIO MMIO device from the given memory region. +/// +/// If the device is recognized, returns the device type and a transport object +/// for later operations. Otherwise, returns [`None`]. +pub fn probe_mmio_device( + reg_base: *mut u8, + _reg_size: usize, +) -> Option<(DeviceType, MmioTransport)> { + use core::ptr::NonNull; + use virtio_drivers::transport::mmio::VirtIOHeader; + + let header = NonNull::new(reg_base as *mut VirtIOHeader).unwrap(); + let transport = unsafe { MmioTransport::new(header) }.ok()?; + let dev_type = as_dev_type(transport.device_type())?; + Some((dev_type, transport)) +} + +/// Try to probe a VirtIO PCI device from the given PCI address. +/// +/// If the device is recognized, returns the device type and a transport object +/// for later operations. Otherwise, returns [`None`]. +pub fn probe_pci_device( + root: &mut PciRoot, + bdf: DeviceFunction, + dev_info: &DeviceFunctionInfo, +) -> Option<(DeviceType, PciTransport)> { + use virtio_drivers::transport::pci::virtio_device_type; + + let dev_type = virtio_device_type(dev_info).and_then(as_dev_type)?; + let transport = PciTransport::new::(root, bdf).ok()?; + Some((dev_type, transport)) +} + +const fn as_dev_type(t: VirtIoDevType) -> Option { + use VirtIoDevType::*; + match t { + Block => Some(DeviceType::Block), + Network => Some(DeviceType::Net), + GPU => Some(DeviceType::Display), + _ => None, + } +} + +#[allow(dead_code)] +const fn as_dev_err(e: virtio_drivers::Error) -> DevError { + use virtio_drivers::Error::*; + match e { + QueueFull => DevError::BadState, + NotReady => DevError::Again, + WrongToken => DevError::BadState, + AlreadyUsed => DevError::AlreadyExists, + InvalidParam => DevError::InvalidParam, + DmaError => DevError::NoMemory, + IoError => DevError::Io, + Unsupported => DevError::Unsupported, + ConfigSpaceTooSmall => DevError::BadState, + ConfigSpaceMissing => DevError::BadState, + _ => DevError::BadState, + } +} diff --git a/crates/driver_virtio/src/net.rs b/crates/driver_virtio/src/net.rs new file mode 100644 index 000000000..e106f7e53 --- /dev/null +++ b/crates/driver_virtio/src/net.rs @@ -0,0 +1,193 @@ +use crate::as_dev_err; +use alloc::{sync::Arc, vec::Vec}; +use driver_common::{BaseDriverOps, DevError, DevResult, DeviceType}; +use driver_net::{EthernetAddress, NetBuf, NetBufBox, NetBufPool, NetBufPtr, NetDriverOps}; +use virtio_drivers::{device::net::VirtIONetRaw as InnerDev, transport::Transport, Hal}; + +extern crate alloc; + +const NET_BUF_LEN: usize = 1526; + +/// The VirtIO network device driver. +/// +/// `QS` is the VirtIO queue size. +pub struct VirtIoNetDev { + rx_buffers: [Option; QS], + tx_buffers: [Option; QS], + free_tx_bufs: Vec, + buf_pool: Arc, + inner: InnerDev, +} + +unsafe impl Send for VirtIoNetDev {} +unsafe impl Sync for VirtIoNetDev {} + +impl VirtIoNetDev { + /// Creates a new driver instance and initializes the device, or returns + /// an error if any step fails. + pub fn try_new(transport: T) -> DevResult { + // 0. Create a new driver instance. + const NONE_BUF: Option = None; + let inner = InnerDev::new(transport).map_err(as_dev_err)?; + let rx_buffers = [NONE_BUF; QS]; + let tx_buffers = [NONE_BUF; QS]; + let buf_pool = NetBufPool::new(2 * QS, NET_BUF_LEN)?; + let free_tx_bufs = Vec::with_capacity(QS); + + let mut dev = Self { + rx_buffers, + inner, + tx_buffers, + free_tx_bufs, + buf_pool, + }; + + // 1. Fill all rx buffers. + for (i, rx_buf_place) in dev.rx_buffers.iter_mut().enumerate() { + let mut rx_buf = dev.buf_pool.alloc_boxed().ok_or(DevError::NoMemory)?; + // Safe because the buffer lives as long as the queue. + let token = unsafe { + dev.inner + .receive_begin(rx_buf.raw_buf_mut()) + .map_err(as_dev_err)? + }; + assert_eq!(token, i as u16); + *rx_buf_place = Some(rx_buf); + } + + // 2. Allocate all tx buffers. + for _ in 0..QS { + let mut tx_buf = dev.buf_pool.alloc_boxed().ok_or(DevError::NoMemory)?; + // Fill header + let hdr_len = dev + .inner + .fill_buffer_header(tx_buf.raw_buf_mut()) + .or(Err(DevError::InvalidParam))?; + tx_buf.set_header_len(hdr_len); + dev.free_tx_bufs.push(tx_buf); + } + + // 3. Return the driver instance. + Ok(dev) + } +} + +impl const BaseDriverOps for VirtIoNetDev { + fn device_name(&self) -> &str { + "virtio-net" + } + + fn device_type(&self) -> DeviceType { + DeviceType::Net + } +} + +impl NetDriverOps for VirtIoNetDev { + #[inline] + fn mac_address(&self) -> EthernetAddress { + EthernetAddress(self.inner.mac_address()) + } + + #[inline] + fn can_transmit(&self) -> bool { + !self.free_tx_bufs.is_empty() && self.inner.can_transmit() + } + + #[inline] + fn can_receive(&self) -> bool { + self.inner.can_receive() + } + + #[inline] + fn rx_queue_size(&self) -> usize { + QS + } + + #[inline] + fn tx_queue_size(&self) -> usize { + QS + } + + fn recycle_rx_buffer(&mut self, rx_buf: NetBufPtr) -> DevResult { + let mut rx_buf = unsafe { NetBuf::from_buf_ptr(rx_buf) }; + // Safe because we take the ownership of `rx_buf` back to `rx_buffers`, + // it lives as long as the queue. + let new_token = unsafe { + self.inner + .receive_begin(rx_buf.raw_buf_mut()) + .map_err(as_dev_err)? + }; + // `rx_buffers[new_token]` is expected to be `None` since it was taken + // away at `Self::receive()` and has not been added back. + if self.rx_buffers[new_token as usize].is_some() { + return Err(DevError::BadState); + } + self.rx_buffers[new_token as usize] = Some(rx_buf); + Ok(()) + } + + fn recycle_tx_buffers(&mut self) -> DevResult { + while let Some(token) = self.inner.poll_transmit() { + let tx_buf = self.tx_buffers[token as usize] + .take() + .ok_or(DevError::BadState)?; + unsafe { + self.inner + .transmit_complete(token, tx_buf.packet_with_header()) + .map_err(as_dev_err)?; + } + // Recycle the buffer. + self.free_tx_bufs.push(tx_buf); + } + Ok(()) + } + + fn transmit(&mut self, tx_buf: NetBufPtr) -> DevResult { + // 0. prepare tx buffer. + let tx_buf = unsafe { NetBuf::from_buf_ptr(tx_buf) }; + // 1. transmit packet. + let token = unsafe { + self.inner + .transmit_begin(tx_buf.packet_with_header()) + .map_err(as_dev_err)? + }; + self.tx_buffers[token as usize] = Some(tx_buf); + Ok(()) + } + + fn receive(&mut self) -> DevResult { + if let Some(token) = self.inner.poll_receive() { + let mut rx_buf = self.rx_buffers[token as usize] + .take() + .ok_or(DevError::BadState)?; + // Safe because the buffer lives as long as the queue. + let (hdr_len, pkt_len) = unsafe { + self.inner + .receive_complete(token, rx_buf.raw_buf_mut()) + .map_err(as_dev_err)? + }; + rx_buf.set_header_len(hdr_len); + rx_buf.set_packet_len(pkt_len); + + Ok(rx_buf.into_buf_ptr()) + } else { + Err(DevError::Again) + } + } + + fn alloc_tx_buffer(&mut self, size: usize) -> DevResult { + // 0. Allocate a buffer from the queue. + let mut net_buf = self.free_tx_bufs.pop().ok_or(DevError::NoMemory)?; + let pkt_len = size; + + // 1. Check if the buffer is large enough. + let hdr_len = net_buf.header_len(); + if hdr_len + pkt_len > net_buf.capacity() { + return Err(DevError::InvalidParam); + } + net_buf.set_packet_len(pkt_len); + + // 2. Return the buffer. + Ok(net_buf.into_buf_ptr()) + } +} diff --git a/crates/dw_apb_uart/Cargo.toml b/crates/dw_apb_uart/Cargo.toml new file mode 100644 index 000000000..503e44baa --- /dev/null +++ b/crates/dw_apb_uart/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dw_apb_uart" +authors = ["Luoyuan Xiao "] +version = "0.1.0" +edition = "2021" +license = "GPL-2.0" +repository = "https://github.com/elliott10/arceos/tree/main/crates/dw_apb_uart" +description = "Uart snps,dw-apb-uart driver in Rust for BST A1000b FADA board." + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tock-registers = "0.8" diff --git a/crates/dw_apb_uart/src/lib.rs b/crates/dw_apb_uart/src/lib.rs new file mode 100644 index 000000000..069f07961 --- /dev/null +++ b/crates/dw_apb_uart/src/lib.rs @@ -0,0 +1,118 @@ +//! Definitions for snps,dw-apb-uart serial driver. +//! Uart snps,dw-apb-uart driver in Rust for BST A1000b FADA board. +#![no_std] + +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::{ReadOnly, ReadWrite}, +}; + +register_structs! { + DW8250Regs { + /// Get or Put Register. + (0x00 => rbr: ReadWrite), + (0x04 => ier: ReadWrite), + (0x08 => fcr: ReadWrite), + (0x0c => lcr: ReadWrite), + (0x10 => mcr: ReadWrite), + (0x14 => lsr: ReadOnly), + (0x18 => msr: ReadOnly), + (0x1c => scr: ReadWrite), + (0x20 => lpdll: ReadWrite), + (0x24 => _reserved0), + /// Uart Status Register. + (0x7c => usr: ReadOnly), + (0x80 => _reserved1), + (0xc0 => dlf: ReadWrite), + (0xc4 => @END), + } +} + +/// dw-apb-uart serial driver: DW8250 +pub struct DW8250 { + base_vaddr: usize, +} + +impl DW8250 { + /// New a DW8250 + pub const fn new(base_vaddr: usize) -> Self { + Self { base_vaddr } + } + + const fn regs(&self) -> &DW8250Regs { + unsafe { &*(self.base_vaddr as *const _) } + } + + /// DW8250 initialize + pub fn init(&mut self) { + const UART_SRC_CLK: u32 = 25000000; + const BST_UART_DLF_LEN: u32 = 6; + const BAUDRATE: u32 = 115200; + //const BAUDRATE: u32 = 38400; + + let get_baud_divider = |baudrate| (UART_SRC_CLK << (BST_UART_DLF_LEN - 4)) / baudrate; + let divider = get_baud_divider(BAUDRATE); + + // Waiting to be no USR_BUSY. + while self.regs().usr.get() & 0b1 != 0 {} + + // bst_serial_hw_init_clk_rst + + /* Disable interrupts and Enable FIFOs */ + self.regs().ier.set(0); + self.regs().fcr.set(1); + + /* Disable flow ctrl */ + self.regs().mcr.set(0); + + /* Clear MCR_RTS */ + self.regs().mcr.set(self.regs().mcr.get() | (1 << 1)); + + /* Enable access DLL & DLH. Set LCR_DLAB */ + self.regs().lcr.set(self.regs().lcr.get() | (1 << 7)); + + /* Set baud rate. Set DLL, DLH, DLF */ + self.regs().rbr.set((divider >> BST_UART_DLF_LEN) & 0xff); + self.regs() + .ier + .set((divider >> (BST_UART_DLF_LEN + 8)) & 0xff); + self.regs().dlf.set(divider & ((1 << BST_UART_DLF_LEN) - 1)); + + /* Clear DLAB bit */ + self.regs().lcr.set(self.regs().lcr.get() & !(1 << 7)); + + /* Set data length to 8 bit, 1 stop bit, no parity. Set LCR_WLS1 | LCR_WLS0 */ + self.regs().lcr.set(self.regs().lcr.get() | 0b11); + } + + /// DW8250 serial output + pub fn putchar(&mut self, c: u8) { + // Check LSR_TEMT + // Wait for last character to go. + while self.regs().lsr.get() & (1 << 6) == 0 {} + self.regs().rbr.set(c as u32); + } + + /// DW8250 serial input + pub fn getchar(&mut self) -> Option { + // Check LSR_DR + // Wait for a character to arrive. + if self.regs().lsr.get() & 0b1 != 0 { + Some((self.regs().rbr.get() & 0xff) as u8) + } else { + None + } + } + + /// DW8250 serial interrupt enable or disable + pub fn set_ier(&mut self, enable: bool) { + if enable { + // Enable interrupts + self.regs().ier.set(1); + } else { + // Disable interrupts + self.regs().ier.set(0); + } + } +} diff --git a/crates/flatten_objects/Cargo.toml b/crates/flatten_objects/Cargo.toml new file mode 100644 index 000000000..aea72b5a7 --- /dev/null +++ b/crates/flatten_objects/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "flatten_objects" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "A container that stores numbered objects. Each object can be assigned with a unique ID." +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/flatten_objects" +documentation = "https://rcore-os.github.io/arceos/flatten_objects/index.html" + +[dependencies] +bitmaps = { version = "3.2", default-features = false } diff --git a/crates/flatten_objects/src/lib.rs b/crates/flatten_objects/src/lib.rs new file mode 100644 index 000000000..d5637f001 --- /dev/null +++ b/crates/flatten_objects/src/lib.rs @@ -0,0 +1,158 @@ +//! [`FlattenObjects`] is a container that stores numbered objects. +//! +//! Objects can be added to the [`FlattenObjects`], a unique ID will be assigned +//! to the object. The ID can be used to retrieve the object later. +//! +//! # Example +//! +//! ``` +//! use flatten_objects::FlattenObjects; +//! +//! let mut objects = FlattenObjects::::new(); +//! +//! // Add `23` 10 times and assign them IDs from 0 to 9. +//! for i in 0..=9 { +//! objects.add_at(i, 23).unwrap(); +//! assert!(objects.is_assigned(i)); +//! } +//! +//! // Remove the object with ID 6. +//! assert_eq!(objects.remove(6), Some(23)); +//! assert!(!objects.is_assigned(6)); +//! +//! // Add `42` (the ID 6 is available now). +//! let id = objects.add(42).unwrap(); +//! assert_eq!(id, 6); +//! assert!(objects.is_assigned(id)); +//! assert_eq!(objects.get(id), Some(&42)); +//! assert_eq!(objects.remove(id), Some(42)); +//! assert!(!objects.is_assigned(id)); +//! ``` + +#![no_std] +#![feature(const_maybe_uninit_zeroed)] +#![feature(maybe_uninit_uninit_array)] +#![feature(const_maybe_uninit_uninit_array)] + +use bitmaps::Bitmap; +use core::mem::MaybeUninit; + +/// A container that stores numbered objects. +/// +/// See the [crate-level documentation](crate) for more details. +/// +/// `CAP` is the maximum number of objects that can be held. It also equals the +/// maximum ID that can be assigned plus one. Currently, `CAP` must not be +/// greater than 1024. +pub struct FlattenObjects { + objects: [MaybeUninit; CAP], + id_bitmap: Bitmap<1024>, + count: usize, +} + +impl FlattenObjects { + /// Creates a new empty `FlattenObjects`. + pub const fn new() -> Self { + assert!(CAP <= 1024); + Self { + objects: MaybeUninit::uninit_array(), + // SAFETY: zero initialization is OK for `id_bitmap` (an array of integers). + id_bitmap: unsafe { MaybeUninit::zeroed().assume_init() }, + count: 0, + } + } + + /// Returns the maximum number of objects that can be held. + /// + /// It also equals the maximum ID that can be assigned plus one. + #[inline] + pub const fn capacity(&self) -> usize { + CAP + } + + /// Returns the number of objects that have been added. + #[inline] + pub const fn count(&self) -> usize { + self.count + } + + /// Returns `true` if the given `id` is already be assigned. + #[inline] + pub fn is_assigned(&self, id: usize) -> bool { + id < CAP && self.id_bitmap.get(id) + } + + /// Returns the reference of the element with the given `id` if it already + /// be assigned. Otherwise, returns `None`. + #[inline] + pub fn get(&self, id: usize) -> Option<&T> { + if self.is_assigned(id) { + // SAFETY: the object at `id` should be initialized by `add` or + // `add_at`. + unsafe { Some(self.objects[id].assume_init_ref()) } + } else { + None + } + } + + /// Returns the mutable reference of the element with the given `id` if it + /// exists. Otherwise, returns `None`. + #[inline] + pub fn get_mut(&mut self, id: usize) -> Option<&mut T> { + if self.is_assigned(id) { + // SAFETY: the object at `id` should be initialized by `add` or + // `add_at`. + unsafe { Some(self.objects[id].assume_init_mut()) } + } else { + None + } + } + + /// Add an object and assigns it a unique ID. + /// + /// Returns the ID if there is one available. Otherwise, returns `None`. + pub fn add(&mut self, value: T) -> Option { + let id = self.id_bitmap.first_false_index()?; + if id < CAP { + self.count += 1; + self.id_bitmap.set(id, true); + self.objects[id].write(value); + Some(id) + } else { + None + } + } + + /// Add an object and assigns it a specific ID. + /// + /// Returns the ID if it's not used by others. Otherwise, returns `None`. + pub fn add_at(&mut self, id: usize, value: T) -> Option { + if self.is_assigned(id) { + return None; + } + self.count += 1; + self.id_bitmap.set(id, true); + self.objects[id].write(value); + Some(id) + } + + /// Removes the object with the given ID. + /// + /// Returns the object if there is one assigned to the ID. Otherwise, + /// returns `None`. + /// + /// After this operation, the ID is freed and can be assigned for next + /// object again. + pub fn remove(&mut self, id: usize) -> Option { + if self.is_assigned(id) { + self.id_bitmap.set(id, false); + self.count -= 1; + // SAFETY: the object at `id` should be initialized by `add` or + // `add_at`, and can not be retrieved by `get` or `get_mut` unless + // it be added again. + unsafe { Some(self.objects[id].assume_init_read()) } + } else { + None + } + } +} diff --git a/crates/handler_table/Cargo.toml b/crates/handler_table/Cargo.toml new file mode 100644 index 000000000..e4ecacea9 --- /dev/null +++ b/crates/handler_table/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "handler_table" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "A lock-free table of event handlers" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/handler_table" +documentation = "https://rcore-os.github.io/arceos/handler_table/index.html" +keywords = ["arceos", "event", "lock-free"] +categories = ["no-std"] + +[dependencies] diff --git a/crates/handler_table/README.md b/crates/handler_table/README.md new file mode 100644 index 000000000..33878d1ad --- /dev/null +++ b/crates/handler_table/README.md @@ -0,0 +1,23 @@ +# handler_table + +[![Crates.io](https://img.shields.io/crates/v/handler_table)](https://crates.io/crates/handler_table) + +A lock-free table of event handlers. + +## Examples + +```rust +use handler_table::HandlerTable; + +static TABLE: HandlerTable<8> = HandlerTable::new(); + +TABLE.register_handler(0, || { + println!("Hello, event 0!"); +}); +TABLE.register_handler(1, || { + println!("Hello, event 1!"); +}); + +assert!(TABLE.handle(0)); // print "Hello, event 0!" +assert!(!TABLE.handle(2)); // unregistered +``` diff --git a/crates/handler_table/src/lib.rs b/crates/handler_table/src/lib.rs new file mode 100644 index 000000000..becd784ba --- /dev/null +++ b/crates/handler_table/src/lib.rs @@ -0,0 +1,49 @@ +#![no_std] +#![doc = include_str!("../README.md")] + +use core::sync::atomic::{AtomicUsize, Ordering}; + +/// The type of an event handler. +/// +/// Currently no arguments and return values are supported. +pub type Handler = fn(); + +/// A lock-free table of event handlers. +/// +/// It internally uses an array of `AtomicUsize` to store the handlers. +pub struct HandlerTable { + handlers: [AtomicUsize; N], +} + +impl HandlerTable { + /// Creates a new handler table with all entries empty. + #[allow(clippy::declare_interior_mutable_const)] + pub const fn new() -> Self { + const EMPTY: AtomicUsize = AtomicUsize::new(0); + Self { + handlers: [EMPTY; N], + } + } + + /// Registers a handler for the given index. + pub fn register_handler(&self, idx: usize, handler: Handler) -> bool { + self.handlers[idx] + .compare_exchange(0, handler as usize, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + } + + /// Handles the event with the given index. + /// + /// Returns `true` if the event is handled, `false` if no handler is + /// registered for the given index. + pub fn handle(&self, idx: usize) -> bool { + let handler = self.handlers[idx].load(Ordering::Acquire); + if handler != 0 { + let handler: Handler = unsafe { core::mem::transmute(handler) }; + handler(); + true + } else { + false + } + } +} diff --git a/crates/kernel_guard/Cargo.toml b/crates/kernel_guard/Cargo.toml new file mode 100644 index 000000000..841470199 --- /dev/null +++ b/crates/kernel_guard/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "kernel_guard" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "RAII wrappers to create a critical section with local IRQs or preemption disabled" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/kernel_guard" +documentation = "https://rcore-os.github.io/arceos/kernel_guard/index.html" +keywords = ["arceos", "synchronization", "preemption"] +categories = ["os", "no-std"] + +[features] +preempt = [] +default = [] + +[dependencies] +cfg-if = "1.0" +crate_interface = "0.1" diff --git a/crates/kernel_guard/README.md b/crates/kernel_guard/README.md new file mode 100644 index 000000000..4f137e084 --- /dev/null +++ b/crates/kernel_guard/README.md @@ -0,0 +1,56 @@ +# kernel_guard + +[![Crates.io](https://img.shields.io/crates/v/kernel_guard)](https://crates.io/crates/kernel_guard) + +RAII wrappers to create a critical section with local IRQs or preemption +disabled, used to implement spin locks in kernel. + +The critical section is created after the guard struct is created, and is +ended when the guard falls out of scope. + +The crate user must implement the `KernelGuardIf` trait using +[`crate_interface::impl_interface`](https://crates.io/crates/crate_interface) to provide the low-level implementantion +of how to enable/disable kernel preemption, if the feature `preempt` is +enabled. + +Available guards: + +- `NoOp`: Does nothing around the critical section. +- `IrqSave`: Disables/enables local IRQs around the critical section. +- `NoPreempt`: Disables/enables kernel preemption around the critical +section. +- `NoPreemptIrqSave`: Disables/enables both kernel preemption and local +IRQs around the critical section. + +## Crate features + +- `preempt`: Use in the preemptive system. If this feature is enabled, you +need to implement the `KernelGuardIf` trait in other crates. Otherwise +the preemption enable/disable operations will be no-ops. This feature is +disabled by default. + +## Examples + +```rust +use kernel_guard::{KernelGuardIf, NoPreempt}; + +struct KernelGuardIfImpl; + +#[crate_interface::impl_interface] +impl KernelGuardIf for KernelGuardIfImpl { + fn enable_preempt() { + // Your implementation here + } + fn disable_preempt() { + // Your implementation here + } +} + +let guard = NoPreempt::new(); +/* The critical section starts here + +Do something that requires preemption to be disabled + +The critical section ends here */ +drop(guard); +``` diff --git a/crates/kernel_guard/src/arch/aarch64.rs b/crates/kernel_guard/src/arch/aarch64.rs new file mode 100644 index 000000000..554dd09b8 --- /dev/null +++ b/crates/kernel_guard/src/arch/aarch64.rs @@ -0,0 +1,14 @@ +use core::arch::asm; + +#[inline] +pub fn local_irq_save_and_disable() -> usize { + let flags: usize; + // save `DAIF` flags, mask `I` bit (disable IRQs) + unsafe { asm!("mrs {}, daif; msr daifset, #2", out(reg) flags) }; + flags +} + +#[inline] +pub fn local_irq_restore(flags: usize) { + unsafe { asm!("msr daif, {}", in(reg) flags) }; +} diff --git a/crates/kernel_guard/src/arch/mod.rs b/crates/kernel_guard/src/arch/mod.rs new file mode 100644 index 000000000..3a0514972 --- /dev/null +++ b/crates/kernel_guard/src/arch/mod.rs @@ -0,0 +1,14 @@ +#![cfg_attr(not(target_os = "none"), allow(dead_code))] + +cfg_if::cfg_if! { + if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] { + mod x86; + pub use self::x86::*; + } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { + mod riscv; + pub use self::riscv::*; + } else if #[cfg(target_arch = "aarch64")] { + mod aarch64; + pub use self::aarch64::*; + } +} diff --git a/crates/kernel_guard/src/arch/riscv.rs b/crates/kernel_guard/src/arch/riscv.rs new file mode 100644 index 000000000..5e73c6384 --- /dev/null +++ b/crates/kernel_guard/src/arch/riscv.rs @@ -0,0 +1,18 @@ +use core::arch::asm; + +/// Bit 1: Supervisor Interrupt Enable +const SIE_BIT: usize = 1 << 1; + +#[inline] +pub fn local_irq_save_and_disable() -> usize { + let flags: usize; + // clear the `SIE` bit, and return the old CSR + unsafe { asm!("csrrc {}, sstatus, {}", out(reg) flags, const SIE_BIT) }; + flags & SIE_BIT +} + +#[inline] +pub fn local_irq_restore(flags: usize) { + // restore the `SIE` bit + unsafe { asm!("csrrs x0, sstatus, {}", in(reg) flags) }; +} diff --git a/crates/kernel_guard/src/arch/x86.rs b/crates/kernel_guard/src/arch/x86.rs new file mode 100644 index 000000000..0520a02bc --- /dev/null +++ b/crates/kernel_guard/src/arch/x86.rs @@ -0,0 +1,20 @@ +use core::arch::asm; + +/// Interrupt Enable Flag (IF) +const IF_BIT: usize = 1 << 9; + +#[inline] +pub fn local_irq_save_and_disable() -> usize { + let flags: usize; + unsafe { asm!("pushf; pop {}; cli", out(reg) flags) }; + flags & IF_BIT +} + +#[inline] +pub fn local_irq_restore(flags: usize) { + if flags != 0 { + unsafe { asm!("sti") }; + } else { + unsafe { asm!("cli") }; + } +} diff --git a/crates/kernel_guard/src/lib.rs b/crates/kernel_guard/src/lib.rs new file mode 100644 index 000000000..97e6a8b06 --- /dev/null +++ b/crates/kernel_guard/src/lib.rs @@ -0,0 +1,239 @@ +//! RAII wrappers to create a critical section with local IRQs or preemption +//! disabled, used to implement spin locks in kernel. +//! +//! The critical section is created after the guard struct is created, and is +//! ended when the guard falls out of scope. +//! +//! The crate user must implement the [`KernelGuardIf`] trait using +//! [`crate_interface::impl_interface`] to provide the low-level implementantion +//! of how to enable/disable kernel preemption, if the feature `preempt` is +//! enabled. +//! +//! Available guards: +//! +//! - [`NoOp`]: Does nothing around the critical section. +//! - [`IrqSave`]: Disables/enables local IRQs around the critical section. +//! - [`NoPreempt`]: Disables/enables kernel preemption around the critical +//! section. +//! - [`NoPreemptIrqSave`]: Disables/enables both kernel preemption and local +//! IRQs around the critical section. +//! +//! # Crate features +//! +//! - `preempt`: Use in the preemptive system. If this feature is enabled, you +//! need to implement the [`KernelGuardIf`] trait in other crates. Otherwise +//! the preemption enable/disable operations will be no-ops. This feature is +//! disabled by default. +//! +//! # Examples +//! +//! ``` +//! use kernel_guard::{KernelGuardIf, NoPreempt}; +//! +//! struct KernelGuardIfImpl; +//! +//! #[crate_interface::impl_interface] +//! impl KernelGuardIf for KernelGuardIfImpl { +//! fn enable_preempt() { +//! // Your implementation here +//! } +//! fn disable_preempt() { +//! // Your implementation here +//! } +//! } +//! +//! let guard = NoPreempt::new(); +//! /* The critical section starts here +//! +//! Do something that requires preemption to be disabled +//! +//! The critical section ends here */ +//! drop(guard); +//! ``` + +#![no_std] +#![feature(asm_const)] + +mod arch; + +/// Low-level interfaces that must be implemented by the crate user. +#[crate_interface::def_interface] +pub trait KernelGuardIf { + /// How to enable kernel preemption. + fn enable_preempt(); + + /// How to disable kernel preemption. + fn disable_preempt(); +} + +/// A base trait that all guards implement. +pub trait BaseGuard { + /// The saved state when entering the critical section. + type State: Clone + Copy; + + /// Something that must be done before entering the critical section. + fn acquire() -> Self::State; + + /// Something that must be done after leaving the critical section. + fn release(state: Self::State); +} + +/// A no-op guard that does nothing around the critical section. +pub struct NoOp; + +cfg_if::cfg_if! { + // For user-mode std apps, we use the alias of [`NoOp`] for all guards, + // since we can not disable IRQs or preemption in user-mode. + if #[cfg(any(target_os = "none", doc))] { + /// A guard that disables/enables local IRQs around the critical section. + pub struct IrqSave(usize); + + /// A guard that disables/enables kernel preemption around the critical + /// section. + pub struct NoPreempt; + + /// A guard that disables/enables both kernel preemption and local IRQs + /// around the critical section. + /// + /// When entering the critical section, it disables kernel preemption + /// first, followed by local IRQs. When leaving the critical section, it + /// re-enables local IRQs first, followed by kernel preemption. + pub struct NoPreemptIrqSave(usize); + } else { + /// Alias of [`NoOp`]. + pub type IrqSave = NoOp; + + /// Alias of [`NoOp`]. + pub type NoPreempt = NoOp; + + /// Alias of [`NoOp`]. + pub type NoPreemptIrqSave = NoOp; + } +} + +impl BaseGuard for NoOp { + type State = (); + fn acquire() -> Self::State {} + fn release(_state: Self::State) {} +} + +impl NoOp { + /// Creates a new [`NoOp`] guard. + pub const fn new() -> Self { + Self + } +} + +impl Drop for NoOp { + fn drop(&mut self) {} +} + +#[cfg(any(target_os = "none", doc))] +mod imp { + use super::*; + + impl BaseGuard for IrqSave { + type State = usize; + + #[inline] + fn acquire() -> Self::State { + super::arch::local_irq_save_and_disable() + } + + #[inline] + fn release(state: Self::State) { + // restore IRQ states + super::arch::local_irq_restore(state); + } + } + + impl BaseGuard for NoPreempt { + type State = (); + fn acquire() -> Self::State { + // disable preempt + #[cfg(feature = "preempt")] + crate_interface::call_interface!(KernelGuardIf::disable_preempt); + } + fn release(_state: Self::State) { + // enable preempt + #[cfg(feature = "preempt")] + crate_interface::call_interface!(KernelGuardIf::enable_preempt); + } + } + + impl BaseGuard for NoPreemptIrqSave { + type State = usize; + fn acquire() -> Self::State { + // disable preempt + #[cfg(feature = "preempt")] + crate_interface::call_interface!(KernelGuardIf::disable_preempt); + // disable IRQs and save IRQ states + super::arch::local_irq_save_and_disable() + } + fn release(state: Self::State) { + // restore IRQ states + super::arch::local_irq_restore(state); + // enable preempt + #[cfg(feature = "preempt")] + crate_interface::call_interface!(KernelGuardIf::enable_preempt); + } + } + + impl IrqSave { + /// Creates a new [`IrqSave`] guard. + pub fn new() -> Self { + Self(Self::acquire()) + } + } + + impl Drop for IrqSave { + fn drop(&mut self) { + Self::release(self.0) + } + } + + impl Default for IrqSave { + fn default() -> Self { + Self::new() + } + } + + impl NoPreempt { + /// Creates a new [`NoPreempt`] guard. + pub fn new() -> Self { + Self::acquire(); + Self + } + } + + impl Drop for NoPreempt { + fn drop(&mut self) { + Self::release(()) + } + } + + impl Default for NoPreempt { + fn default() -> Self { + Self::new() + } + } + + impl NoPreemptIrqSave { + /// Creates a new [`NoPreemptIrqSave`] guard. + pub fn new() -> Self { + Self(Self::acquire()) + } + } + + impl Drop for NoPreemptIrqSave { + fn drop(&mut self) { + Self::release(self.0) + } + } + + impl Default for NoPreemptIrqSave { + fn default() -> Self { + Self::new() + } + } +} diff --git a/crates/lazy_init/Cargo.toml b/crates/lazy_init/Cargo.toml new file mode 100644 index 000000000..78d6a09bd --- /dev/null +++ b/crates/lazy_init/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "lazy_init" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "A wrapper for lazy initialized values without concurrency safety but more efficient" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/lazy_init" +documentation = "https://rcore-os.github.io/arceos/lazy_init/index.html" + +[dependencies] diff --git a/crates/lazy_init/src/lib.rs b/crates/lazy_init/src/lib.rs new file mode 100644 index 000000000..b2f2e0d29 --- /dev/null +++ b/crates/lazy_init/src/lib.rs @@ -0,0 +1,157 @@ +//! A wrapper for lazy initialized values. +//! +//! Unlike [`lazy_static`][1], this crate does not provide concurrency safety. +//! The value **MUST** be used after only **ONE** initialization. However, it +//! can be more efficient, as there is no need to check whether other threads +//! are also performing initialization at the same time. +//! +//! # Examples +//! +//! ``` +//! use lazy_init::LazyInit; +//! +//! static VALUE: LazyInit = LazyInit::new(); +//! assert!(!VALUE.is_init()); +//! // println!("{}", *VALUE); // panic: use uninitialized value +//! assert_eq!(VALUE.try_get(), None); +//! +//! VALUE.init_by(233); +//! // VALUE.init_by(666); // panic: already initialized +//! assert!(VALUE.is_init()); +//! assert_eq!(*VALUE, 233); +//! assert_eq!(VALUE.try_get(), Some(&233)); +//! ``` +//! +//! [1]: https://docs.rs/lazy_static/latest/lazy_static/ + +#![no_std] + +use core::cell::UnsafeCell; +use core::fmt; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; +use core::sync::atomic::{AtomicBool, Ordering}; + +/// A wrapper of a lazy initialized value. +/// +/// It implements [`Deref`] and [`DerefMut`]. The caller must use the dereference +/// operation after initialization, otherwise it will panic. +pub struct LazyInit { + inited: AtomicBool, + data: UnsafeCell>, +} + +unsafe impl Sync for LazyInit {} +unsafe impl Send for LazyInit {} + +impl LazyInit { + /// Creates a new uninitialized value. + pub const fn new() -> Self { + Self { + inited: AtomicBool::new(false), + data: UnsafeCell::new(MaybeUninit::uninit()), + } + } + + /// Initializes the value. + /// + /// # Panics + /// + /// Panics if the value is already initialized. + pub fn init_by(&self, data: T) { + assert!(!self.is_init()); + unsafe { (*self.data.get()).as_mut_ptr().write(data) }; + self.inited.store(true, Ordering::Release); + } + + /// Checks whether the value is initialized. + pub fn is_init(&self) -> bool { + self.inited.load(Ordering::Acquire) + } + + /// Gets a reference to the value. + /// + /// Returns [`None`] if the value is not initialized. + pub fn try_get(&self) -> Option<&T> { + if self.is_init() { + unsafe { Some(&*(*self.data.get()).as_ptr()) } + } else { + None + } + } + + fn check_init(&self) { + if !self.is_init() { + panic!( + "Use uninitialized value: {:?}", + core::any::type_name::() + ) + } + } + + #[inline] + fn get(&self) -> &T { + self.check_init(); + unsafe { self.get_unchecked() } + } + + #[inline] + fn get_mut(&mut self) -> &mut T { + self.check_init(); + unsafe { self.get_mut_unchecked() } + } + + /// Gets the reference to the value without checking if it is initialized. + /// + /// # Safety + /// + /// Must be called after initialization. + #[inline] + pub unsafe fn get_unchecked(&self) -> &T { + &*(*self.data.get()).as_ptr() + } + + /// Get a mutable reference to the value without checking if it is initialized. + /// + /// # Safety + /// + /// Must be called after initialization. + #[inline] + pub unsafe fn get_mut_unchecked(&mut self) -> &mut T { + &mut *(*self.data.get()).as_mut_ptr() + } +} + +impl fmt::Debug for LazyInit { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.try_get() { + Some(s) => write!(f, "LazyInit {{ data: ") + .and_then(|()| s.fmt(f)) + .and_then(|()| write!(f, "}}")), + None => write!(f, "LazyInit {{ }}"), + } + } +} + +impl Deref for LazyInit { + type Target = T; + #[inline] + fn deref(&self) -> &T { + self.get() + } +} + +impl DerefMut for LazyInit { + #[inline] + fn deref_mut(&mut self) -> &mut T { + self.get_mut() + } +} + +impl Drop for LazyInit { + fn drop(&mut self) { + if self.is_init() { + unsafe { core::ptr::drop_in_place((*self.data.get()).as_mut_ptr()) }; + } + } +} diff --git a/crates/linked_list/Cargo.toml b/crates/linked_list/Cargo.toml new file mode 100644 index 000000000..73fe1a04c --- /dev/null +++ b/crates/linked_list/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "linked_list" +version = "0.1.0" +edition = "2021" +authors = ["Wedson Almeida Filho "] +description = "Linked lists that supports arbitrary removal in constant time" +license = "GPL-2.0-or-later" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/linked_list" +documentation = "https://rcore-os.github.io/arceos/linked_list/index.html" + +[dependencies] diff --git a/crates/linked_list/src/lib.rs b/crates/linked_list/src/lib.rs new file mode 100644 index 000000000..9fb264ea0 --- /dev/null +++ b/crates/linked_list/src/lib.rs @@ -0,0 +1,14 @@ +//! Linked lists that supports arbitrary removal in constant time. +//! +//! It is based on the linked list implementation in [Rust-for-Linux][1]. +//! +//! [1]: https://github.com/Rust-for-Linux/linux/blob/rust/rust/kernel/linked_list.rs + +#![no_std] + +mod linked_list; + +pub mod unsafe_list; + +pub use self::linked_list::{AdapterWrapped, List, Wrapper}; +pub use unsafe_list::{Adapter, Cursor, Links}; diff --git a/crates/linked_list/src/linked_list.rs b/crates/linked_list/src/linked_list.rs new file mode 100644 index 000000000..0c188038a --- /dev/null +++ b/crates/linked_list/src/linked_list.rs @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Linked lists. +//! +//! Based on linux/rust/kernel/linked_list.rs, but use +//! [`unsafe_list::List`] as the inner implementation. +//! +//! TODO: This module is a work in progress. + +extern crate alloc; + +use alloc::{boxed::Box, sync::Arc}; +use core::ptr::NonNull; + +use crate::unsafe_list::{self, Adapter, Cursor, Links}; + +// TODO: Use the one from `kernel::file_operations::PointerWrapper` instead. +/// Wraps an object to be inserted in a linked list. +pub trait Wrapper { + /// Converts the wrapped object into a pointer that represents it. + fn into_pointer(self) -> NonNull; + + /// Converts the object back from the pointer representation. + /// + /// # Safety + /// + /// The passed pointer must come from a previous call to [`Wrapper::into_pointer()`]. + unsafe fn from_pointer(ptr: NonNull) -> Self; + + /// Returns a reference to the wrapped object. + fn as_ref(&self) -> &T; +} + +impl Wrapper for Box { + #[inline] + fn into_pointer(self) -> NonNull { + NonNull::new(Box::into_raw(self)).unwrap() + } + + #[inline] + unsafe fn from_pointer(ptr: NonNull) -> Self { + unsafe { Box::from_raw(ptr.as_ptr()) } + } + + #[inline] + fn as_ref(&self) -> &T { + AsRef::as_ref(self) + } +} + +impl Wrapper for Arc { + #[inline] + fn into_pointer(self) -> NonNull { + NonNull::new(Arc::into_raw(self) as _).unwrap() + } + + #[inline] + unsafe fn from_pointer(ptr: NonNull) -> Self { + // SAFETY: The safety requirements of `from_pointer` satisfy the ones from `Arc::from_raw`. + unsafe { Arc::from_raw(ptr.as_ptr() as _) } + } + + #[inline] + fn as_ref(&self) -> &T { + AsRef::as_ref(self) + } +} + +impl Wrapper for &T { + #[inline] + fn into_pointer(self) -> NonNull { + NonNull::from(self) + } + + #[inline] + unsafe fn from_pointer(ptr: NonNull) -> Self { + unsafe { &*ptr.as_ptr() } + } + + #[inline] + fn as_ref(&self) -> &T { + self + } +} + +/// A descriptor of wrapped list elements. +pub trait AdapterWrapped: Adapter { + /// Specifies which wrapper (e.g., `Box` and `Arc`) wraps the list entries. + type Wrapped: Wrapper; +} + +impl AdapterWrapped for Box +where + Box: Adapter, +{ + type Wrapped = Box< as Adapter>::EntryType>; +} + +unsafe impl Adapter for Box { + type EntryType = T::EntryType; + + #[inline] + fn to_links(data: &Self::EntryType) -> &Links { + ::to_links(data) + } +} + +impl AdapterWrapped for Arc +where + Arc: Adapter, +{ + type Wrapped = Arc< as Adapter>::EntryType>; +} + +unsafe impl Adapter for Arc { + type EntryType = T::EntryType; + + #[inline] + fn to_links(data: &Self::EntryType) -> &Links { + ::to_links(data) + } +} + +/// A linked list. +/// +/// Elements in the list are wrapped and ownership is transferred to the list while the element is +/// in the list. +pub struct List { + list: unsafe_list::List, +} + +impl List { + /// Constructs a new empty linked list. + pub const fn new() -> Self { + Self { + list: unsafe_list::List::new(), + } + } + + /// Returns whether the list is empty. + #[inline] + pub const fn is_empty(&self) -> bool { + self.list.is_empty() + } + + /// Adds the given object to the end (back) of the list. + /// + /// It is dropped if it's already on this (or another) list; this can happen for + /// reference-counted objects, so dropping means decrementing the reference count. + pub fn push_back(&mut self, data: G::Wrapped) { + let ptr = data.into_pointer(); + + // SAFETY: We took ownership of the entry, so it is safe to insert it. + unsafe { self.list.push_back(ptr.as_ref()) } + } + + /// Inserts the given object after `existing`. + /// + /// It is dropped if it's already on this (or another) list; this can happen for + /// reference-counted objects, so dropping means decrementing the reference count. + /// + /// # Safety + /// + /// Callers must ensure that `existing` points to a valid entry that is on the list. + pub unsafe fn insert_after(&mut self, existing: NonNull, data: G::Wrapped) { + let ptr = data.into_pointer(); + unsafe { self.list.insert_after(existing, ptr.as_ref()) } + } + + /// Removes the given entry. + /// + /// # Safety + /// + /// Callers must ensure that `data` is either on this list. It being on another + /// list leads to memory unsafety. + pub unsafe fn remove(&mut self, data: &G::Wrapped) -> Option { + let entry_ref = Wrapper::as_ref(data); + unsafe { self.list.remove(entry_ref) }; + Some(unsafe { G::Wrapped::from_pointer(NonNull::from(entry_ref)) }) + } + + /// Removes the element currently at the front of the list and returns it. + /// + /// Returns `None` if the list is empty. + pub fn pop_front(&mut self) -> Option { + let entry_ref = unsafe { self.list.front()?.as_ref() }; + unsafe { self.list.remove(entry_ref) }; + Some(unsafe { G::Wrapped::from_pointer(NonNull::from(entry_ref)) }) + } + + /// Returns the first element of the list, if one exists. + #[inline] + pub fn front(&self) -> Option<&G::EntryType> { + self.list.front().map(|ptr| unsafe { ptr.as_ref() }) + } + + /// Returns the last element of the list, if one exists. + #[inline] + pub fn back(&self) -> Option<&G::EntryType> { + self.list.back().map(|ptr| unsafe { ptr.as_ref() }) + } + + /// Returns a cursor starting on the first (front) element of the list. + #[inline] + pub fn cursor_front(&self) -> Cursor<'_, G> { + self.list.cursor_front() + } +} + +impl Default for List { + fn default() -> Self { + Self::new() + } +} + +impl Drop for List { + fn drop(&mut self) { + while self.pop_front().is_some() {} + } +} diff --git a/crates/linked_list/src/unsafe_list.rs b/crates/linked_list/src/unsafe_list.rs new file mode 100644 index 000000000..68fe46cb4 --- /dev/null +++ b/crates/linked_list/src/unsafe_list.rs @@ -0,0 +1,683 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Intrusive circular doubly-linked lists. +//! +//! Copied from linux/rust/kernel/unsafe_list.rs. +//! +//! We don't use the C version for two main reasons: +//! - Next/prev pointers do not support `?Sized` types, so wouldn't be able to have a list of, for +//! example, `dyn Trait`. +//! - It would require the list head to be pinned (in addition to the list entries). + +use core::{cell::UnsafeCell, iter, marker::PhantomPinned, mem::MaybeUninit, ptr::NonNull}; + +/// An intrusive circular doubly-linked list. +/// +/// Membership of elements of the list must be tracked by the owner of the list. +/// +/// While elements of the list must remain pinned while in the list, the list itself does not +/// require pinning. In other words, users are allowed to move instances of [`List`]. +/// +/// # Invariants +/// +/// The links of an entry are wrapped in [`UnsafeCell`] and they are acessible when the list itself +/// is. For example, when a thread has a mutable reference to a list, it may also safely get +/// mutable references to the links of the elements in the list. +/// +/// The links of an entry are also wrapped in [`MaybeUninit`] and they are initialised when they +/// are present in a list. Otherwise they are uninitialised. +/// +/// # Examples +/// +/// ``` +/// # use linked_list::unsafe_list::{Adapter, Links, List}; +/// +/// struct Example { +/// v: usize, +/// links: Links, +/// } +/// +/// // SAFETY: This adapter is the only one that uses `Example::links`. +/// unsafe impl Adapter for Example { +/// type EntryType = Self; +/// fn to_links(obj: &Self) -> &Links { +/// &obj.links +/// } +/// } +/// +/// let a = Example { +/// v: 0, +/// links: Links::new(), +/// }; +/// let b = Example { +/// v: 1, +/// links: Links::new(), +/// }; +/// +/// let mut list = List::::new(); +/// assert!(list.is_empty()); +/// +/// // SAFETY: `a` was declared above, it's not in any lists yet, is never moved, and outlives the +/// // list. +/// unsafe { list.push_back(&a) }; +/// +/// // SAFETY: `b` was declared above, it's not in any lists yet, is never moved, and outlives the +/// // list. +/// unsafe { list.push_back(&b) }; +/// +/// assert!(core::ptr::eq(&a, list.front().unwrap().as_ptr())); +/// assert!(core::ptr::eq(&b, list.back().unwrap().as_ptr())); +/// +/// for (i, e) in list.iter().enumerate() { +/// assert_eq!(i, e.v); +/// } +/// +/// for e in &list { +/// println!("{}", e.v); +/// } +/// +/// // SAFETY: `b` was added to the list above and wasn't removed yet. +/// unsafe { list.remove(&b) }; +/// +/// assert!(core::ptr::eq(&a, list.front().unwrap().as_ptr())); +/// assert!(core::ptr::eq(&a, list.back().unwrap().as_ptr())); +/// ``` +pub struct List { + first: Option>, +} + +// SAFETY: The list is itself can be safely sent to other threads but we restrict it to being `Send` +// only when its entries are also `Send`. +unsafe impl Send for List where A::EntryType: Send {} + +// SAFETY: The list is itself usable from other threads via references but we restrict it to being +// `Sync` only when its entries are also `Sync`. +unsafe impl Sync for List where A::EntryType: Sync {} + +impl List { + /// Constructs a new empty list. + pub const fn new() -> Self { + Self { first: None } + } + + /// Determines if the list is empty. + pub const fn is_empty(&self) -> bool { + self.first.is_none() + } + + /// Inserts the only entry to a list. + /// + /// This must only be called when the list is empty. + pub fn insert_only_entry(&mut self, obj: &A::EntryType) { + let obj_ptr = NonNull::from(obj); + + // SAFETY: We have mutable access to the list, so we also have access to the entry + // we're about to insert (and it's not in any other lists per the function safety + // requirements). + let obj_inner = unsafe { &mut *A::to_links(obj).0.get() }; + + // INVARIANTS: All fields of the links of the newly-inserted object are initialised + // below. + obj_inner.write(LinksInner { + next: obj_ptr, + prev: obj_ptr, + _pin: PhantomPinned, + }); + self.first = Some(obj_ptr); + } + + /// Adds the given object to the end of the list. + /// + /// # Safety + /// + /// Callers must ensure that: + /// - The object is not currently in any lists. + /// - The object remains alive until it is removed from the list. + /// - The object is not moved until it is removed from the list. + pub unsafe fn push_back(&mut self, obj: &A::EntryType) { + if let Some(first) = self.first { + // SAFETY: The previous entry to the first one is necessarily present in the list (it + // may in fact be the first entry itself as this is a circular list). The safety + // requirements of this function regarding `obj` satisfy those of `insert_after`. + unsafe { self.insert_after(self.inner_ref(first).prev, obj) }; + } else { + self.insert_only_entry(obj); + } + } + + /// Adds the given object to the beginning of the list. + /// + /// # Safety + /// + /// Callers must ensure that: + /// - The object is not currently in any lists. + /// - The object remains alive until it is removed from the list. + /// - The object is not moved until it is removed from the list. + pub unsafe fn push_front(&mut self, obj: &A::EntryType) { + if let Some(first) = self.first { + // SAFETY: The safety requirements of this function regarding `obj` satisfy those of + // `insert_before`. Additionally, `first` is in the list. + unsafe { self.insert_before(first, obj) }; + } else { + self.insert_only_entry(obj); + } + } + + /// Removes the given object from the list. + /// + /// # Safety + /// + /// The object must be in the list. In other words, the object must have previously been + /// inserted into this list and not removed yet. + pub unsafe fn remove(&mut self, entry: &A::EntryType) { + // SAFETY: Per the function safety requirements, `entry` is in the list. + let inner = unsafe { self.inner_ref(NonNull::from(entry)) }; + let next = inner.next; + let prev = inner.prev; + + // SAFETY: We have mutable access to the list, so we also have access to the entry we're + // about to remove (which we know is in the list per the function safety requirements). + let inner = unsafe { &mut *A::to_links(entry).0.get() }; + + // SAFETY: Since the entry was in the list, it was initialised. + unsafe { inner.assume_init_drop() }; + + if core::ptr::eq(next.as_ptr(), entry) { + // Removing the only element. + self.first = None; + } else { + // SAFETY: `prev` is in the list because it is pointed at by the entry being removed. + unsafe { self.inner(prev).next = next }; + // SAFETY: `next` is in the list because it is pointed at by the entry being removed. + unsafe { self.inner(next).prev = prev }; + + if core::ptr::eq(self.first.unwrap().as_ptr(), entry) { + // Update the pointer to the first element as we're removing it. + self.first = Some(next); + } + } + } + + /// Adds the given object after another object already in the list. + /// + /// # Safety + /// + /// Callers must ensure that: + /// - The existing object is currently in the list. + /// - The new object is not currently in any lists. + /// - The new object remains alive until it is removed from the list. + /// - The new object is not moved until it is removed from the list. + pub unsafe fn insert_after(&mut self, existing: NonNull, new: &A::EntryType) { + // SAFETY: We have mutable access to the list, so we also have access to the entry we're + // about to insert (and it's not in any other lists per the function safety requirements). + let new_inner = unsafe { &mut *A::to_links(new).0.get() }; + + // SAFETY: Per the function safety requirements, `existing` is in the list. + let existing_inner = unsafe { self.inner(existing) }; + let next = existing_inner.next; + + // INVARIANTS: All fields of the links of the newly-inserted object are initialised below. + new_inner.write(LinksInner { + next, + prev: existing, + _pin: PhantomPinned, + }); + + existing_inner.next = NonNull::from(new); + + // SAFETY: `next` is in the list because it's pointed at by the existing entry. + unsafe { self.inner(next).prev = NonNull::from(new) }; + } + + /// Adds the given object before another object already in the list. + /// + /// # Safety + /// + /// Callers must ensure that: + /// - The existing object is currently in the list. + /// - The new object is not currently in any lists. + /// - The new object remains alive until it is removed from the list. + /// - The new object is not moved until it is removed from the list. + pub unsafe fn insert_before(&mut self, existing: NonNull, new: &A::EntryType) { + // SAFETY: The safety requirements of this function satisfy those of `insert_after`. + unsafe { self.insert_after(self.inner_ref(existing).prev, new) }; + + if self.first.unwrap() == existing { + // Update the pointer to the first element as we're inserting before it. + self.first = Some(NonNull::from(new)); + } + } + + /// Returns the first element of the list, if one exists. + pub fn front(&self) -> Option> { + self.first + } + + /// Returns the last element of the list, if one exists. + pub fn back(&self) -> Option> { + // SAFETY: Having a pointer to it guarantees that the object is in the list. + self.first.map(|f| unsafe { self.inner_ref(f).prev }) + } + + /// Returns an iterator for the list starting at the first entry. + pub fn iter(&self) -> Iterator<'_, A> { + Iterator::new(self.cursor_front()) + } + + /// Returns an iterator for the list starting at the last entry. + pub fn iter_back(&self) -> impl iter::DoubleEndedIterator { + Iterator::new(self.cursor_back()) + } + + /// Returns a cursor starting on the first (front) element of the list. + pub fn cursor_front(&self) -> Cursor<'_, A> { + // SAFETY: `front` is in the list (or is `None`) because we've read it from the list head + // and the list cannot have changed because we hold a shared reference to it. + unsafe { Cursor::new(self, self.front()) } + } + + /// Returns a cursor starting on the last (back) element of the list. + pub fn cursor_back(&self) -> Cursor<'_, A> { + // SAFETY: `back` is in the list (or is `None`) because we've read it from the list head + // and the list cannot have changed because we hold a shared reference to it. + unsafe { Cursor::new(self, self.back()) } + } + + /// Returns a mutable reference to the links of a given object. + /// + /// # Safety + /// + /// Callers must ensure that the element is in the list. + unsafe fn inner(&mut self, ptr: NonNull) -> &mut LinksInner { + // SAFETY: The safety requirements guarantee that we the links are initialised because + // that's part of the type invariants. Additionally, the type invariants also guarantee + // that having a mutable reference to the list guarantees that the links are mutably + // accessible as well. + unsafe { (*A::to_links(ptr.as_ref()).0.get()).assume_init_mut() } + } + + /// Returns a shared reference to the links of a given object. + /// + /// # Safety + /// + /// Callers must ensure that the element is in the list. + unsafe fn inner_ref(&self, ptr: NonNull) -> &LinksInner { + // SAFETY: The safety requirements guarantee that we the links are initialised because + // that's part of the type invariants. Additionally, the type invariants also guarantee + // that having a shared reference to the list guarantees that the links are accessible in + // shared mode as well. + unsafe { (*A::to_links(ptr.as_ref()).0.get()).assume_init_ref() } + } +} + +impl<'a, A: Adapter + ?Sized> iter::IntoIterator for &'a List { + type Item = &'a A::EntryType; + type IntoIter = Iterator<'a, A>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// An iterator for the linked list. +pub struct Iterator<'a, A: Adapter + ?Sized> { + cursor: Cursor<'a, A>, +} + +impl<'a, A: Adapter + ?Sized> Iterator<'a, A> { + fn new(cursor: Cursor<'a, A>) -> Self { + Self { cursor } + } +} + +impl<'a, A: Adapter + ?Sized> iter::Iterator for Iterator<'a, A> { + type Item = &'a A::EntryType; + + fn next(&mut self) -> Option { + let ret = self.cursor.current()?; + self.cursor.move_next(); + Some(ret) + } +} + +impl iter::DoubleEndedIterator for Iterator<'_, A> { + fn next_back(&mut self) -> Option { + let ret = self.cursor.current()?; + self.cursor.move_prev(); + Some(ret) + } +} + +/// A linked-list adapter. +/// +/// It is a separate type (as opposed to implemented by the type of the elements of the list) +/// so that a given type can be inserted into multiple lists at the same time; in such cases, each +/// list needs its own adapter that returns a different pointer to links. +/// +/// It may, however, be implemented by the type itself to be inserted into lists, which makes it +/// more readable. +/// +/// # Safety +/// +/// Implementers must ensure that the links returned by [`Adapter::to_links`] are unique to the +/// adapter. That is, different adapters must return different links for a given object. +/// +/// The reason for this requirement is to avoid confusion that may lead to UB. In particular, if +/// two adapters were to use the same links, a user may have two lists (one for each adapter) and +/// try to insert the same object into both at the same time; although this clearly violates the +/// list safety requirements (e.g., those in [`List::push_back`]), for users to notice it, they'd +/// have to dig into the details of the two adapters. +/// +/// By imposing the requirement on the adapter, we make it easier for users to check compliance +/// with the requirements when using the list. +/// +/// # Examples +/// +/// ``` +/// # use linked_list::unsafe_list::{Adapter, Links, List}; +/// +/// struct Example { +/// a: u32, +/// b: u32, +/// links1: Links, +/// links2: Links, +/// } +/// +/// // SAFETY: This adapter is the only one that uses `Example::links1`. +/// unsafe impl Adapter for Example { +/// type EntryType = Self; +/// fn to_links(obj: &Self) -> &Links { +/// &obj.links1 +/// } +/// } +/// +/// struct ExampleAdapter; +/// +/// // SAFETY: This adapter is the only one that uses `Example::links2`. +/// unsafe impl Adapter for ExampleAdapter { +/// type EntryType = Example; +/// fn to_links(obj: &Example) -> &Links { +/// &obj.links2 +/// } +/// } +/// +/// static LIST1: List = List::new(); +/// static LIST2: List = List::new(); +/// ``` +pub unsafe trait Adapter { + /// The type of the enties in the list. + type EntryType: ?Sized; + + /// Retrieves the linked list links for the given object. + fn to_links(obj: &Self::EntryType) -> &Links; +} + +struct LinksInner { + next: NonNull, + prev: NonNull, + _pin: PhantomPinned, +} + +/// Links of a linked list. +/// +/// List entries need one of these per concurrent list. +pub struct Links(UnsafeCell>>); + +// SAFETY: `Links` can be safely sent to other threads but we restrict it to being `Send` only when +// the list entries it points to are also `Send`. +unsafe impl Send for Links {} + +// SAFETY: `Links` is usable from other threads via references but we restrict it to being `Sync` +// only when the list entries it points to are also `Sync`. +unsafe impl Sync for Links {} + +impl Links { + /// Constructs a new instance of the linked-list links. + pub const fn new() -> Self { + Self(UnsafeCell::new(MaybeUninit::uninit())) + } +} + +pub(crate) struct CommonCursor { + pub(crate) cur: Option>, +} + +impl CommonCursor { + pub(crate) fn new(cur: Option>) -> Self { + Self { cur } + } + + /// Moves the cursor to the next entry of the list. + /// + /// # Safety + /// + /// Callers must ensure that the cursor is either [`None`] or points to an entry that is in + /// `list`. + pub(crate) unsafe fn move_next(&mut self, list: &List) { + match self.cur.take() { + None => self.cur = list.first, + Some(cur) => { + if let Some(head) = list.first { + // SAFETY: Per the function safety requirements, `cur` is in the list. + let links = unsafe { list.inner_ref(cur) }; + if links.next != head { + self.cur = Some(links.next); + } + } + } + } + } + + /// Moves the cursor to the previous entry of the list. + /// + /// # Safety + /// + /// Callers must ensure that the cursor is either [`None`] or points to an entry that is in + /// `list`. + pub(crate) unsafe fn move_prev(&mut self, list: &List) { + match list.first { + None => self.cur = None, + Some(head) => { + let next = match self.cur.take() { + None => head, + Some(cur) => { + if cur == head { + return; + } + cur + } + }; + // SAFETY: `next` is either `head` or `cur`. The former is in the list because it's + // its head; the latter is in the list per the function safety requirements. + self.cur = Some(unsafe { list.inner_ref(next) }.prev); + } + } + } +} + +/// A list cursor that allows traversing a linked list and inspecting elements. +pub struct Cursor<'a, A: Adapter + ?Sized> { + cursor: CommonCursor, + list: &'a List, +} + +impl<'a, A: Adapter + ?Sized> Cursor<'a, A> { + /// Creates a new cursor. + /// + /// # Safety + /// + /// Callers must ensure that `cur` is either [`None`] or points to an entry in `list`. + pub(crate) unsafe fn new(list: &'a List, cur: Option>) -> Self { + Self { + list, + cursor: CommonCursor::new(cur), + } + } + + /// Returns the element the cursor is currently positioned on. + pub fn current(&self) -> Option<&'a A::EntryType> { + let cur = self.cursor.cur?; + // SAFETY: `cursor` starts off in the list and only changes within the list. Additionally, + // the list cannot change because we hold a shared reference to it, so the cursor is always + // within the list. + Some(unsafe { cur.as_ref() }) + } + + /// Moves the cursor to the next element. + pub fn move_next(&mut self) { + // SAFETY: `cursor` starts off in the list and only changes within the list. Additionally, + // the list cannot change because we hold a shared reference to it, so the cursor is always + // within the list. + unsafe { self.cursor.move_next(self.list) }; + } + + /// Moves the cursor to the previous element. + pub fn move_prev(&mut self) { + // SAFETY: `cursor` starts off in the list and only changes within the list. Additionally, + // the list cannot change because we hold a shared reference to it, so the cursor is always + // within the list. + unsafe { self.cursor.move_prev(self.list) }; + } +} + +#[cfg(test)] +mod tests { + extern crate alloc; + use alloc::{boxed::Box, vec::Vec}; + use core::ptr::NonNull; + + struct Example { + links: super::Links, + } + + // SAFETY: This is the only adapter that uses `Example::links`. + unsafe impl super::Adapter for Example { + type EntryType = Self; + fn to_links(obj: &Self) -> &super::Links { + &obj.links + } + } + + fn build_vector(size: usize) -> Vec> { + let mut v = Vec::new(); + v.reserve(size); + for _ in 0..size { + v.push(Box::new(Example { + links: super::Links::new(), + })); + } + v + } + + #[track_caller] + fn assert_list_contents(v: &[Box], list: &super::List) { + let n = v.len(); + + // Assert that the list is ok going forward. + let mut count = 0; + for (i, e) in list.iter().enumerate() { + assert!(core::ptr::eq(e, &*v[i])); + count += 1; + } + assert_eq!(count, n); + + // Assert that the list is ok going backwards. + let mut count = 0; + for (i, e) in list.iter_back().rev().enumerate() { + assert!(core::ptr::eq(e, &*v[n - 1 - i])); + count += 1; + } + assert_eq!(count, n); + } + + #[track_caller] + fn test_each_element( + min_len: usize, + max_len: usize, + test: impl Fn(&mut Vec>, &mut super::List, usize, Box), + ) { + for n in min_len..=max_len { + for i in 0..n { + let extra = Box::new(Example { + links: super::Links::new(), + }); + let mut v = build_vector(n); + let mut list = super::List::::new(); + + // Build list. + for j in 0..n { + // SAFETY: The entry was allocated above, it's not in any lists yet, is never + // moved, and outlives the list. + unsafe { list.push_back(&v[j]) }; + } + + // Call the test case. + test(&mut v, &mut list, i, extra); + + // Check that the list is ok. + assert_list_contents(&v, &list); + } + } + } + + #[test] + fn test_push_back() { + const MAX: usize = 10; + let v = build_vector(MAX); + let mut list = super::List::::new(); + + for n in 1..=MAX { + // SAFETY: The entry was allocated above, it's not in any lists yet, is never moved, + // and outlives the list. + unsafe { list.push_back(&v[n - 1]) }; + assert_list_contents(&v[..n], &list); + } + } + + #[test] + fn test_push_front() { + const MAX: usize = 10; + let v = build_vector(MAX); + let mut list = super::List::::new(); + + for n in 1..=MAX { + // SAFETY: The entry was allocated above, it's not in any lists yet, is never moved, + // and outlives the list. + unsafe { list.push_front(&v[MAX - n]) }; + assert_list_contents(&v[MAX - n..], &list); + } + } + + #[test] + fn test_one_removal() { + test_each_element(1, 10, |v, list, i, _| { + // Remove the i-th element. + // SAFETY: The i-th element was added to the list above, and wasn't removed yet. + unsafe { list.remove(&v[i]) }; + v.remove(i); + }); + } + + #[test] + fn test_one_insert_after() { + test_each_element(1, 10, |v, list, i, extra| { + // Insert after the i-th element. + // SAFETY: The i-th element was added to the list above, and wasn't removed yet. + // Additionally, the new element isn't in any list yet, isn't moved, and outlives + // the list. + unsafe { list.insert_after(NonNull::from(&*v[i]), &*extra) }; + v.insert(i + 1, extra); + }); + } + + #[test] + fn test_one_insert_before() { + test_each_element(1, 10, |v, list, i, extra| { + // Insert before the i-th element. + // SAFETY: The i-th element was added to the list above, and wasn't removed yet. + // Additionally, the new element isn't in any list yet, isn't moved, and outlives + // the list. + unsafe { list.insert_before(NonNull::from(&*v[i]), &*extra) }; + v.insert(i, extra); + }); + } +} diff --git a/crates/memory_addr/Cargo.toml b/crates/memory_addr/Cargo.toml new file mode 100644 index 000000000..1b900359d --- /dev/null +++ b/crates/memory_addr/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "memory_addr" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Wrappers and helper functions for physical and virtual addresses" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/memory_addr" +documentation = "https://rcore-os.github.io/arceos/memory_addr/index.html" +keywords = ["arceos", "address", "virtual-memory"] +categories = ["os", "memory-management", "no-std"] + +[dependencies] diff --git a/crates/memory_addr/README.md b/crates/memory_addr/README.md new file mode 100644 index 000000000..2b1768b5f --- /dev/null +++ b/crates/memory_addr/README.md @@ -0,0 +1,20 @@ +# memory_addr + +[![Crates.io](https://img.shields.io/crates/v/memory_addr)](https://crates.io/crates/memory_addr) + +Wrappers and helper functions for physical and virtual memory addresses. + +## Examples + +```rust +use memory_addr::{PhysAddr, VirtAddr}; + +let phys_addr = PhysAddr::from(0x12345678); +let virt_addr = VirtAddr::from(0x87654321); + +assert_eq!(phys_addr.align_down(0x1000usize), PhysAddr::from(0x12345000)); +assert_eq!(phys_addr.align_offset_4k(), 0x678); +assert_eq!(virt_addr.align_up_4k(), VirtAddr::from(0x87655000)); +assert!(!virt_addr.is_aligned_4k()); +assert!(VirtAddr::from(0xabcedf0).is_aligned(16usize)); +``` diff --git a/crates/memory_addr/src/lib.rs b/crates/memory_addr/src/lib.rs new file mode 100644 index 000000000..4a73609da --- /dev/null +++ b/crates/memory_addr/src/lib.rs @@ -0,0 +1,384 @@ +#![no_std] +#![feature(const_mut_refs)] +#![doc = include_str!("../README.md")] + +use core::fmt; +use core::ops::{Add, AddAssign, Sub, SubAssign}; + +/// The size of a 4K page (4096 bytes). +pub const PAGE_SIZE_4K: usize = 0x1000; + +/// Align address downwards. +/// +/// Returns the greatest `x` with alignment `align` so that `x <= addr`. +/// +/// The alignment must be a power of two. +#[inline] +pub const fn align_down(addr: usize, align: usize) -> usize { + addr & !(align - 1) +} + +/// Align address upwards. +/// +/// Returns the smallest `x` with alignment `align` so that `x >= addr`. +/// +/// The alignment must be a power of two. +#[inline] +pub const fn align_up(addr: usize, align: usize) -> usize { + (addr + align - 1) & !(align - 1) +} + +/// Returns the offset of the address within the alignment. +/// +/// Equivalent to `addr % align`, but the alignment must be a power of two. +#[inline] +pub const fn align_offset(addr: usize, align: usize) -> usize { + addr & (align - 1) +} + +/// Checks whether the address has the demanded alignment. +/// +/// Equivalent to `addr % align == 0`, but the alignment must be a power of two. +#[inline] +pub const fn is_aligned(addr: usize, align: usize) -> bool { + align_offset(addr, align) == 0 +} + +/// Align address downwards to 4096 (bytes). +#[inline] +pub const fn align_down_4k(addr: usize) -> usize { + align_down(addr, PAGE_SIZE_4K) +} + +/// Align address upwards to 4096 (bytes). +#[inline] +pub const fn align_up_4k(addr: usize) -> usize { + align_up(addr, PAGE_SIZE_4K) +} + +/// Returns the offset of the address within a 4K-sized page. +#[inline] +pub const fn align_offset_4k(addr: usize) -> usize { + align_offset(addr, PAGE_SIZE_4K) +} + +/// Checks whether the address is 4K-aligned. +#[inline] +pub const fn is_aligned_4k(addr: usize) -> bool { + is_aligned(addr, PAGE_SIZE_4K) +} + +/// A physical memory address. +/// +/// It's a wrapper type around an `usize`. +#[repr(transparent)] +#[derive(Copy, Clone, Default, Ord, PartialOrd, Eq, PartialEq)] +pub struct PhysAddr(usize); + +/// A virtual memory address. +/// +/// It's a wrapper type around an `usize`. +#[repr(transparent)] +#[derive(Copy, Clone, Default, Ord, PartialOrd, Eq, PartialEq)] +pub struct VirtAddr(usize); + +impl PhysAddr { + /// Converts an `usize` to a physical address. + #[inline] + pub const fn from(addr: usize) -> Self { + Self(addr) + } + + /// Converts the address to an `usize`. + #[inline] + pub const fn as_usize(self) -> usize { + self.0 + } + + /// Aligns the address downwards to the given alignment. + /// + /// See the [`align_down`] function for more information. + #[inline] + pub fn align_down(self, align: U) -> Self + where + U: Into, + { + Self(align_down(self.0, align.into())) + } + + /// Aligns the address upwards to the given alignment. + /// + /// See the [`align_up`] function for more information. + #[inline] + pub fn align_up(self, align: U) -> Self + where + U: Into, + { + Self(align_up(self.0, align.into())) + } + + /// Returns the offset of the address within the given alignment. + /// + /// See the [`align_offset`] function for more information. + #[inline] + pub fn align_offset(self, align: U) -> usize + where + U: Into, + { + align_offset(self.0, align.into()) + } + + /// Checks whether the address has the demanded alignment. + /// + /// See the [`is_aligned`] function for more information. + #[inline] + pub fn is_aligned(self, align: U) -> bool + where + U: Into, + { + is_aligned(self.0, align.into()) + } + + /// Aligns the address downwards to 4096 (bytes). + #[inline] + pub fn align_down_4k(self) -> Self { + self.align_down(PAGE_SIZE_4K) + } + + /// Aligns the address upwards to 4096 (bytes). + #[inline] + pub fn align_up_4k(self) -> Self { + self.align_up(PAGE_SIZE_4K) + } + + /// Returns the offset of the address within a 4K-sized page. + #[inline] + pub fn align_offset_4k(self) -> usize { + self.align_offset(PAGE_SIZE_4K) + } + + /// Checks whether the address is 4K-aligned. + #[inline] + pub fn is_aligned_4k(self) -> bool { + self.is_aligned(PAGE_SIZE_4K) + } +} + +impl VirtAddr { + /// Converts an `usize` to a virtual address. + #[inline] + pub const fn from(addr: usize) -> Self { + Self(addr) + } + + /// Converts the address to an `usize`. + #[inline] + pub const fn as_usize(self) -> usize { + self.0 + } + + /// Converts the virtual address to a raw pointer. + #[inline] + pub const fn as_ptr(self) -> *const u8 { + self.0 as *const u8 + } + + /// Converts the virtual address to a mutable raw pointer. + #[inline] + pub const fn as_mut_ptr(self) -> *mut u8 { + self.0 as *mut u8 + } + + /// Aligns the address downwards to the given alignment. + /// + /// See the [`align_down`] function for more information. + #[inline] + pub fn align_down(self, align: U) -> Self + where + U: Into, + { + Self(align_down(self.0, align.into())) + } + + /// Aligns the address upwards to the given alignment. + /// + /// See the [`align_up`] function for more information. + #[inline] + pub fn align_up(self, align: U) -> Self + where + U: Into, + { + Self(align_up(self.0, align.into())) + } + + /// Returns the offset of the address within the given alignment. + /// + /// See the [`align_offset`] function for more information. + #[inline] + pub fn align_offset(self, align: U) -> usize + where + U: Into, + { + align_offset(self.0, align.into()) + } + + /// Checks whether the address has the demanded alignment. + /// + /// See the [`is_aligned`] function for more information. + #[inline] + pub fn is_aligned(self, align: U) -> bool + where + U: Into, + { + is_aligned(self.0, align.into()) + } + + /// Aligns the address downwards to 4096 (bytes). + #[inline] + pub fn align_down_4k(self) -> Self { + self.align_down(PAGE_SIZE_4K) + } + + /// Aligns the address upwards to 4096 (bytes). + #[inline] + pub fn align_up_4k(self) -> Self { + self.align_up(PAGE_SIZE_4K) + } + + /// Returns the offset of the address within a 4K-sized page. + #[inline] + pub fn align_offset_4k(self) -> usize { + self.align_offset(PAGE_SIZE_4K) + } + + /// Checks whether the address is 4K-aligned. + #[inline] + pub fn is_aligned_4k(self) -> bool { + self.is_aligned(PAGE_SIZE_4K) + } +} + +impl From for PhysAddr { + #[inline] + fn from(addr: usize) -> Self { + Self(addr) + } +} + +impl From for VirtAddr { + #[inline] + fn from(addr: usize) -> Self { + Self(addr) + } +} + +impl From for usize { + #[inline] + fn from(addr: PhysAddr) -> usize { + addr.0 + } +} + +impl From for usize { + #[inline] + fn from(addr: VirtAddr) -> usize { + addr.0 + } +} + +impl Add for PhysAddr { + type Output = Self; + #[inline] + fn add(self, rhs: usize) -> Self { + Self(self.0 + rhs) + } +} + +impl AddAssign for PhysAddr { + #[inline] + fn add_assign(&mut self, rhs: usize) { + *self = *self + rhs; + } +} + +impl Sub for PhysAddr { + type Output = Self; + #[inline] + fn sub(self, rhs: usize) -> Self { + Self(self.0 - rhs) + } +} + +impl SubAssign for PhysAddr { + #[inline] + fn sub_assign(&mut self, rhs: usize) { + *self = *self - rhs; + } +} + +impl Add for VirtAddr { + type Output = Self; + #[inline] + fn add(self, rhs: usize) -> Self { + Self(self.0 + rhs) + } +} + +impl AddAssign for VirtAddr { + #[inline] + fn add_assign(&mut self, rhs: usize) { + *self = *self + rhs; + } +} + +impl Sub for VirtAddr { + type Output = Self; + #[inline] + fn sub(self, rhs: usize) -> Self { + Self(self.0 - rhs) + } +} + +impl SubAssign for VirtAddr { + #[inline] + fn sub_assign(&mut self, rhs: usize) { + *self = *self - rhs; + } +} + +impl fmt::Debug for PhysAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("PA:{:#x}", self.0)) + } +} + +impl fmt::Debug for VirtAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("VA:{:#x}", self.0)) + } +} + +impl fmt::LowerHex for PhysAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("PA:{:#x}", self.0)) + } +} + +impl fmt::UpperHex for PhysAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("PA:{:#X}", self.0)) + } +} + +impl fmt::LowerHex for VirtAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("VA:{:#x}", self.0)) + } +} + +impl fmt::UpperHex for VirtAddr { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("VA:{:#X}", self.0)) + } +} diff --git a/crates/page_table/Cargo.toml b/crates/page_table/Cargo.toml new file mode 100644 index 000000000..4178458b1 --- /dev/null +++ b/crates/page_table/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "page_table" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Generic page table structures for various hardware architectures" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/page_table" +documentation = "https://rcore-os.github.io/arceos/page_table/index.html" + +[dependencies] +log = "0.4" +memory_addr = { path = "../memory_addr" } +page_table_entry = { path = "../page_table_entry" } diff --git a/crates/page_table/src/arch/aarch64.rs b/crates/page_table/src/arch/aarch64.rs new file mode 100644 index 000000000..e652544ba --- /dev/null +++ b/crates/page_table/src/arch/aarch64.rs @@ -0,0 +1,22 @@ +//! AArch64 specific page table structures. + +use crate::{PageTable64, PagingMetaData}; +use page_table_entry::aarch64::A64PTE; + +/// Metadata of AArch64 page tables. +#[derive(Copy, Clone)] +pub struct A64PagingMetaData; + +impl const PagingMetaData for A64PagingMetaData { + const LEVELS: usize = 4; + const PA_MAX_BITS: usize = 48; + const VA_MAX_BITS: usize = 48; + + fn vaddr_is_valid(vaddr: usize) -> bool { + let top_bits = vaddr >> Self::VA_MAX_BITS; + top_bits == 0 || top_bits == 0xffff + } +} + +/// AArch64 VMSAv8-64 translation table. +pub type A64PageTable = PageTable64; diff --git a/crates/page_table/src/arch/mod.rs b/crates/page_table/src/arch/mod.rs new file mode 100644 index 000000000..13fff18b9 --- /dev/null +++ b/crates/page_table/src/arch/mod.rs @@ -0,0 +1,8 @@ +#[cfg(any(target_arch = "x86_64", doc))] +pub mod x86_64; + +#[cfg(any(target_arch = "riscv32", target_arch = "riscv64", doc))] +pub mod riscv; + +#[cfg(any(target_arch = "aarch64", doc))] +pub mod aarch64; diff --git a/crates/page_table/src/arch/riscv.rs b/crates/page_table/src/arch/riscv.rs new file mode 100644 index 000000000..6c15678b9 --- /dev/null +++ b/crates/page_table/src/arch/riscv.rs @@ -0,0 +1,30 @@ +//! RISC-V specific page table structures. + +use crate::{PageTable64, PagingMetaData}; +use page_table_entry::riscv::Rv64PTE; + +/// Metadata of RISC-V Sv39 page tables. +#[derive(Clone, Copy)] +pub struct Sv39MetaData; + +/// Metadata of RISC-V Sv48 page tables. +#[derive(Clone, Copy)] +pub struct Sv48MetaData; + +impl const PagingMetaData for Sv39MetaData { + const LEVELS: usize = 3; + const PA_MAX_BITS: usize = 56; + const VA_MAX_BITS: usize = 39; +} + +impl const PagingMetaData for Sv48MetaData { + const LEVELS: usize = 4; + const PA_MAX_BITS: usize = 56; + const VA_MAX_BITS: usize = 48; +} + +/// Sv39: Page-Based 39-bit (3 levels) Virtual-Memory System. +pub type Sv39PageTable = PageTable64; + +/// Sv48: Page-Based 48-bit (4 levels) Virtual-Memory System. +pub type Sv48PageTable = PageTable64; diff --git a/crates/page_table/src/arch/x86_64.rs b/crates/page_table/src/arch/x86_64.rs new file mode 100644 index 000000000..627f8f4ab --- /dev/null +++ b/crates/page_table/src/arch/x86_64.rs @@ -0,0 +1,16 @@ +//! x86 specific page table structures. + +use crate::{PageTable64, PagingMetaData}; +use page_table_entry::x86_64::X64PTE; + +/// metadata of x86_64 page tables. +pub struct X64PagingMetaData; + +impl const PagingMetaData for X64PagingMetaData { + const LEVELS: usize = 4; + const PA_MAX_BITS: usize = 52; + const VA_MAX_BITS: usize = 48; +} + +/// x86_64 page table. +pub type X64PageTable = PageTable64; diff --git a/crates/page_table/src/bits64.rs b/crates/page_table/src/bits64.rs new file mode 100644 index 000000000..dee0d9d4f --- /dev/null +++ b/crates/page_table/src/bits64.rs @@ -0,0 +1,386 @@ +extern crate alloc; + +use alloc::{vec, vec::Vec}; +use core::marker::PhantomData; + +use memory_addr::{PhysAddr, VirtAddr, PAGE_SIZE_4K}; + +use crate::{GenericPTE, PagingIf, PagingMetaData}; +use crate::{MappingFlags, PageSize, PagingError, PagingResult}; + +const ENTRY_COUNT: usize = 512; + +const fn p4_index(vaddr: VirtAddr) -> usize { + (vaddr.as_usize() >> (12 + 27)) & (ENTRY_COUNT - 1) +} + +const fn p3_index(vaddr: VirtAddr) -> usize { + (vaddr.as_usize() >> (12 + 18)) & (ENTRY_COUNT - 1) +} + +const fn p2_index(vaddr: VirtAddr) -> usize { + (vaddr.as_usize() >> (12 + 9)) & (ENTRY_COUNT - 1) +} + +const fn p1_index(vaddr: VirtAddr) -> usize { + (vaddr.as_usize() >> 12) & (ENTRY_COUNT - 1) +} + +/// A generic page table struct for 64-bit platform. +/// +/// It also tracks all intermediate level tables. They will be deallocated +/// When the [`PageTable64`] itself is dropped. +pub struct PageTable64 { + root_paddr: PhysAddr, + intrm_tables: Vec, + _phantom: PhantomData<(M, PTE, IF)>, +} + +impl PageTable64 { + /// Creates a new page table instance or returns the error. + /// + /// It will allocate a new page for the root page table. + pub fn try_new() -> PagingResult { + let root_paddr = Self::alloc_table()?; + Ok(Self { + root_paddr, + intrm_tables: vec![root_paddr], + _phantom: PhantomData, + }) + } + + /// Returns the physical address of the root page table. + pub const fn root_paddr(&self) -> PhysAddr { + self.root_paddr + } + + /// Maps a virtual page to a physical frame with the given `page_size` + /// and mapping `flags`. + /// + /// The virtual page starts with `vaddr`, amd the physical frame starts with + /// `target`. If the addresses is not aligned to the page size, they will be + /// aligned down automatically. + /// + /// Returns [`Err(PagingError::AlreadyMapped)`](PagingError::AlreadyMapped) + /// if the mapping is already present. + pub fn map( + &mut self, + vaddr: VirtAddr, + target: PhysAddr, + page_size: PageSize, + flags: MappingFlags, + ) -> PagingResult { + let entry = self.get_entry_mut_or_create(vaddr, page_size)?; + if !entry.is_unused() { + return Err(PagingError::AlreadyMapped); + } + *entry = GenericPTE::new_page(target.align_down(page_size), flags, page_size.is_huge()); + Ok(()) + } + + /// Unmaps the mapping starts with `vaddr`. + /// + /// Returns [`Err(PagingError::NotMapped)`](PagingError::NotMapped) if the + /// mapping is not present. + pub fn unmap(&mut self, vaddr: VirtAddr) -> PagingResult<(PhysAddr, PageSize)> { + let (entry, size) = self.get_entry_mut(vaddr)?; + if entry.is_unused() { + return Err(PagingError::NotMapped); + } + let paddr = entry.paddr(); + entry.clear(); + Ok((paddr, size)) + } + + /// Query the result of the mapping starts with `vaddr`. + /// + /// Returns the physical address of the target frame, mapping flags, and + /// the page size. + /// + /// Returns [`Err(PagingError::NotMapped)`](PagingError::NotMapped) if the + /// mapping is not present. + pub fn query(&self, vaddr: VirtAddr) -> PagingResult<(PhysAddr, MappingFlags, PageSize)> { + let (entry, size) = self.get_entry_mut(vaddr)?; + if entry.is_unused() { + return Err(PagingError::NotMapped); + } + let off = vaddr.align_offset(size); + Ok((entry.paddr() + off, entry.flags(), size)) + } + + /// Updates the target or flags of the mapping starts with `vaddr`. If the + /// corresponding argument is `None`, it will not be updated. + /// + /// Returns the page size of the mapping. + /// + /// Returns [`Err(PagingError::NotMapped)`](PagingError::NotMapped) if the + /// mapping is not present. + pub fn update( + &mut self, + vaddr: VirtAddr, + paddr: Option, + flags: Option, + ) -> PagingResult { + let (entry, size) = self.get_entry_mut(vaddr)?; + if let Some(paddr) = paddr { + entry.set_paddr(paddr); + } + if let Some(flags) = flags { + entry.set_flags(flags, size.is_huge()); + } + Ok(size) + } + + /// Map a contiguous virtual memory region to a contiguous physical memory + /// region with the given mapping `flags`. + /// + /// The virtual and physical memory regions start with `vaddr` and `paddr` + /// respectively. The region size is `size`. The addresses and `size` must + /// be aligned to 4K, otherwise it will return [`Err(PagingError::NotAligned)`]. + /// + /// When `allow_huge` is true, it will try to map the region with huge pages + /// if possible. Otherwise, it will map the region with 4K pages. + /// + /// [`Err(PagingError::NotAligned)`]: PagingError::NotAligned + pub fn map_region( + &mut self, + vaddr: VirtAddr, + paddr: PhysAddr, + size: usize, + flags: MappingFlags, + allow_huge: bool, + ) -> PagingResult { + if !vaddr.is_aligned(PageSize::Size4K) + || !paddr.is_aligned(PageSize::Size4K) + || !memory_addr::is_aligned(size, PageSize::Size4K.into()) + { + return Err(PagingError::NotAligned); + } + trace!( + "map_region({:#x}): [{:#x}, {:#x}) -> [{:#x}, {:#x}) {:?}", + self.root_paddr(), + vaddr, + vaddr + size, + paddr, + paddr + size, + flags, + ); + let mut vaddr = vaddr; + let mut paddr = paddr; + let mut size = size; + while size > 0 { + let page_size = if allow_huge { + if vaddr.is_aligned(PageSize::Size1G) + && paddr.is_aligned(PageSize::Size1G) + && size >= PageSize::Size1G as usize + { + PageSize::Size1G + } else if vaddr.is_aligned(PageSize::Size2M) + && paddr.is_aligned(PageSize::Size2M) + && size >= PageSize::Size2M as usize + { + PageSize::Size2M + } else { + PageSize::Size4K + } + } else { + PageSize::Size4K + }; + self.map(vaddr, paddr, page_size, flags).inspect_err(|e| { + error!( + "failed to map page: {:#x?}({:?}) -> {:#x?}, {:?}", + vaddr, page_size, paddr, e + ) + })?; + vaddr += page_size as usize; + paddr += page_size as usize; + size -= page_size as usize; + } + Ok(()) + } + + /// Unmap a contiguous virtual memory region. + /// + /// The region must be mapped before using [`PageTable64::map_region`], or + /// unexpected behaviors may occur. + pub fn unmap_region(&mut self, vaddr: VirtAddr, size: usize) -> PagingResult { + trace!( + "unmap_region({:#x}) [{:#x}, {:#x})", + self.root_paddr(), + vaddr, + vaddr + size, + ); + let mut vaddr = vaddr; + let mut size = size; + while size > 0 { + let (_, page_size) = self + .unmap(vaddr) + .inspect_err(|e| error!("failed to unmap page: {:#x?}, {:?}", vaddr, e))?; + assert!(vaddr.is_aligned(page_size)); + assert!(page_size as usize <= size); + vaddr += page_size as usize; + size -= page_size as usize; + } + Ok(()) + } + + /// Walk the page table recursively. + /// + /// When reaching the leaf page table, call `func` on the current page table + /// entry. The max number of enumerations in one table is limited by `limit`. + /// + /// The arguments of `func` are: + /// - Current level (starts with `0`): `usize` + /// - The index of the entry in the current-level table: `usize` + /// - The virtual address that is mapped to the entry: [`VirtAddr`] + /// - The reference of the entry: [`&PTE`](GenericPTE) + pub fn walk(&self, limit: usize, func: &F) -> PagingResult + where + F: Fn(usize, usize, VirtAddr, &PTE), + { + self.walk_recursive( + self.table_of(self.root_paddr()), + 0, + VirtAddr::from(0), + limit, + func, + ) + } +} + +// Private implements. +impl PageTable64 { + fn alloc_table() -> PagingResult { + if let Some(paddr) = IF::alloc_frame() { + let ptr = IF::phys_to_virt(paddr).as_mut_ptr(); + unsafe { core::ptr::write_bytes(ptr, 0, PAGE_SIZE_4K) }; + Ok(paddr) + } else { + Err(PagingError::NoMemory) + } + } + + fn table_of<'a>(&self, paddr: PhysAddr) -> &'a [PTE] { + let ptr = IF::phys_to_virt(paddr).as_ptr() as _; + unsafe { core::slice::from_raw_parts(ptr, ENTRY_COUNT) } + } + + fn table_of_mut<'a>(&self, paddr: PhysAddr) -> &'a mut [PTE] { + let ptr = IF::phys_to_virt(paddr).as_mut_ptr() as _; + unsafe { core::slice::from_raw_parts_mut(ptr, ENTRY_COUNT) } + } + + fn next_table_mut<'a>(&self, entry: &PTE) -> PagingResult<&'a mut [PTE]> { + if !entry.is_present() { + Err(PagingError::NotMapped) + } else if entry.is_huge() { + Err(PagingError::MappedToHugePage) + } else { + Ok(self.table_of_mut(entry.paddr())) + } + } + + fn next_table_mut_or_create<'a>(&mut self, entry: &mut PTE) -> PagingResult<&'a mut [PTE]> { + if entry.is_unused() { + let paddr = Self::alloc_table()?; + self.intrm_tables.push(paddr); + *entry = GenericPTE::new_table(paddr); + Ok(self.table_of_mut(paddr)) + } else { + self.next_table_mut(entry) + } + } + + fn get_entry_mut(&self, vaddr: VirtAddr) -> PagingResult<(&mut PTE, PageSize)> { + let p3 = if M::LEVELS == 3 { + self.table_of_mut(self.root_paddr()) + } else if M::LEVELS == 4 { + let p4 = self.table_of_mut(self.root_paddr()); + let p4e = &mut p4[p4_index(vaddr)]; + self.next_table_mut(p4e)? + } else { + unreachable!() + }; + let p3e = &mut p3[p3_index(vaddr)]; + if p3e.is_huge() { + return Ok((p3e, PageSize::Size1G)); + } + + let p2 = self.next_table_mut(p3e)?; + let p2e = &mut p2[p2_index(vaddr)]; + if p2e.is_huge() { + return Ok((p2e, PageSize::Size2M)); + } + + let p1 = self.next_table_mut(p2e)?; + let p1e = &mut p1[p1_index(vaddr)]; + Ok((p1e, PageSize::Size4K)) + } + + fn get_entry_mut_or_create( + &mut self, + vaddr: VirtAddr, + page_size: PageSize, + ) -> PagingResult<&mut PTE> { + let p3 = if M::LEVELS == 3 { + self.table_of_mut(self.root_paddr()) + } else if M::LEVELS == 4 { + let p4 = self.table_of_mut(self.root_paddr()); + let p4e = &mut p4[p4_index(vaddr)]; + self.next_table_mut_or_create(p4e)? + } else { + unreachable!() + }; + let p3e = &mut p3[p3_index(vaddr)]; + if page_size == PageSize::Size1G { + return Ok(p3e); + } + + let p2 = self.next_table_mut_or_create(p3e)?; + let p2e = &mut p2[p2_index(vaddr)]; + if page_size == PageSize::Size2M { + return Ok(p2e); + } + + let p1 = self.next_table_mut_or_create(p2e)?; + let p1e = &mut p1[p1_index(vaddr)]; + Ok(p1e) + } + + fn walk_recursive( + &self, + table: &[PTE], + level: usize, + start_vaddr: VirtAddr, + limit: usize, + func: &F, + ) -> PagingResult + where + F: Fn(usize, usize, VirtAddr, &PTE), + { + let mut n = 0; + for (i, entry) in table.iter().enumerate() { + let vaddr = start_vaddr + (i << (12 + (M::LEVELS - 1 - level) * 9)); + if entry.is_present() { + func(level, i, vaddr, entry); + if level < M::LEVELS - 1 && !entry.is_huge() { + let table_entry = self.next_table_mut(entry)?; + self.walk_recursive(table_entry, level + 1, vaddr, limit, func)?; + } + n += 1; + if n >= limit { + break; + } + } + } + Ok(()) + } +} + +impl Drop for PageTable64 { + fn drop(&mut self) { + for frame in &self.intrm_tables { + IF::dealloc_frame(*frame); + } + } +} diff --git a/crates/page_table/src/lib.rs b/crates/page_table/src/lib.rs new file mode 100644 index 000000000..0af664994 --- /dev/null +++ b/crates/page_table/src/lib.rs @@ -0,0 +1,124 @@ +//! This crate provides generic, unified, architecture-independent, and OS-free +//! page table structures for various hardware architectures. +//! +//! The core struct is [`PageTable64`]. OS-functions and +//! architecture-dependent types are provided by generic parameters: +//! +//! - `M`: The architecture-dependent metadata, requires to implement +//! the [`PagingMetaData`] trait. +//! - `PTE`: The architecture-dependent page table entry, requires to implement +//! the [`GenericPTE`] trait. +//! - `IF`: OS-functions such as physical memory allocation, requires to +//! implement the [`PagingIf`] trait. +//! +//! Currently supported architectures and page table structures: +//! +//! - x86: [`x86_64::X64PageTable`] +//! - ARM: [`aarch64::A64PageTable`] +//! - RISC-V: [`riscv::Sv39PageTable`], [`riscv::Sv48PageTable`] + +#![no_std] +#![feature(const_trait_impl)] +#![feature(result_option_inspect)] +#![feature(doc_auto_cfg)] + +#[macro_use] +extern crate log; + +mod arch; +mod bits64; + +use memory_addr::{PhysAddr, VirtAddr}; + +pub use self::arch::*; +pub use self::bits64::PageTable64; + +#[doc(no_inline)] +pub use page_table_entry::{GenericPTE, MappingFlags}; + +/// The error type for page table operation failures. +#[derive(Debug)] +pub enum PagingError { + /// Cannot allocate memory. + NoMemory, + /// The address is not aligned to the page size. + NotAligned, + /// The mapping is not present. + NotMapped, + /// The mapping is already present. + AlreadyMapped, + /// The page table entry represents a huge page, but the target physical + /// frame is 4K in size. + MappedToHugePage, +} + +/// The specialized `Result` type for page table operations. +pub type PagingResult = Result; + +/// The **architecture-dependent** metadata that must be provided for +/// [`PageTable64`]. +#[const_trait] +pub trait PagingMetaData: Sync + Send + Sized { + /// The number of levels of the hardware page table. + const LEVELS: usize; + /// The maximum number of bits of physical address. + const PA_MAX_BITS: usize; + /// The maximum number of bits of virtual address. + const VA_MAX_BITS: usize; + + /// The maximum physical address. + const PA_MAX_ADDR: usize = (1 << Self::PA_MAX_BITS) - 1; + + /// Whether a given physical address is valid. + #[inline] + fn paddr_is_valid(paddr: usize) -> bool { + paddr <= Self::PA_MAX_ADDR // default + } + + /// Whether a given virtual address is valid. + #[inline] + fn vaddr_is_valid(vaddr: usize) -> bool { + // default: top bits sign extended + let top_mask = usize::MAX << (Self::VA_MAX_BITS - 1); + (vaddr & top_mask) == 0 || (vaddr & top_mask) == top_mask + } +} + +/// The low-level **OS-dependent** helpers that must be provided for +/// [`PageTable64`]. +pub trait PagingIf: Sized { + /// Request to allocate a 4K-sized physical frame. + fn alloc_frame() -> Option; + /// Request to free a allocated physical frame. + fn dealloc_frame(paddr: PhysAddr); + /// Returns a virtual address that maps to the given physical address. + /// + /// Used to access the physical memory directly in page table implementation. + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr; +} + +/// The page sizes supported by the hardware page table. +#[repr(usize)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum PageSize { + /// Size of 4 kilobytes (212 bytes). + Size4K = 0x1000, + /// Size of 2 megabytes (221 bytes). + Size2M = 0x20_0000, + /// Size of 1 gigabytes (230 bytes). + Size1G = 0x4000_0000, +} + +impl PageSize { + /// Whether this page size is considered huge (larger than 4K). + pub const fn is_huge(self) -> bool { + matches!(self, Self::Size1G | Self::Size2M) + } +} + +impl From for usize { + #[inline] + fn from(size: PageSize) -> usize { + size as usize + } +} diff --git a/crates/page_table_entry/Cargo.toml b/crates/page_table_entry/Cargo.toml new file mode 100644 index 000000000..a5a35dff2 --- /dev/null +++ b/crates/page_table_entry/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "page_table_entry" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Page table entry definition for various hardware architectures" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/page_table_entry" +documentation = "https://rcore-os.github.io/arceos/page_table_entry/index.html" + +[dependencies] +bitflags = "2.2" +memory_addr = { path = "../memory_addr" } +aarch64-cpu = "9.3" # TODO: put it in [target.'cfg(target_arch = "aarch64")'.dependencies] + +[target.'cfg(target_arch = "x86_64")'.dependencies] +x86_64 = "0.14" diff --git a/crates/page_table_entry/src/arch/aarch64.rs b/crates/page_table_entry/src/arch/aarch64.rs new file mode 100644 index 000000000..5b88b97ec --- /dev/null +++ b/crates/page_table_entry/src/arch/aarch64.rs @@ -0,0 +1,240 @@ +//! AArch64 VMSAv8-64 translation table format descriptors. + +use aarch64_cpu::registers::MAIR_EL1; +use core::fmt; +use memory_addr::PhysAddr; + +use crate::{GenericPTE, MappingFlags}; + +bitflags::bitflags! { + /// Memory attribute fields in the VMSAv8-64 translation table format descriptors. + #[derive(Debug)] + pub struct DescriptorAttr: u64 { + // Attribute fields in stage 1 VMSAv8-64 Block and Page descriptors: + + /// Whether the descriptor is valid. + const VALID = 1 << 0; + /// The descriptor gives the address of the next level of translation table or 4KB page. + /// (not a 2M, 1G block) + const NON_BLOCK = 1 << 1; + /// Memory attributes index field. + const ATTR_INDX = 0b111 << 2; + /// Non-secure bit. For memory accesses from Secure state, specifies whether the output + /// address is in Secure or Non-secure memory. + const NS = 1 << 5; + /// Access permission: accessable at EL0. + const AP_EL0 = 1 << 6; + /// Access permission: read-only. + const AP_RO = 1 << 7; + /// Shareability: Inner Shareable (otherwise Outer Shareable). + const INNER = 1 << 8; + /// Shareability: Inner or Outer Shareable (otherwise Non-shareable). + const SHAREABLE = 1 << 9; + /// The Access flag. + const AF = 1 << 10; + /// The not global bit. + const NG = 1 << 11; + /// Indicates that 16 adjacent translation table entries point to contiguous memory regions. + const CONTIGUOUS = 1 << 52; + /// The Privileged execute-never field. + const PXN = 1 << 53; + /// The Execute-never or Unprivileged execute-never field. + const UXN = 1 << 54; + + // Next-level attributes in stage 1 VMSAv8-64 Table descriptors: + + /// PXN limit for subsequent levels of lookup. + const PXN_TABLE = 1 << 59; + /// XN limit for subsequent levels of lookup. + const XN_TABLE = 1 << 60; + /// Access permissions limit for subsequent levels of lookup: access at EL0 not permitted. + const AP_NO_EL0_TABLE = 1 << 61; + /// Access permissions limit for subsequent levels of lookup: write access not permitted. + const AP_NO_WRITE_TABLE = 1 << 62; + /// For memory accesses from Secure state, specifies the Security state for subsequent + /// levels of lookup. + const NS_TABLE = 1 << 63; + } +} + +/// The memory attributes index field in the descriptor, which is used to index +/// into the MAIR (Memory Attribute Indirection Register). +#[repr(u64)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum MemAttr { + /// Device-nGnRE memory + Device = 0, + /// Normal memory + Normal = 1, + /// Normal non-cacheable memory + NormalNonCacheable = 2, +} + +impl DescriptorAttr { + #[allow(clippy::unusual_byte_groupings)] + const ATTR_INDEX_MASK: u64 = 0b111_00; + + /// Constructs a descriptor from the memory index, leaving the other fields + /// empty. + pub const fn from_mem_attr(idx: MemAttr) -> Self { + let mut bits = (idx as u64) << 2; + if matches!(idx, MemAttr::Normal | MemAttr::NormalNonCacheable) { + bits |= Self::INNER.bits() | Self::SHAREABLE.bits(); + } + Self::from_bits_retain(bits) + } + + /// Returns the memory attribute index field. + pub const fn mem_attr(&self) -> Option { + let idx = (self.bits() & Self::ATTR_INDEX_MASK) >> 2; + Some(match idx { + 0 => MemAttr::Device, + 1 => MemAttr::Normal, + 2 => MemAttr::NormalNonCacheable, + _ => return None, + }) + } +} + +impl MemAttr { + /// The MAIR_ELx register should be set to this value to match the memory + /// attributes in the descriptors. + pub const MAIR_VALUE: u64 = { + // Device-nGnRE memory + let attr0 = MAIR_EL1::Attr0_Device::nonGathering_nonReordering_EarlyWriteAck.value; + // Normal memory + let attr1 = MAIR_EL1::Attr1_Normal_Inner::WriteBack_NonTransient_ReadWriteAlloc.value + | MAIR_EL1::Attr1_Normal_Outer::WriteBack_NonTransient_ReadWriteAlloc.value; + let attr2 = MAIR_EL1::Attr2_Normal_Inner::NonCacheable.value + + MAIR_EL1::Attr2_Normal_Outer::NonCacheable.value; + attr0 | attr1 | attr2 // 0x44_ff_04 + }; +} + +impl From for MappingFlags { + fn from(attr: DescriptorAttr) -> Self { + let mut flags = Self::empty(); + if attr.contains(DescriptorAttr::VALID) { + flags |= Self::READ; + } + if !attr.contains(DescriptorAttr::AP_RO) { + flags |= Self::WRITE; + } + if attr.contains(DescriptorAttr::AP_EL0) { + flags |= Self::USER; + if !attr.contains(DescriptorAttr::UXN) { + flags |= Self::EXECUTE; + } + } else if !attr.intersects(DescriptorAttr::PXN) { + flags |= Self::EXECUTE; + } + match attr.mem_attr() { + Some(MemAttr::Device) => flags |= Self::DEVICE, + Some(MemAttr::NormalNonCacheable) => flags |= Self::UNCACHED, + _ => {} + } + flags + } +} + +impl From for DescriptorAttr { + fn from(flags: MappingFlags) -> Self { + let mut attr = if flags.contains(MappingFlags::DEVICE) { + Self::from_mem_attr(MemAttr::Device) + } else if flags.contains(MappingFlags::UNCACHED) { + Self::from_mem_attr(MemAttr::NormalNonCacheable) + } else { + Self::from_mem_attr(MemAttr::Normal) + }; + if flags.contains(MappingFlags::READ) { + attr |= Self::VALID; + } + if !flags.contains(MappingFlags::WRITE) { + attr |= Self::AP_RO; + } + if flags.contains(MappingFlags::USER) { + attr |= Self::AP_EL0 | Self::PXN; + if !flags.contains(MappingFlags::EXECUTE) { + attr |= Self::UXN; + } + } else { + attr |= Self::UXN; + if !flags.contains(MappingFlags::EXECUTE) { + attr |= Self::PXN; + } + } + attr + } +} + +/// A VMSAv8-64 translation table descriptor. +/// +/// Note that the **AttrIndx\[2:0\]** (bit\[4:2\]) field is set to `0` for device +/// memory, and `1` for normal memory. The system must configure the MAIR_ELx +/// system register accordingly. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct A64PTE(u64); + +impl A64PTE { + const PHYS_ADDR_MASK: u64 = 0x0000_ffff_ffff_f000; // bits 12..48 + + /// Creates an empty descriptor with all bits set to zero. + pub const fn empty() -> Self { + Self(0) + } +} + +impl GenericPTE for A64PTE { + fn new_page(paddr: PhysAddr, flags: MappingFlags, is_huge: bool) -> Self { + let mut attr = DescriptorAttr::from(flags) | DescriptorAttr::AF; + if !is_huge { + attr |= DescriptorAttr::NON_BLOCK; + } + Self(attr.bits() | (paddr.as_usize() as u64 & Self::PHYS_ADDR_MASK)) + } + fn new_table(paddr: PhysAddr) -> Self { + let attr = DescriptorAttr::NON_BLOCK | DescriptorAttr::VALID; + Self(attr.bits() | (paddr.as_usize() as u64 & Self::PHYS_ADDR_MASK)) + } + fn paddr(&self) -> PhysAddr { + PhysAddr::from((self.0 & Self::PHYS_ADDR_MASK) as usize) + } + fn flags(&self) -> MappingFlags { + DescriptorAttr::from_bits_truncate(self.0).into() + } + fn set_paddr(&mut self, paddr: PhysAddr) { + self.0 = (self.0 & !Self::PHYS_ADDR_MASK) | (paddr.as_usize() as u64 & Self::PHYS_ADDR_MASK) + } + fn set_flags(&mut self, flags: MappingFlags, is_huge: bool) { + let mut attr = DescriptorAttr::from(flags) | DescriptorAttr::AF; + if !is_huge { + attr |= DescriptorAttr::NON_BLOCK; + } + self.0 = (self.0 & Self::PHYS_ADDR_MASK) | attr.bits(); + } + + fn is_unused(&self) -> bool { + self.0 == 0 + } + fn is_present(&self) -> bool { + DescriptorAttr::from_bits_truncate(self.0).contains(DescriptorAttr::VALID) + } + fn is_huge(&self) -> bool { + !DescriptorAttr::from_bits_truncate(self.0).contains(DescriptorAttr::NON_BLOCK) + } + fn clear(&mut self) { + self.0 = 0 + } +} + +impl fmt::Debug for A64PTE { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut f = f.debug_struct("A64PTE"); + f.field("raw", &self.0) + .field("paddr", &self.paddr()) + .field("attr", &DescriptorAttr::from_bits_truncate(self.0)) + .field("flags", &self.flags()) + .finish() + } +} diff --git a/crates/page_table_entry/src/arch/mod.rs b/crates/page_table_entry/src/arch/mod.rs new file mode 100644 index 000000000..9491f9539 --- /dev/null +++ b/crates/page_table_entry/src/arch/mod.rs @@ -0,0 +1,9 @@ +#[cfg(target_arch = "x86_64")] +pub mod x86_64; + +#[doc(cfg(any(target_arch = "riscv32", target_arch = "riscv64")))] +pub mod riscv; + +// TODO: `#[cfg(any(target_arch = "aarch64", doc))]` does not work. +#[doc(cfg(target_arch = "aarch64"))] +pub mod aarch64; diff --git a/crates/page_table_entry/src/arch/riscv.rs b/crates/page_table_entry/src/arch/riscv.rs new file mode 100644 index 000000000..16c9fc592 --- /dev/null +++ b/crates/page_table_entry/src/arch/riscv.rs @@ -0,0 +1,130 @@ +//! RISC-V page table entries. + +use core::fmt; +use memory_addr::PhysAddr; + +use crate::{GenericPTE, MappingFlags}; + +bitflags::bitflags! { + /// Page-table entry flags. + #[derive(Debug)] + pub struct PTEFlags: usize { + /// Whether the PTE is valid. + const V = 1 << 0; + /// Whether the page is readable. + const R = 1 << 1; + /// Whether the page is writable. + const W = 1 << 2; + /// Whether the page is executable. + const X = 1 << 3; + /// Whether the page is accessible to user mode. + const U = 1 << 4; + /// Designates a global mapping. + const G = 1 << 5; + /// Indicates the virtual page has been read, written, or fetched from + /// since the last time the A bit was cleared. + const A = 1 << 6; + /// Indicates the virtual page has been written since the last time the + /// D bit was cleared. + const D = 1 << 7; + } +} + +impl From for MappingFlags { + fn from(f: PTEFlags) -> Self { + let mut ret = Self::empty(); + if f.contains(PTEFlags::R) { + ret |= Self::READ; + } + if f.contains(PTEFlags::W) { + ret |= Self::WRITE; + } + if f.contains(PTEFlags::X) { + ret |= Self::EXECUTE; + } + if f.contains(PTEFlags::U) { + ret |= Self::USER; + } + ret + } +} + +impl From for PTEFlags { + fn from(f: MappingFlags) -> Self { + if f.is_empty() { + return Self::empty(); + } + let mut ret = Self::V; + if f.contains(MappingFlags::READ) { + ret |= Self::R; + } + if f.contains(MappingFlags::WRITE) { + ret |= Self::W; + } + if f.contains(MappingFlags::EXECUTE) { + ret |= Self::X; + } + if f.contains(MappingFlags::USER) { + ret |= Self::U; + } + ret + } +} + +/// Sv39 and Sv48 page table entry for RV64 systems. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct Rv64PTE(u64); + +impl Rv64PTE { + const PHYS_ADDR_MASK: u64 = (1 << 54) - (1 << 10); // bits 10..54 +} + +impl GenericPTE for Rv64PTE { + fn new_page(paddr: PhysAddr, flags: MappingFlags, _is_huge: bool) -> Self { + let flags = PTEFlags::from(flags) | PTEFlags::A | PTEFlags::D; + debug_assert!(flags.intersects(PTEFlags::R | PTEFlags::X)); + Self(flags.bits() as u64 | ((paddr.as_usize() >> 2) as u64 & Self::PHYS_ADDR_MASK)) + } + fn new_table(paddr: PhysAddr) -> Self { + Self(PTEFlags::V.bits() as u64 | ((paddr.as_usize() >> 2) as u64 & Self::PHYS_ADDR_MASK)) + } + fn paddr(&self) -> PhysAddr { + PhysAddr::from(((self.0 & Self::PHYS_ADDR_MASK) << 2) as usize) + } + fn flags(&self) -> MappingFlags { + PTEFlags::from_bits_truncate(self.0 as usize).into() + } + fn set_paddr(&mut self, paddr: PhysAddr) { + self.0 = (self.0 & !Self::PHYS_ADDR_MASK) + | ((paddr.as_usize() as u64 >> 2) & Self::PHYS_ADDR_MASK); + } + fn set_flags(&mut self, flags: MappingFlags, _is_huge: bool) { + let flags = PTEFlags::from(flags) | PTEFlags::A | PTEFlags::D; + debug_assert!(flags.intersects(PTEFlags::R | PTEFlags::X)); + self.0 = (self.0 & Self::PHYS_ADDR_MASK) | flags.bits() as u64; + } + + fn is_unused(&self) -> bool { + self.0 == 0 + } + fn is_present(&self) -> bool { + PTEFlags::from_bits_truncate(self.0 as usize).contains(PTEFlags::V) + } + fn is_huge(&self) -> bool { + PTEFlags::from_bits_truncate(self.0 as usize).intersects(PTEFlags::R | PTEFlags::X) + } + fn clear(&mut self) { + self.0 = 0 + } +} + +impl fmt::Debug for Rv64PTE { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut f = f.debug_struct("Rv64PTE"); + f.field("raw", &self.0) + .field("paddr", &self.paddr()) + .field("flags", &self.flags()) + .finish() + } +} diff --git a/crates/page_table_entry/src/arch/x86_64.rs b/crates/page_table_entry/src/arch/x86_64.rs new file mode 100644 index 000000000..9ae024c58 --- /dev/null +++ b/crates/page_table_entry/src/arch/x86_64.rs @@ -0,0 +1,114 @@ +//! x86 page table entries on 64-bit paging. + +use core::fmt; +use memory_addr::PhysAddr; + +pub use x86_64::structures::paging::page_table::PageTableFlags as PTF; + +use crate::{GenericPTE, MappingFlags}; + +impl From for MappingFlags { + fn from(f: PTF) -> Self { + if f.is_empty() { + return Self::empty(); + } + let mut ret = Self::READ; + if f.contains(PTF::WRITABLE) { + ret |= Self::WRITE; + } + if !f.contains(PTF::NO_EXECUTE) { + ret |= Self::EXECUTE; + } + if f.contains(PTF::USER_ACCESSIBLE) { + ret |= Self::USER; + } + if f.contains(PTF::NO_CACHE) { + ret |= Self::UNCACHED; + } + ret + } +} + +impl From for PTF { + fn from(f: MappingFlags) -> Self { + if f.is_empty() { + return Self::empty(); + } + let mut ret = Self::PRESENT; + if f.contains(MappingFlags::WRITE) { + ret |= Self::WRITABLE; + } + if !f.contains(MappingFlags::EXECUTE) { + ret |= Self::NO_EXECUTE; + } + if f.contains(MappingFlags::USER) { + ret |= Self::USER_ACCESSIBLE; + } + if f.contains(MappingFlags::DEVICE) || f.contains(MappingFlags::UNCACHED) { + ret |= Self::NO_CACHE | Self::WRITE_THROUGH; + } + ret + } +} + +/// An x86_64 page table entry. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct X64PTE(u64); + +impl X64PTE { + const PHYS_ADDR_MASK: u64 = 0x000f_ffff_ffff_f000; // bits 12..52 +} + +impl GenericPTE for X64PTE { + fn new_page(paddr: PhysAddr, flags: MappingFlags, is_huge: bool) -> Self { + let mut flags = PTF::from(flags); + if is_huge { + flags |= PTF::HUGE_PAGE; + } + Self(flags.bits() | (paddr.as_usize() as u64 & Self::PHYS_ADDR_MASK)) + } + fn new_table(paddr: PhysAddr) -> Self { + let flags = PTF::PRESENT | PTF::WRITABLE | PTF::USER_ACCESSIBLE; + Self(flags.bits() | (paddr.as_usize() as u64 & Self::PHYS_ADDR_MASK)) + } + fn paddr(&self) -> PhysAddr { + PhysAddr::from((self.0 & Self::PHYS_ADDR_MASK) as usize) + } + fn flags(&self) -> MappingFlags { + PTF::from_bits_truncate(self.0).into() + } + fn set_paddr(&mut self, paddr: PhysAddr) { + self.0 = (self.0 & !Self::PHYS_ADDR_MASK) | (paddr.as_usize() as u64 & Self::PHYS_ADDR_MASK) + } + fn set_flags(&mut self, flags: MappingFlags, is_huge: bool) { + let mut flags = PTF::from(flags); + if is_huge { + flags |= PTF::HUGE_PAGE; + } + self.0 = (self.0 & Self::PHYS_ADDR_MASK) | flags.bits() + } + + fn is_unused(&self) -> bool { + self.0 == 0 + } + fn is_present(&self) -> bool { + PTF::from_bits_truncate(self.0).contains(PTF::PRESENT) + } + fn is_huge(&self) -> bool { + PTF::from_bits_truncate(self.0).contains(PTF::HUGE_PAGE) + } + fn clear(&mut self) { + self.0 = 0 + } +} + +impl fmt::Debug for X64PTE { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut f = f.debug_struct("X64PTE"); + f.field("raw", &self.0) + .field("paddr", &self.paddr()) + .field("flags", &self.flags()) + .finish() + } +} diff --git a/crates/page_table_entry/src/lib.rs b/crates/page_table_entry/src/lib.rs new file mode 100644 index 000000000..bd1a269de --- /dev/null +++ b/crates/page_table_entry/src/lib.rs @@ -0,0 +1,72 @@ +//! This crate provides the definition of page table entry for various hardware +//! architectures. +//! +//! Currently supported architectures and page table entry types: +//! +//! - x86: [`x86_64::X64PTE`] +//! - ARM: [`aarch64::A64PTE`] +//! - RISC-V: [`riscv::Rv64PTE`] +//! +//! All these types implement the [`GenericPTE`] trait, which provides unified +//! methods for manipulating various page table entries. + +#![no_std] +#![feature(doc_auto_cfg)] +#![feature(doc_cfg)] + +mod arch; + +use core::fmt::Debug; +use memory_addr::PhysAddr; + +pub use self::arch::*; + +bitflags::bitflags! { + /// Generic page table entry flags that indicate the corresponding mapped + /// memory region permissions and attributes. + #[derive(Debug, Clone, Copy)] + pub struct MappingFlags: usize { + /// The memory is readable. + const READ = 1 << 0; + /// The memory is writable. + const WRITE = 1 << 1; + /// The memory is executable. + const EXECUTE = 1 << 2; + /// The memory is user accessible. + const USER = 1 << 3; + /// The memory is device memory. + const DEVICE = 1 << 4; + /// The memory is uncached. + const UNCACHED = 1 << 5; + } +} + +/// A generic page table entry. +/// +/// All architecture-specific page table entry types implement this trait. +pub trait GenericPTE: Debug + Clone + Copy + Sync + Send + Sized { + /// Creates a page table entry point to a terminate page or block. + fn new_page(paddr: PhysAddr, flags: MappingFlags, is_huge: bool) -> Self; + /// Creates a page table entry point to a next level page table. + fn new_table(paddr: PhysAddr) -> Self; + + /// Returns the physical address mapped by this entry. + fn paddr(&self) -> PhysAddr; + /// Returns the flags of this entry. + fn flags(&self) -> MappingFlags; + + /// Set mapped physical address of the entry. + fn set_paddr(&mut self, paddr: PhysAddr); + /// Set flags of the entry. + fn set_flags(&mut self, flags: MappingFlags, is_huge: bool); + + /// Returns whether this entry is zero. + fn is_unused(&self) -> bool; + /// Returns whether this entry flag indicates present. + fn is_present(&self) -> bool; + /// For non-last level translation, returns whether this entry maps to a + /// huge frame. + fn is_huge(&self) -> bool; + /// Set this entry to zero. + fn clear(&mut self); +} diff --git a/crates/percpu/Cargo.toml b/crates/percpu/Cargo.toml new file mode 100644 index 000000000..864169122 --- /dev/null +++ b/crates/percpu/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "percpu" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Define and access per-CPU data structures" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/percpu" +documentation = "https://rcore-os.github.io/arceos/percpu/index.html" + +[features] +# For single CPU use, just make the per-CPU data a global variable. +sp-naive = ["percpu_macros/sp-naive"] + +# Whether the system enables preemption. +preempt = ["percpu_macros/preempt", "dep:kernel_guard"] + +default = [] + +[dependencies] +cfg-if = "1.0" +kernel_guard = { path = "../kernel_guard", optional = true } +percpu_macros = { path = "../percpu_macros" } + +[target.'cfg(target_arch = "x86_64")'.dependencies] +x86 = "0.52" + +[target.'cfg(not(target_os = "none"))'.dependencies] +spin = "0.9" diff --git a/crates/percpu/build.rs b/crates/percpu/build.rs new file mode 100644 index 000000000..187ba3ff4 --- /dev/null +++ b/crates/percpu/build.rs @@ -0,0 +1,9 @@ +use std::path::Path; + +fn main() { + if cfg!(target_os = "linux") && cfg!(not(feature = "sp-naive")) { + let ld_script_path = Path::new(std::env!("CARGO_MANIFEST_DIR")).join("test_percpu.x"); + println!("cargo:rustc-link-arg-tests=-no-pie"); + println!("cargo:rustc-link-arg-tests=-T{}", ld_script_path.display()); + } +} diff --git a/crates/percpu/src/imp.rs b/crates/percpu/src/imp.rs new file mode 100644 index 000000000..322867b71 --- /dev/null +++ b/crates/percpu/src/imp.rs @@ -0,0 +1,125 @@ +const fn align_up(val: usize) -> usize { + const PAGE_SIZE: usize = 0x1000; + (val + PAGE_SIZE - 1) & !(PAGE_SIZE - 1) +} + +#[cfg(not(target_os = "none"))] +static PERCPU_AREA_BASE: spin::once::Once = spin::once::Once::new(); + +/// Returns the per-CPU data area size for one CPU. +#[doc(cfg(not(feature = "sp-naive")))] +pub fn percpu_area_size() -> usize { + extern "C" { + fn _percpu_load_start(); + fn _percpu_load_end(); + } + use percpu_macros::percpu_symbol_offset; + percpu_symbol_offset!(_percpu_load_end) - percpu_symbol_offset!(_percpu_load_start) +} + +/// Returns the base address of the per-CPU data area on the given CPU. +/// +/// if `cpu_id` is 0, it returns the base address of all per-CPU data areas. +#[doc(cfg(not(feature = "sp-naive")))] +pub fn percpu_area_base(cpu_id: usize) -> usize { + cfg_if::cfg_if! { + if #[cfg(target_os = "none")] { + extern "C" { + fn _percpu_start(); + } + let base = _percpu_start as usize; + } else { + let base = *PERCPU_AREA_BASE.get().unwrap(); + } + } + base + cpu_id * align_up(percpu_area_size()) +} + +/// Initialize the per-CPU data area for `max_cpu_num` CPUs. +pub fn init(max_cpu_num: usize) { + let size = percpu_area_size(); + + #[cfg(target_os = "linux")] + { + // we not load the percpu section in ELF, allocate them here. + let total_size = align_up(size) * max_cpu_num; + let layout = std::alloc::Layout::from_size_align(total_size, 0x1000).unwrap(); + PERCPU_AREA_BASE.call_once(|| unsafe { std::alloc::alloc(layout) as usize }); + } + + let base = percpu_area_base(0); + for i in 1..max_cpu_num { + let secondary_base = percpu_area_base(i); + // copy per-cpu data of the primary CPU to other CPUs. + unsafe { + core::ptr::copy_nonoverlapping(base as *const u8, secondary_base as *mut u8, size); + } + } +} + +/// Read the architecture-specific thread pointer register on the current CPU. +pub fn get_local_thread_pointer() -> usize { + let tp; + unsafe { + cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + tp = if cfg!(target_os = "linux") { + SELF_PTR.read_current_raw() + } else if cfg!(target_os = "none") { + x86::msr::rdmsr(x86::msr::IA32_GS_BASE) as usize + } else { + unimplemented!() + }; + } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { + core::arch::asm!("mv {}, gp", out(reg) tp) + } else if #[cfg(target_arch = "aarch64")] { + core::arch::asm!("mrs {}, TPIDR_EL1", out(reg) tp) + } + } + } + tp +} + +/// Set the architecture-specific thread pointer register to the per-CPU data +/// area base on the current CPU. +/// +/// `cpu_id` indicates which per-CPU data area to use. +pub fn set_local_thread_pointer(cpu_id: usize) { + let tp = percpu_area_base(cpu_id); + unsafe { + cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + if cfg!(target_os = "linux") { + const ARCH_SET_GS: u32 = 0x1001; + const SYS_ARCH_PRCTL: u32 = 158; + core::arch::asm!( + "syscall", + in("eax") SYS_ARCH_PRCTL, + in("edi") ARCH_SET_GS, + in("rsi") tp, + ); + } else if cfg!(target_os = "none") { + x86::msr::wrmsr(x86::msr::IA32_GS_BASE, tp as u64); + } else { + unimplemented!() + } + SELF_PTR.write_current_raw(tp); + } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { + core::arch::asm!("mv gp, {}", in(reg) tp) + } else if #[cfg(target_arch = "aarch64")] { + core::arch::asm!("msr TPIDR_EL1, {}", in(reg) tp) + } + } + } +} + +/// To use `percpu::__priv::NoPreemptGuard::new()` in macro expansion. +#[allow(unused_imports)] +#[cfg(feature = "preempt")] +use crate as percpu; + +/// On x86, we use `gs:SELF_PTR` to store the address of the per-CPU data area base. +#[cfg(target_arch = "x86_64")] +#[no_mangle] +#[percpu_macros::def_percpu] +static SELF_PTR: usize = 0; diff --git a/crates/percpu/src/lib.rs b/crates/percpu/src/lib.rs new file mode 100644 index 000000000..9867bc6d0 --- /dev/null +++ b/crates/percpu/src/lib.rs @@ -0,0 +1,69 @@ +//! Define and access per-CPU data structures. +//! +//! All per-CPU data is placed into several contiguous memory regions called +//! **per-CPU data areas**, the number of which is the number of CPUs. Each CPU +//! has its own per-CPU data area. The architecture-specific thread pointer +//! register (e.g., `GS_BASE` on x86_64) is set to the base address of the area +//! on initialization. +//! +//! When accessing the per-CPU data on the current CPU, it first use the thread +//! pointer register to obtain the corresponding per-CPU data area, and then add +//! an offset to access the corresponding field. +//! +//! # Notes +//! +//! Since RISC-V does not provide separate thread pointer registers for user and +//! kernel mode, we temporarily use the `gp` register to point to the per-CPU data +//! area, while the `tp` register is used for thread-local storage. +//! +//! # Examples +//! +//! ```no_run +//! #[percpu::def_percpu] +//! static CPU_ID: usize = 0; +//! +//! // initialize per-CPU data for 4 CPUs. +//! percpu::init(4); +//! // set the thread pointer register to the per-CPU data area 0. +//! percpu::set_local_thread_pointer(0); +//! +//! // access the per-CPU data `CPU_ID` on the current CPU. +//! println!("{}", CPU_ID.read_current()); // prints "0" +//! CPU_ID.write_current(1); +//! println!("{}", CPU_ID.read_current()); // prints "1" +//! ``` +//! +//! # Cargo Features +//! +//! - `sp-naive`: For **single-core** use. In this case, each per-CPU data is +//! just a global variable, architecture-specific thread pointer register is +//! not used. +//! - `preempt`: For **preemptible** system use. In this case, we need to disable +//! preemption when accessing per-CPU data. Otherwise, the data may be corrupted +//! when it's being accessing and the current thread happens to be preempted. + +#![cfg_attr(target_os = "none", no_std)] +#![feature(doc_cfg)] + +extern crate percpu_macros; + +#[cfg_attr(feature = "sp-naive", path = "naive.rs")] +mod imp; + +pub use self::imp::*; +pub use percpu_macros::def_percpu; + +#[doc(hidden)] +pub mod __priv { + #[cfg(feature = "preempt")] + pub use kernel_guard::NoPreempt as NoPreemptGuard; +} + +cfg_if::cfg_if! { + if #[cfg(doc)] { + /// Example per-CPU data for documentation only. + #[doc(cfg(doc))] + #[def_percpu] + pub static EXAMPLE_PERCPU_DATA: usize = 0; + } +} diff --git a/crates/percpu/src/naive.rs b/crates/percpu/src/naive.rs new file mode 100644 index 000000000..0c9086c2d --- /dev/null +++ b/crates/percpu/src/naive.rs @@ -0,0 +1,10 @@ +/// No effect for "sp-naive" use. +pub fn init(_max_cpu_num: usize) {} + +/// Always returns `0` for "sp-naive" use. +pub fn get_local_thread_pointer() -> usize { + 0 +} + +/// No effect for "sp-naive" use. +pub fn set_local_thread_pointer(_cpu_id: usize) {} diff --git a/crates/percpu/test_percpu.x b/crates/percpu/test_percpu.x new file mode 100644 index 000000000..89dd5ba93 --- /dev/null +++ b/crates/percpu/test_percpu.x @@ -0,0 +1,19 @@ +CPU_NUM = 4; + +SECTIONS +{ + . = ALIGN(4K); + _percpu_start = .; + .percpu 0x0 (NOLOAD) : AT(_percpu_start) { + _percpu_load_start = .; + *(.percpu .percpu.*) + _percpu_load_end = .; + . = ALIGN(64); + _percpu_size_aligned = .; + + . = _percpu_load_start + _percpu_size_aligned * CPU_NUM; + } + . = _percpu_start + SIZEOF(.percpu); + _percpu_end = .; +} +INSERT BEFORE .bss; diff --git a/crates/percpu/tests/test_percpu.rs b/crates/percpu/tests/test_percpu.rs new file mode 100644 index 000000000..13abbe771 --- /dev/null +++ b/crates/percpu/tests/test_percpu.rs @@ -0,0 +1,102 @@ +#![cfg(not(target_os = "macos"))] + +use percpu::*; + +// Initial value is unsupported for testing. + +#[def_percpu] +static BOOL: bool = false; + +#[def_percpu] +static U8: u8 = 0; + +#[def_percpu] +static U16: u16 = 0; + +#[def_percpu] +static U32: u32 = 0; + +#[def_percpu] +static U64: u64 = 0; + +#[def_percpu] +static USIZE: usize = 0; + +struct Struct { + foo: usize, + bar: u8, +} + +#[def_percpu] +static STRUCT: Struct = Struct { foo: 0, bar: 0 }; + +#[cfg(target_os = "linux")] +#[test] +fn test_percpu() { + println!("feature = \"sp-naive\": {}", cfg!(feature = "sp-naive")); + + #[cfg(feature = "sp-naive")] + let base = 0; + + #[cfg(not(feature = "sp-naive"))] + let base = { + init(4); + set_local_thread_pointer(0); + + let base = get_local_thread_pointer(); + println!("per-CPU area base = {:#x}", base); + println!("per-CPU area size = {}", percpu_area_size()); + base + }; + + println!("bool offset: {:#x}", BOOL.offset()); + println!("u8 offset: {:#x}", U8.offset()); + println!("u16 offset: {:#x}", U16.offset()); + println!("u32 offset: {:#x}", U32.offset()); + println!("u64 offset: {:#x}", U64.offset()); + println!("usize offset: {:#x}", USIZE.offset()); + println!("struct offset: {:#x}", STRUCT.offset()); + println!(); + + unsafe { + assert_eq!(base + BOOL.offset(), BOOL.current_ptr() as usize); + assert_eq!(base + U8.offset(), U8.current_ptr() as usize); + assert_eq!(base + U16.offset(), U16.current_ptr() as usize); + assert_eq!(base + U32.offset(), U32.current_ptr() as usize); + assert_eq!(base + U64.offset(), U64.current_ptr() as usize); + assert_eq!(base + USIZE.offset(), USIZE.current_ptr() as usize); + assert_eq!(base + STRUCT.offset(), STRUCT.current_ptr() as usize); + } + + BOOL.write_current(true); + U8.write_current(123); + U16.write_current(0xabcd); + U32.write_current(0xdead_beef); + U64.write_current(0xa2ce_a2ce_a2ce_a2ce); + USIZE.write_current(0xffff_0000); + + STRUCT.with_current(|s| { + s.foo = 0x2333; + s.bar = 100; + }); + + println!("bool value: {}", BOOL.read_current()); + println!("u8 value: {}", U8.read_current()); + println!("u16 value: {:#x}", U16.read_current()); + println!("u32 value: {:#x}", U32.read_current()); + println!("u64 value: {:#x}", U64.read_current()); + println!("usize value: {:#x}", USIZE.read_current()); + + assert_eq!(U8.read_current(), 123); + assert_eq!(U16.read_current(), 0xabcd); + assert_eq!(U32.read_current(), 0xdead_beef); + assert_eq!(U64.read_current(), 0xa2ce_a2ce_a2ce_a2ce); + assert_eq!(USIZE.read_current(), 0xffff_0000); + + STRUCT.with_current(|s| { + println!("struct.foo value: {:#x}", s.foo); + println!("struct.bar value: {}", s.bar); + assert_eq!(s.foo, 0x2333); + assert_eq!(s.bar, 100); + }); +} diff --git a/crates/percpu_macros/Cargo.toml b/crates/percpu_macros/Cargo.toml new file mode 100644 index 000000000..1f6963fbf --- /dev/null +++ b/crates/percpu_macros/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "percpu_macros" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Macros to define and access a per-CPU data structure" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/percpu_macros" +documentation = "https://rcore-os.github.io/arceos/percpu_macros/index.html" + +[features] +# For single CPU use, just make the per-CPU data a global variable. +sp-naive = [] + +# Whether the system enables preemption. +preempt = [] + +default = [] + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["full"] } + +[lib] +proc-macro = true diff --git a/crates/percpu_macros/src/arch.rs b/crates/percpu_macros/src/arch.rs new file mode 100644 index 000000000..6d77eb681 --- /dev/null +++ b/crates/percpu_macros/src/arch.rs @@ -0,0 +1,195 @@ +use quote::{format_ident, quote}; +use syn::{Ident, Type}; + +fn macos_unimplemented(item: proc_macro2::TokenStream) -> proc_macro2::TokenStream { + quote! { + #[cfg(not(target_os = "macos"))] + { #item } + #[cfg(target_os = "macos")] + unimplemented!() + } +} + +pub fn gen_offset(symbol: &Ident) -> proc_macro2::TokenStream { + quote! { + let value: usize; + unsafe { + #[cfg(target_arch = "x86_64")] + ::core::arch::asm!( + "movabs {0}, offset {VAR}", + out(reg) value, + VAR = sym #symbol, + ); + #[cfg(target_arch = "aarch64")] + ::core::arch::asm!( + "movz {0}, #:abs_g0_nc:{VAR}", + out(reg) value, + VAR = sym #symbol, + ); + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + ::core::arch::asm!( + "lui {0}, %hi({VAR})", + "addi {0}, {0}, %lo({VAR})", + out(reg) value, + VAR = sym #symbol, + ); + } + value + } +} + +pub fn gen_current_ptr(symbol: &Ident, ty: &Type) -> proc_macro2::TokenStream { + macos_unimplemented(quote! { + let base: usize; + #[cfg(target_arch = "x86_64")] + { + // `__PERCPU_SELF_PTR` stores GS_BASE, which is defined in crate `percpu`. + ::core::arch::asm!( + "mov {0}, gs:[offset __PERCPU_SELF_PTR]", + "add {0}, offset {VAR}", + out(reg) base, + VAR = sym #symbol, + ); + base as *const #ty + } + #[cfg(not(target_arch = "x86_64"))] + { + #[cfg(target_arch = "aarch64")] + ::core::arch::asm!("mrs {}, TPIDR_EL1", out(reg) base); + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + ::core::arch::asm!("mv {}, gp", out(reg) base); + (base + self.offset()) as *const #ty + } + }) +} + +pub fn gen_read_current_raw(symbol: &Ident, ty: &Type) -> proc_macro2::TokenStream { + let ty_str = quote!(#ty).to_string(); + let rv64_op = match ty_str.as_str() { + "bool" => "lbu", + "u8" => "lbu", + "u16" => "lhu", + "u32" => "lwu", + "u64" => "ld", + "usize" => "ld", + _ => unreachable!(), + }; + let rv64_asm = quote! { + ::core::arch::asm!( + "lui {0}, %hi({VAR})", + "add {0}, {0}, gp", + concat!(#rv64_op, " {0}, %lo({VAR})({0})"), + out(reg) value, + VAR = sym #symbol, + ) + }; + + let (x64_asm, x64_reg) = if ["bool", "u8"].contains(&ty_str.as_str()) { + ( + "mov {0}, byte ptr gs:[offset {VAR}]".into(), + format_ident!("reg_byte"), + ) + } else { + let (x64_mod, x64_ptr) = match ty_str.as_str() { + "u16" => ("x", "word"), + "u32" => ("e", "dword"), + "u64" => ("r", "qword"), + "usize" => ("r", "qword"), + _ => unreachable!(), + }; + ( + format!("mov {{0:{x64_mod}}}, {x64_ptr} ptr gs:[offset {{VAR}}]"), + format_ident!("reg"), + ) + }; + let x64_asm = quote! { + ::core::arch::asm!(#x64_asm, out(#x64_reg) value, VAR = sym #symbol) + }; + + let gen_code = |asm_stmt| { + if ty_str.as_str() == "bool" { + quote! { + let value: u8; + #asm_stmt; + value != 0 + } + } else { + quote! { + let value: #ty; + #asm_stmt; + value + } + } + }; + + let rv64_code = gen_code(rv64_asm); + let x64_code = gen_code(x64_asm); + macos_unimplemented(quote! { + #[cfg(target_arch = "riscv64")] + { #rv64_code } + #[cfg(target_arch = "x86_64")] + { #x64_code } + #[cfg(not(any(target_arch = "riscv64", target_arch = "x86_64")))] + { *self.current_ptr() } + }) +} + +pub fn gen_write_current_raw(symbol: &Ident, val: &Ident, ty: &Type) -> proc_macro2::TokenStream { + let ty_str = quote!(#ty).to_string(); + let ty_fixup = if ty_str.as_str() == "bool" { + format_ident!("u8") + } else { + format_ident!("{}", ty_str) + }; + + let rv64_op = match ty_str.as_str() { + "bool" => "sb", + "u8" => "sb", + "u16" => "sh", + "u32" => "sw", + "u64" => "sd", + "usize" => "sd", + _ => unreachable!(), + }; + let rv64_code = quote! { + ::core::arch::asm!( + "lui {0}, %hi({VAR})", + "add {0}, {0}, gp", + concat!(#rv64_op, " {1}, %lo({VAR})({0})"), + out(reg) _, + in(reg) #val as #ty_fixup, + VAR = sym #symbol, + ); + }; + + let (x64_asm, x64_reg) = if ["bool", "u8"].contains(&ty_str.as_str()) { + ( + "mov byte ptr gs:[offset {VAR}], {0}".into(), + format_ident!("reg_byte"), + ) + } else { + let (x64_mod, x64_ptr) = match ty_str.as_str() { + "u16" => ("x", "word"), + "u32" => ("e", "dword"), + "u64" => ("r", "qword"), + "usize" => ("r", "qword"), + _ => unreachable!(), + }; + ( + format!("mov {x64_ptr} ptr gs:[offset {{VAR}}], {{0:{x64_mod}}}"), + format_ident!("reg"), + ) + }; + let x64_code = quote! { + ::core::arch::asm!(#x64_asm, in(#x64_reg) #val as #ty_fixup, VAR = sym #symbol) + }; + + macos_unimplemented(quote! { + #[cfg(target_arch = "riscv64")] + { #rv64_code } + #[cfg(target_arch = "x86_64")] + { #x64_code } + #[cfg(not(any(target_arch = "riscv64", target_arch = "x86_64")))] + { *(self.current_ptr() as *mut #ty) = #val } + }) +} diff --git a/crates/percpu_macros/src/lib.rs b/crates/percpu_macros/src/lib.rs new file mode 100644 index 000000000..6daeeaa90 --- /dev/null +++ b/crates/percpu_macros/src/lib.rs @@ -0,0 +1,175 @@ +//! Macros to define and access a per-CPU data structure. +//! +//! **DO NOT** use this crate directly. Use the [percpu] crate instead. +//! +//! [percpu]: ../percpu/index.html + +#![feature(doc_cfg)] + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{format_ident, quote}; +use syn::{Error, ItemStatic}; + +#[cfg_attr(feature = "sp-naive", path = "naive.rs")] +mod arch; + +fn compiler_error(err: Error) -> TokenStream { + err.to_compile_error().into() +} + +/// Defines a per-CPU data structure. +/// +/// It should be used on a `static` variable. +/// +/// See the [crate-level documentation](../percpu/index.html) for more details. +#[proc_macro_attribute] +pub fn def_percpu(attr: TokenStream, item: TokenStream) -> TokenStream { + if !attr.is_empty() { + return compiler_error(Error::new( + Span::call_site(), + "expect an empty attribute: `#[def_percpu]`", + )); + } + + let ast = syn::parse_macro_input!(item as ItemStatic); + + let attrs = &ast.attrs; + let vis = &ast.vis; + let name = &ast.ident; + let ty = &ast.ty; + let init_expr = &ast.expr; + + let inner_symbol_name = &format_ident!("__PERCPU_{}", name); + let struct_name = &format_ident!("{}_WRAPPER", name); + + let ty_str = quote!(#ty).to_string(); + let is_primitive_int = ["bool", "u8", "u16", "u32", "u64", "usize"].contains(&ty_str.as_str()); + + let no_preempt_guard = if cfg!(feature = "preempt") { + quote! { let _guard = percpu::__priv::NoPreemptGuard::new(); } + } else { + quote! {} + }; + + // Do not generate `fn read_current()`, `fn write_current()`, etc for non primitive types. + let read_write_methods = if is_primitive_int { + let read_current_raw = arch::gen_read_current_raw(inner_symbol_name, ty); + let write_current_raw = + arch::gen_write_current_raw(inner_symbol_name, &format_ident!("val"), ty); + + quote! { + /// Returns the value of the per-CPU data on the current CPU. + /// + /// # Safety + /// + /// Caller must ensure that preemption is disabled on the current CPU. + #[inline] + pub unsafe fn read_current_raw(&self) -> #ty { + #read_current_raw + } + + /// Set the value of the per-CPU data on the current CPU. + /// + /// # Safety + /// + /// Caller must ensure that preemption is disabled on the current CPU. + #[inline] + pub unsafe fn write_current_raw(&self, val: #ty) { + #write_current_raw + } + + /// Returns the value of the per-CPU data on the current CPU. Preemption will + /// be disabled during the call. + pub fn read_current(&self) -> #ty { + #no_preempt_guard + unsafe { self.read_current_raw() } + } + + /// Set the value of the per-CPU data on the current CPU. Preemption will + /// be disabled during the call. + pub fn write_current(&self, val: #ty) { + #no_preempt_guard + unsafe { self.write_current_raw(val) } + } + } + } else { + quote! {} + }; + + let offset = arch::gen_offset(inner_symbol_name); + let current_ptr = arch::gen_current_ptr(inner_symbol_name, ty); + quote! { + #[cfg_attr(not(target_os = "macos"), link_section = ".percpu")] // unimplemented on macos + #(#attrs)* + static mut #inner_symbol_name: #ty = #init_expr; + + #[doc = concat!("Wrapper struct for the per-CPU data [`", stringify!(#name), "`]")] + #[allow(non_camel_case_types)] + #vis struct #struct_name {} + + #(#attrs)* + #vis static #name: #struct_name = #struct_name {}; + + impl #struct_name { + /// Returns the offset relative to the per-CPU data area base on the current CPU. + #[inline] + pub fn offset(&self) -> usize { + #offset + } + + /// Returns the raw pointer of this per-CPU data on the current CPU. + /// + /// # Safety + /// + /// Caller must ensure that preemption is disabled on the current CPU. + #[inline] + pub unsafe fn current_ptr(&self) -> *const #ty { + #current_ptr + } + + /// Returns the reference of the per-CPU data on the current CPU. + /// + /// # Safety + /// + /// Caller must ensure that preemption is disabled on the current CPU. + #[inline] + pub unsafe fn current_ref_raw(&self) -> &#ty { + &*self.current_ptr() + } + + /// Returns the mutable reference of the per-CPU data on the current CPU. + /// + /// # Safety + /// + /// Caller must ensure that preemption is disabled on the current CPU. + #[inline] + #[allow(clippy::mut_from_ref)] + pub unsafe fn current_ref_mut_raw(&self) -> &mut #ty { + &mut *(self.current_ptr() as *mut #ty) + } + + /// Manipulate the per-CPU data on the current CPU in the given closure. + /// Preemption will be disabled during the call. + pub fn with_current(&self, f: F) -> T + where + F: FnOnce(&mut #ty) -> T, + { + #no_preempt_guard + f(unsafe { self.current_ref_mut_raw() }) + } + + #read_write_methods + } + } + .into() +} + +#[doc(hidden)] +#[cfg(not(feature = "sp-naive"))] +#[proc_macro] +pub fn percpu_symbol_offset(item: TokenStream) -> TokenStream { + let symbol = &format_ident!("{}", item.to_string()); + let offset = arch::gen_offset(symbol); + quote!({ #offset }).into() +} diff --git a/crates/percpu_macros/src/naive.rs b/crates/percpu_macros/src/naive.rs new file mode 100644 index 000000000..0ebf77036 --- /dev/null +++ b/crates/percpu_macros/src/naive.rs @@ -0,0 +1,28 @@ +//! For single CPU use, we just make the per-CPU data a global variable. + +use quote::quote; +use syn::{Ident, Type}; + +pub fn gen_offset(symbol: &Ident) -> proc_macro2::TokenStream { + quote! { + unsafe { ::core::ptr::addr_of!(#symbol) as usize } + } +} + +pub fn gen_current_ptr(symbol: &Ident, _ty: &Type) -> proc_macro2::TokenStream { + quote! { + unsafe { ::core::ptr::addr_of!(#symbol) } + } +} + +pub fn gen_read_current_raw(_symbol: &Ident, _ty: &Type) -> proc_macro2::TokenStream { + quote! { + *self.current_ptr() + } +} + +pub fn gen_write_current_raw(_symbol: &Ident, val: &Ident, ty: &Type) -> proc_macro2::TokenStream { + quote! { + *(self.current_ptr() as *mut #ty) = #val + } +} diff --git a/crates/ratio/Cargo.toml b/crates/ratio/Cargo.toml new file mode 100644 index 000000000..2dae14d41 --- /dev/null +++ b/crates/ratio/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "ratio" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "The type of ratios and related operations" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/ratio" +documentation = "https://rcore-os.github.io/arceos/ratio/index.html" + +[dependencies] diff --git a/crates/ratio/src/lib.rs b/crates/ratio/src/lib.rs new file mode 100644 index 000000000..5ae5383b0 --- /dev/null +++ b/crates/ratio/src/lib.rs @@ -0,0 +1,187 @@ +//! The type of ratios and related operations. +//! +//! A **ratio** is the result of dividing two integers, i.e., the numerator and +//! denominator. +//! +//! # Examples +//! +//! ``` +//! use ratio::Ratio; +//! +//! let ratio = Ratio::new(1, 3); // 1 / 3 +//! assert_eq!(ratio.mul_trunc(20), 6); // trunc(20 * 1 / 3) = trunc(6.66..) = 6 +//! assert_eq!(ratio.mul_round(20), 7); // round(20 * 1 / 3) = round(6.66..) = 7 +//! println!("{:?}", ratio); // Ratio(1/3 ~= 1431655765/4294967296) +//! ``` + +#![cfg_attr(not(test), no_std)] + +use core::{cmp::PartialEq, fmt}; + +/// The ratio type. +/// +/// It converts `numerator / denominator` to `mult / (1 << shift)` to avoid +/// `u128` division on calculation. The `shift` is as large as possible to +/// improve precision. +/// +/// Currently, it only supports `u32` as the numerator and denominator. +pub struct Ratio { + numerator: u32, + denominator: u32, + mult: u32, + shift: u32, +} + +impl Ratio { + /// The zero ratio. + pub const fn zero() -> Self { + Self { + numerator: 0, + denominator: 0, + mult: 0, + shift: 0, + } + } + + /// Creates a new ratio `numerator / denominator`. + pub const fn new(numerator: u32, denominator: u32) -> Self { + assert!(!(denominator == 0 && numerator != 0)); + if numerator == 0 { + return Self { + numerator, + denominator, + mult: 0, + shift: 0, + }; + } + + // numerator / denominator == (numerator * (1 << shift) / denominator) / (1 << shift) + let mut shift = 32; + let mut mult; + loop { + mult = (((numerator as u64) << shift) + denominator as u64 / 2) / denominator as u64; + if mult <= u32::MAX as u64 || shift == 0 { + break; + } + shift -= 1; + } + + while mult % 2 == 0 && shift > 0 { + mult /= 2; + shift -= 1; + } + + Self { + numerator, + denominator, + mult: mult as u32, + shift, + } + } + + /// Get the inverse ratio. + /// + /// # Examples + /// + /// ``` + /// use ratio::Ratio; + /// + /// let ratio = Ratio::new(1, 2); + /// assert_eq!(ratio.inverse(), Ratio::new(2, 1)); + /// ``` + pub const fn inverse(&self) -> Self { + Self::new(self.denominator, self.numerator) + } + + /// Multiplies the ratio by a value and rounds the result down. + /// + /// # Examples + /// + /// ``` + /// use ratio::Ratio; + /// + /// let ratio = Ratio::new(2, 3); + /// assert_eq!(ratio.mul_trunc(99), 66); // 99 * 2 / 3 = 66 + /// assert_eq!(ratio.mul_trunc(100), 66); // trunc(100 * 2 / 3) = trunc(66.66...) = 66 + /// ``` + pub const fn mul_trunc(&self, value: u64) -> u64 { + ((value as u128 * self.mult as u128) >> self.shift) as u64 + } + + /// Multiplies the ratio by a value and rounds the result to the nearest + /// whole number. + /// + /// # Examples + /// + /// ``` + /// use ratio::Ratio; + /// + /// let ratio = Ratio::new(2, 3); + /// assert_eq!(ratio.mul_round(99), 66); // 99 * 2 / 3 = 66 + /// assert_eq!(ratio.mul_round(100), 67); // round(100 * 2 / 3) = round(66.66...) = 67 + /// ``` + pub const fn mul_round(&self, value: u64) -> u64 { + ((value as u128 * self.mult as u128 + (1 << self.shift >> 1)) >> self.shift) as u64 + } +} + +impl fmt::Debug for Ratio { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Ratio({}/{} ~= {}/{})", + self.numerator, + self.denominator, + self.mult, + 1u64 << self.shift + ) + } +} + +impl PartialEq for Ratio { + #[inline] + fn eq(&self, other: &Ratio) -> bool { + self.mult == other.mult && self.shift == other.shift + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ratio() { + let a = Ratio::new(625_000, 1_000_000); + let b = Ratio::new(1, u32::MAX); + let c = Ratio::new(u32::MAX, u32::MAX); + let d = Ratio::new(u32::MAX, 1); + + assert_eq!(a.mult, 5); + assert_eq!(a.shift, 3); + assert_eq!(a.mul_trunc(800), 500); + + assert_eq!(b.mult, 1); + assert_eq!(b.shift, 32); + assert_eq!(b.mul_trunc(u32::MAX as _), 0); + assert_eq!(b.mul_round(u32::MAX as _), 1); + + assert_eq!(c.mult, 1); + assert_eq!(c.shift, 0); + assert_eq!(c.mul_trunc(u32::MAX as _), u32::MAX as _); + + println!("{:?}", a); + println!("{:?}", b); + println!("{:?}", c); + println!("{:?}", d); + } + + #[test] + fn test_zero() { + let z1 = Ratio::new(0, 100); + let z2 = Ratio::zero(); + let z3 = Ratio::new(0, 0); + assert_eq!(z1.mul_trunc(233), 0); + assert_eq!(z2.mul_trunc(0), 0); + assert_eq!(z3.mul_round(456), 0); + } +} diff --git a/crates/scheduler/Cargo.toml b/crates/scheduler/Cargo.toml new file mode 100644 index 000000000..e6a26af2c --- /dev/null +++ b/crates/scheduler/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "scheduler" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Various scheduler algorithms in a unified interface" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/scheduler" +documentation = "https://rcore-os.github.io/arceos/scheduler/index.html" + +[dependencies] +linked_list = { path = "../linked_list" } diff --git a/crates/scheduler/src/cfs.rs b/crates/scheduler/src/cfs.rs new file mode 100644 index 000000000..30043aacf --- /dev/null +++ b/crates/scheduler/src/cfs.rs @@ -0,0 +1,190 @@ +use alloc::{collections::BTreeMap, sync::Arc}; +use core::ops::Deref; +use core::sync::atomic::{AtomicIsize, Ordering}; + +use crate::BaseScheduler; + +/// task for CFS +pub struct CFSTask { + inner: T, + init_vruntime: AtomicIsize, + delta: AtomicIsize, + nice: AtomicIsize, + id: AtomicIsize, +} + +// https://elixir.bootlin.com/linux/latest/source/include/linux/sched/prio.h + +const NICE_RANGE_POS: usize = 19; // MAX_NICE in Linux +const NICE_RANGE_NEG: usize = 20; // -MIN_NICE in Linux, the range of nice is [MIN_NICE, MAX_NICE] + +// https://elixir.bootlin.com/linux/latest/source/kernel/sched/core.c + +const NICE2WEIGHT_POS: [isize; NICE_RANGE_POS + 1] = [ + 1024, 820, 655, 526, 423, 335, 272, 215, 172, 137, 110, 87, 70, 56, 45, 36, 29, 23, 18, 15, +]; +const NICE2WEIGHT_NEG: [isize; NICE_RANGE_NEG + 1] = [ + 1024, 1277, 1586, 1991, 2501, 3121, 3906, 4904, 6100, 7620, 9548, 11916, 14949, 18705, 23254, + 29154, 36291, 46273, 56483, 71755, 88761, +]; + +impl CFSTask { + /// new with default values + pub const fn new(inner: T) -> Self { + Self { + inner, + init_vruntime: AtomicIsize::new(0_isize), + delta: AtomicIsize::new(0_isize), + nice: AtomicIsize::new(0_isize), + id: AtomicIsize::new(0_isize), + } + } + + fn get_weight(&self) -> isize { + let nice = self.nice.load(Ordering::Acquire); + if nice >= 0 { + NICE2WEIGHT_POS[nice as usize] + } else { + NICE2WEIGHT_NEG[(-nice) as usize] + } + } + + fn get_id(&self) -> isize { + self.id.load(Ordering::Acquire) + } + + fn get_vruntime(&self) -> isize { + if self.nice.load(Ordering::Acquire) == 0 { + self.init_vruntime.load(Ordering::Acquire) + self.delta.load(Ordering::Acquire) + } else { + self.init_vruntime.load(Ordering::Acquire) + + self.delta.load(Ordering::Acquire) * 1024 / self.get_weight() + } + } + + fn set_vruntime(&self, v: isize) { + self.init_vruntime.store(v, Ordering::Release); + } + + // Simple Implementation: no change in vruntime. + // Only modifying priority of current process is supported currently. + fn set_priority(&self, nice: isize) { + let current_init_vruntime = self.get_vruntime(); + self.init_vruntime + .store(current_init_vruntime, Ordering::Release); + self.delta.store(0, Ordering::Release); + self.nice.store(nice, Ordering::Release); + } + + fn set_id(&self, id: isize) { + self.id.store(id, Ordering::Release); + } + + fn task_tick(&self) { + self.delta.fetch_add(1, Ordering::Release); + } + + /// Returns a reference to the inner task struct. + pub const fn inner(&self) -> &T { + &self.inner + } +} + +impl Deref for CFSTask { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// A simple [Completely Fair Scheduler][1] (CFS). +/// +/// [1]: https://en.wikipedia.org/wiki/Completely_Fair_Scheduler +pub struct CFScheduler { + ready_queue: BTreeMap<(isize, isize), Arc>>, // (vruntime, taskid) + min_vruntime: Option, + id_pool: AtomicIsize, +} + +impl CFScheduler { + /// Creates a new empty [`CFScheduler`]. + pub const fn new() -> Self { + Self { + ready_queue: BTreeMap::new(), + min_vruntime: None, + id_pool: AtomicIsize::new(0_isize), + } + } + /// get the name of scheduler + pub fn scheduler_name() -> &'static str { + "Completely Fair" + } +} + +impl BaseScheduler for CFScheduler { + type SchedItem = Arc>; + + fn init(&mut self) {} + + fn add_task(&mut self, task: Self::SchedItem) { + if self.min_vruntime.is_none() { + self.min_vruntime = Some(AtomicIsize::new(0_isize)); + } + let vruntime = self.min_vruntime.as_mut().unwrap().load(Ordering::Acquire); + let taskid = self.id_pool.fetch_add(1, Ordering::Release); + task.set_vruntime(vruntime); + task.set_id(taskid); + self.ready_queue.insert((vruntime, taskid), task); + if let Some(((min_vruntime, _), _)) = self.ready_queue.first_key_value() { + self.min_vruntime = Some(AtomicIsize::new(*min_vruntime)); + } else { + self.min_vruntime = None; + } + } + + fn remove_task(&mut self, task: &Self::SchedItem) -> Option { + if let Some((_, tmp)) = self + .ready_queue + .remove_entry(&(task.clone().get_vruntime(), task.clone().get_id())) + { + if let Some(((min_vruntime, _), _)) = self.ready_queue.first_key_value() { + self.min_vruntime = Some(AtomicIsize::new(*min_vruntime)); + } else { + self.min_vruntime = None; + } + Some(tmp) + } else { + None + } + } + + fn pick_next_task(&mut self) -> Option { + if let Some((_, v)) = self.ready_queue.pop_first() { + Some(v) + } else { + None + } + } + + fn put_prev_task(&mut self, prev: Self::SchedItem, _preempt: bool) { + let taskid = self.id_pool.fetch_add(1, Ordering::Release); + prev.set_id(taskid); + self.ready_queue + .insert((prev.clone().get_vruntime(), taskid), prev); + } + + fn task_tick(&mut self, current: &Self::SchedItem) -> bool { + current.task_tick(); + self.min_vruntime.is_none() + || current.get_vruntime() > self.min_vruntime.as_mut().unwrap().load(Ordering::Acquire) + } + + fn set_priority(&mut self, task: &Self::SchedItem, prio: isize) -> bool { + if (-20..=19).contains(&prio) { + task.set_priority(prio); + true + } else { + false + } + } +} diff --git a/crates/scheduler/src/fifo.rs b/crates/scheduler/src/fifo.rs new file mode 100644 index 000000000..f2469e5c5 --- /dev/null +++ b/crates/scheduler/src/fifo.rs @@ -0,0 +1,102 @@ +use alloc::sync::Arc; +use core::ops::Deref; + +use linked_list::{Adapter, Links, List}; + +use crate::BaseScheduler; + +/// A task wrapper for the [`FifoScheduler`]. +/// +/// It add extra states to use in [`linked_list::List`]. +pub struct FifoTask { + inner: T, + links: Links, +} + +unsafe impl Adapter for FifoTask { + type EntryType = Self; + + #[inline] + fn to_links(t: &Self) -> &Links { + &t.links + } +} + +impl FifoTask { + /// Creates a new [`FifoTask`] from the inner task struct. + pub const fn new(inner: T) -> Self { + Self { + inner, + links: Links::new(), + } + } + + /// Returns a reference to the inner task struct. + pub const fn inner(&self) -> &T { + &self.inner + } +} + +impl Deref for FifoTask { + type Target = T; + #[inline] + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// A simple FIFO (First-In-First-Out) cooperative scheduler. +/// +/// When a task is added to the scheduler, it's placed at the end of the ready +/// queue. When picking the next task to run, the head of the ready queue is +/// taken. +/// +/// As it's a cooperative scheduler, it does nothing when the timer tick occurs. +/// +/// It internally uses a linked list as the ready queue. +pub struct FifoScheduler { + ready_queue: List>>, +} + +impl FifoScheduler { + /// Creates a new empty [`FifoScheduler`]. + pub const fn new() -> Self { + Self { + ready_queue: List::new(), + } + } + /// get the name of scheduler + pub fn scheduler_name() -> &'static str { + "FIFO" + } +} + +impl BaseScheduler for FifoScheduler { + type SchedItem = Arc>; + + fn init(&mut self) {} + + fn add_task(&mut self, task: Self::SchedItem) { + self.ready_queue.push_back(task); + } + + fn remove_task(&mut self, task: &Self::SchedItem) -> Option { + unsafe { self.ready_queue.remove(task) } + } + + fn pick_next_task(&mut self) -> Option { + self.ready_queue.pop_front() + } + + fn put_prev_task(&mut self, prev: Self::SchedItem, _preempt: bool) { + self.ready_queue.push_back(prev); + } + + fn task_tick(&mut self, _current: &Self::SchedItem) -> bool { + false // no reschedule + } + + fn set_priority(&mut self, _task: &Self::SchedItem, _prio: isize) -> bool { + false + } +} diff --git a/crates/scheduler/src/lib.rs b/crates/scheduler/src/lib.rs new file mode 100644 index 000000000..28f055a53 --- /dev/null +++ b/crates/scheduler/src/lib.rs @@ -0,0 +1,69 @@ +//! Various scheduler algorithms in a unified interface. +//! +//! Currently supported algorithms: +//! +//! - [`FifoScheduler`]: FIFO (First-In-First-Out) scheduler (cooperative). +//! - [`RRScheduler`]: Round-robin scheduler (preemptive). +//! - [`CFScheduler`]: Completely Fair Scheduler (preemptive). + +#![cfg_attr(not(test), no_std)] +#![feature(const_mut_refs)] + +mod cfs; +mod fifo; +mod round_robin; + +#[cfg(test)] +mod tests; + +extern crate alloc; + +pub use cfs::{CFSTask, CFScheduler}; +pub use fifo::{FifoScheduler, FifoTask}; +pub use round_robin::{RRScheduler, RRTask}; + +/// The base scheduler trait that all schedulers should implement. +/// +/// All tasks in the scheduler are considered runnable. If a task is go to +/// sleep, it should be removed from the scheduler. +pub trait BaseScheduler { + /// Type of scheduled entities. Often a task struct. + type SchedItem; + + /// Initializes the scheduler. + fn init(&mut self); + + /// Adds a task to the scheduler. + fn add_task(&mut self, task: Self::SchedItem); + + /// Removes a task by its reference from the scheduler. Returns the owned + /// removed task with ownership if it exists. + /// + /// # Safety + /// + /// The caller should ensure that the task is in the scheduler, otherwise + /// the behavior is undefined. + fn remove_task(&mut self, task: &Self::SchedItem) -> Option; + + /// Picks the next task to run, it will be removed from the scheduler. + /// Returns [`None`] if there is not runnable task. + fn pick_next_task(&mut self) -> Option; + + /// Puts the previous task back to the scheduler. The previous task is + /// usually placed at the end of the ready queue, making it less likely + /// to be re-scheduled. + /// + /// `preempt` indicates whether the previous task is preempted by the next + /// task. In this case, the previous task may be placed at the front of the + /// ready queue. + fn put_prev_task(&mut self, prev: Self::SchedItem, preempt: bool); + + /// Advances the scheduler state at each timer tick. Returns `true` if + /// re-scheduling is required. + /// + /// `current` is the current running task. + fn task_tick(&mut self, current: &Self::SchedItem) -> bool; + + /// set priority for a task + fn set_priority(&mut self, task: &Self::SchedItem, prio: isize) -> bool; +} diff --git a/crates/scheduler/src/round_robin.rs b/crates/scheduler/src/round_robin.rs new file mode 100644 index 000000000..bf7e2c4d2 --- /dev/null +++ b/crates/scheduler/src/round_robin.rs @@ -0,0 +1,113 @@ +use alloc::{collections::VecDeque, sync::Arc}; +use core::ops::Deref; +use core::sync::atomic::{AtomicIsize, Ordering}; + +use crate::BaseScheduler; + +/// A task wrapper for the [`RRScheduler`]. +/// +/// It add a time slice counter to use in round-robin scheduling. +pub struct RRTask { + inner: T, + time_slice: AtomicIsize, +} + +impl RRTask { + /// Creates a new [`RRTask`] from the inner task struct. + pub const fn new(inner: T) -> Self { + Self { + inner, + time_slice: AtomicIsize::new(S as isize), + } + } + + fn time_slice(&self) -> isize { + self.time_slice.load(Ordering::Acquire) + } + + fn reset_time_slice(&self) { + self.time_slice.store(S as isize, Ordering::Release); + } + + /// Returns a reference to the inner task struct. + pub const fn inner(&self) -> &T { + &self.inner + } +} + +impl Deref for RRTask { + type Target = T; + #[inline] + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// A simple [Round-Robin] (RR) preemptive scheduler. +/// +/// It's very similar to the [`FifoScheduler`], but every task has a time slice +/// counter that is decremented each time a timer tick occurs. When the current +/// task's time slice counter reaches zero, the task is preempted and needs to +/// be rescheduled. +/// +/// Unlike [`FifoScheduler`], it uses [`VecDeque`] as the ready queue. So it may +/// take O(n) time to remove a task from the ready queue. +/// +/// [Round-Robin]: https://en.wikipedia.org/wiki/Round-robin_scheduling +/// [`FifoScheduler`]: crate::FifoScheduler +pub struct RRScheduler { + ready_queue: VecDeque>>, +} + +impl RRScheduler { + /// Creates a new empty [`RRScheduler`]. + pub const fn new() -> Self { + Self { + ready_queue: VecDeque::new(), + } + } + /// get the name of scheduler + pub fn scheduler_name() -> &'static str { + "Round-robin" + } +} + +impl BaseScheduler for RRScheduler { + type SchedItem = Arc>; + + fn init(&mut self) {} + + fn add_task(&mut self, task: Self::SchedItem) { + self.ready_queue.push_back(task); + } + + fn remove_task(&mut self, task: &Self::SchedItem) -> Option { + // TODO: more efficient + self.ready_queue + .iter() + .position(|t| Arc::ptr_eq(t, task)) + .and_then(|idx| self.ready_queue.remove(idx)) + } + + fn pick_next_task(&mut self) -> Option { + self.ready_queue.pop_front() + } + + fn put_prev_task(&mut self, prev: Self::SchedItem, preempt: bool) { + if prev.time_slice() > 0 && preempt { + self.ready_queue.push_front(prev) + } else { + prev.reset_time_slice(); + self.ready_queue.push_back(prev) + } + } + + fn task_tick(&mut self, current: &Self::SchedItem) -> bool { + let old_slice = current.time_slice.fetch_sub(1, Ordering::Release); + old_slice <= 1 + } + + fn set_priority(&mut self, _task: &Self::SchedItem, _prio: isize) -> bool { + false + } +} diff --git a/crates/scheduler/src/tests.rs b/crates/scheduler/src/tests.rs new file mode 100644 index 000000000..472820cae --- /dev/null +++ b/crates/scheduler/src/tests.rs @@ -0,0 +1,84 @@ +macro_rules! def_test_sched { + ($name: ident, $scheduler: ty, $task: ty) => { + mod $name { + use crate::*; + use alloc::sync::Arc; + + #[test] + fn test_sched() { + const NUM_TASKS: usize = 11; + + let mut scheduler = <$scheduler>::new(); + for i in 0..NUM_TASKS { + scheduler.add_task(Arc::new(<$task>::new(i))); + } + + for i in 0..NUM_TASKS * 10 - 1 { + let next = scheduler.pick_next_task().unwrap(); + assert_eq!(*next.inner(), i % NUM_TASKS); + // pass a tick to ensure the order of tasks + scheduler.task_tick(&next); + scheduler.put_prev_task(next, false); + } + + let mut n = 0; + while scheduler.pick_next_task().is_some() { + n += 1; + } + assert_eq!(n, NUM_TASKS); + } + + #[test] + fn bench_yield() { + const NUM_TASKS: usize = 1_000_000; + const COUNT: usize = NUM_TASKS * 3; + + let mut scheduler = <$scheduler>::new(); + for i in 0..NUM_TASKS { + scheduler.add_task(Arc::new(<$task>::new(i))); + } + + let t0 = std::time::Instant::now(); + for _ in 0..COUNT { + let next = scheduler.pick_next_task().unwrap(); + scheduler.put_prev_task(next, false); + } + let t1 = std::time::Instant::now(); + println!( + " {}: task yield speed: {:?}/task", + stringify!($scheduler), + (t1 - t0) / (COUNT as u32) + ); + } + + #[test] + fn bench_remove() { + const NUM_TASKS: usize = 10_000; + + let mut scheduler = <$scheduler>::new(); + let mut tasks = Vec::new(); + for i in 0..NUM_TASKS { + let t = Arc::new(<$task>::new(i)); + tasks.push(t.clone()); + scheduler.add_task(t); + } + + let t0 = std::time::Instant::now(); + for i in (0..NUM_TASKS).rev() { + let t = scheduler.remove_task(&tasks[i]).unwrap(); + assert_eq!(*t.inner(), i); + } + let t1 = std::time::Instant::now(); + println!( + " {}: task remove speed: {:?}/task", + stringify!($scheduler), + (t1 - t0) / (NUM_TASKS as u32) + ); + } + } + }; +} + +def_test_sched!(fifo, FifoScheduler::, FifoTask::); +def_test_sched!(rr, RRScheduler::, RRTask::); +def_test_sched!(cfs, CFScheduler::, CFSTask::); diff --git a/crates/slab_allocator/Cargo.toml b/crates/slab_allocator/Cargo.toml new file mode 100644 index 000000000..faba1f5e5 --- /dev/null +++ b/crates/slab_allocator/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "slab_allocator" +version = "0.3.1" +authors = ["Robert Węcławski ", "Yiren Zhang "] +license = "MIT" + +description = "Slab allocator for `no_std` systems. Uses multiple slabs with blocks of different sizes and a linked list for blocks larger than 4096 bytes" +keywords = ["slab", "allocator", "no_std", "heap", "kernel"] + +[dependencies] +buddy_system_allocator = { version = "0.9", default-features = false } diff --git a/crates/slab_allocator/src/lib.rs b/crates/slab_allocator/src/lib.rs new file mode 100644 index 000000000..4f006b41d --- /dev/null +++ b/crates/slab_allocator/src/lib.rs @@ -0,0 +1,256 @@ +//! Slab allocator for `no_std` systems. It uses multiple slabs with blocks of +//! different sizes and a [buddy_system_allocator] for blocks larger than 4096 +//! bytes. +//! +//! It's based on . +//! +//! [buddy_system_allocator]: https://docs.rs/buddy_system_allocator/latest/buddy_system_allocator/ + +#![feature(allocator_api)] +#![no_std] + +extern crate alloc; +extern crate buddy_system_allocator; + +use alloc::alloc::{AllocError, Layout}; +use core::ptr::NonNull; + +#[cfg(test)] +mod tests; + +mod slab; +use slab::Slab; + +const SET_SIZE: usize = 64; +const MIN_HEAP_SIZE: usize = 0x8000; + +enum HeapAllocator { + Slab64Bytes, + Slab128Bytes, + Slab256Bytes, + Slab512Bytes, + Slab1024Bytes, + Slab2048Bytes, + Slab4096Bytes, + BuddyAllocator, +} + +/// A fixed size heap backed by multiple slabs with blocks of different sizes. +/// Allocations over 4096 bytes are served by linked list allocator. +pub struct Heap { + slab_64_bytes: Slab<64>, + slab_128_bytes: Slab<128>, + slab_256_bytes: Slab<256>, + slab_512_bytes: Slab<512>, + slab_1024_bytes: Slab<1024>, + slab_2048_bytes: Slab<2048>, + slab_4096_bytes: Slab<4096>, + buddy_allocator: buddy_system_allocator::Heap<32>, +} + +impl Heap { + /// Creates a new heap with the given `heap_start_addr` and `heap_size`. The start address must be valid + /// and the memory in the `[heap_start_addr, heap_start_addr + heap_size)` range must not be used for + /// anything else. + /// + /// # Safety + /// This function is unsafe because it can cause undefined behavior if the + /// given address is invalid. + pub unsafe fn new(heap_start_addr: usize, heap_size: usize) -> Heap { + assert!( + heap_start_addr % 4096 == 0, + "Start address should be page aligned" + ); + assert!( + heap_size >= MIN_HEAP_SIZE, + "Heap size should be greater or equal to minimum heap size" + ); + assert!( + heap_size % MIN_HEAP_SIZE == 0, + "Heap size should be a multiple of minimum heap size" + ); + Heap { + slab_64_bytes: Slab::<64>::new(0, 0), + slab_128_bytes: Slab::<128>::new(0, 0), + slab_256_bytes: Slab::<256>::new(0, 0), + slab_512_bytes: Slab::<512>::new(0, 0), + slab_1024_bytes: Slab::<1024>::new(0, 0), + slab_2048_bytes: Slab::<2048>::new(0, 0), + slab_4096_bytes: Slab::<4096>::new(0, 0), + buddy_allocator: { + let mut buddy = buddy_system_allocator::Heap::<32>::new(); + buddy.init(heap_start_addr, heap_size); + buddy + }, + } + } + + /// Adds memory to the heap. The start address must be valid + /// and the memory in the `[mem_start_addr, mem_start_addr + heap_size)` range must not be used for + /// anything else. + /// + /// # Safety + /// This function is unsafe because it can cause undefined behavior if the + /// given address is invalid. + pub unsafe fn add_memory(&mut self, heap_start_addr: usize, heap_size: usize) { + assert!( + heap_start_addr % 4096 == 0, + "Start address should be page aligned" + ); + assert!( + heap_size % 4096 == 0, + "Add Heap size should be a multiple of page size" + ); + self.buddy_allocator + .add_to_heap(heap_start_addr, heap_start_addr + heap_size); + } + + /// Adds memory to the heap. The start address must be valid + /// and the memory in the `[mem_start_addr, mem_start_addr + heap_size)` range must not be used for + /// anything else. + /// In case of linked list allocator the memory can only be extended. + /// + /// # Safety + /// This function is unsafe because it can cause undefined behavior if the + /// given address is invalid. + unsafe fn _grow(&mut self, mem_start_addr: usize, mem_size: usize, slab: HeapAllocator) { + match slab { + HeapAllocator::Slab64Bytes => self.slab_64_bytes.grow(mem_start_addr, mem_size), + HeapAllocator::Slab128Bytes => self.slab_128_bytes.grow(mem_start_addr, mem_size), + HeapAllocator::Slab256Bytes => self.slab_256_bytes.grow(mem_start_addr, mem_size), + HeapAllocator::Slab512Bytes => self.slab_512_bytes.grow(mem_start_addr, mem_size), + HeapAllocator::Slab1024Bytes => self.slab_1024_bytes.grow(mem_start_addr, mem_size), + HeapAllocator::Slab2048Bytes => self.slab_2048_bytes.grow(mem_start_addr, mem_size), + HeapAllocator::Slab4096Bytes => self.slab_4096_bytes.grow(mem_start_addr, mem_size), + HeapAllocator::BuddyAllocator => self + .buddy_allocator + .add_to_heap(mem_start_addr, mem_start_addr + mem_size), + } + } + + /// Allocates a chunk of the given size with the given alignment. Returns a pointer to the + /// beginning of that chunk if it was successful. Else it returns `Err`. + /// This function finds the slab of lowest size which can still accommodate the given chunk. + /// The runtime is in `O(1)` for chunks of size <= 4096, and `O(n)` when chunk size is > 4096, + pub fn allocate(&mut self, layout: Layout) -> Result { + match Heap::layout_to_allocator(&layout) { + HeapAllocator::Slab64Bytes => self + .slab_64_bytes + .allocate(layout, &mut self.buddy_allocator), + HeapAllocator::Slab128Bytes => self + .slab_128_bytes + .allocate(layout, &mut self.buddy_allocator), + HeapAllocator::Slab256Bytes => self + .slab_256_bytes + .allocate(layout, &mut self.buddy_allocator), + HeapAllocator::Slab512Bytes => self + .slab_512_bytes + .allocate(layout, &mut self.buddy_allocator), + HeapAllocator::Slab1024Bytes => self + .slab_1024_bytes + .allocate(layout, &mut self.buddy_allocator), + HeapAllocator::Slab2048Bytes => self + .slab_2048_bytes + .allocate(layout, &mut self.buddy_allocator), + HeapAllocator::Slab4096Bytes => self + .slab_4096_bytes + .allocate(layout, &mut self.buddy_allocator), + HeapAllocator::BuddyAllocator => self + .buddy_allocator + .alloc(layout) + .map(|ptr| ptr.as_ptr() as usize) + .map_err(|_| AllocError), + } + } + + /// Frees the given allocation. `ptr` must be a pointer returned + /// by a call to the `allocate` function with identical size and alignment. Undefined + /// behavior may occur for invalid arguments, thus this function is unsafe. + /// + /// This function finds the slab which contains address of `ptr` and adds the blocks beginning + /// with `ptr` address to the list of free blocks. + /// This operation is in `O(1)` for blocks <= 4096 bytes and `O(n)` for blocks > 4096 bytes. + /// + /// # Safety + /// This function is unsafe because it can cause undefined behavior if the + /// given address is invalid. + pub unsafe fn deallocate(&mut self, ptr: usize, layout: Layout) { + match Heap::layout_to_allocator(&layout) { + HeapAllocator::Slab64Bytes => self.slab_64_bytes.deallocate(ptr), + HeapAllocator::Slab128Bytes => self.slab_128_bytes.deallocate(ptr), + HeapAllocator::Slab256Bytes => self.slab_256_bytes.deallocate(ptr), + HeapAllocator::Slab512Bytes => self.slab_512_bytes.deallocate(ptr), + HeapAllocator::Slab1024Bytes => self.slab_1024_bytes.deallocate(ptr), + HeapAllocator::Slab2048Bytes => self.slab_2048_bytes.deallocate(ptr), + HeapAllocator::Slab4096Bytes => self.slab_4096_bytes.deallocate(ptr), + HeapAllocator::BuddyAllocator => self + .buddy_allocator + .dealloc(NonNull::new(ptr as *mut u8).unwrap(), layout), + } + } + + /// Returns bounds on the guaranteed usable size of a successful + /// allocation created with the specified `layout`. + pub fn usable_size(&self, layout: Layout) -> (usize, usize) { + match Heap::layout_to_allocator(&layout) { + HeapAllocator::Slab64Bytes => (layout.size(), 64), + HeapAllocator::Slab128Bytes => (layout.size(), 128), + HeapAllocator::Slab256Bytes => (layout.size(), 256), + HeapAllocator::Slab512Bytes => (layout.size(), 512), + HeapAllocator::Slab1024Bytes => (layout.size(), 1024), + HeapAllocator::Slab2048Bytes => (layout.size(), 2048), + HeapAllocator::Slab4096Bytes => (layout.size(), 4096), + HeapAllocator::BuddyAllocator => (layout.size(), layout.size()), + } + } + + /// Finds allocator to use based on layout size and alignment + fn layout_to_allocator(layout: &Layout) -> HeapAllocator { + if layout.size() > 4096 { + HeapAllocator::BuddyAllocator + } else if layout.size() <= 64 && layout.align() <= 64 { + HeapAllocator::Slab64Bytes + } else if layout.size() <= 128 && layout.align() <= 128 { + HeapAllocator::Slab128Bytes + } else if layout.size() <= 256 && layout.align() <= 256 { + HeapAllocator::Slab256Bytes + } else if layout.size() <= 512 && layout.align() <= 512 { + HeapAllocator::Slab512Bytes + } else if layout.size() <= 1024 && layout.align() <= 1024 { + HeapAllocator::Slab1024Bytes + } else if layout.size() <= 2048 && layout.align() <= 2048 { + HeapAllocator::Slab2048Bytes + } else { + HeapAllocator::Slab4096Bytes + } + } + + /// Returns total memory size in bytes of the heap. + pub fn total_bytes(&self) -> usize { + self.slab_64_bytes.total_blocks() * 64 + + self.slab_128_bytes.total_blocks() * 128 + + self.slab_256_bytes.total_blocks() * 256 + + self.slab_512_bytes.total_blocks() * 512 + + self.slab_1024_bytes.total_blocks() * 1024 + + self.slab_2048_bytes.total_blocks() * 2048 + + self.slab_4096_bytes.total_blocks() * 4096 + + self.buddy_allocator.stats_total_bytes() + } + + /// Returns allocated memory size in bytes. + pub fn used_bytes(&self) -> usize { + self.slab_64_bytes.used_blocks() * 64 + + self.slab_128_bytes.used_blocks() * 128 + + self.slab_256_bytes.used_blocks() * 256 + + self.slab_512_bytes.used_blocks() * 512 + + self.slab_1024_bytes.used_blocks() * 1024 + + self.slab_2048_bytes.used_blocks() * 2048 + + self.slab_4096_bytes.used_blocks() * 4096 + + self.buddy_allocator.stats_alloc_actual() + } + + /// Returns available memory size in bytes. + pub fn available_bytes(&self) -> usize { + self.total_bytes() - self.used_bytes() + } +} diff --git a/crates/slab_allocator/src/slab.rs b/crates/slab_allocator/src/slab.rs new file mode 100644 index 000000000..0060c8c73 --- /dev/null +++ b/crates/slab_allocator/src/slab.rs @@ -0,0 +1,120 @@ +use super::SET_SIZE; +use alloc::alloc::{AllocError, Layout}; + +pub struct Slab { + free_block_list: FreeBlockList, + total_blocks: usize, +} + +impl Slab { + pub unsafe fn new(start_addr: usize, slab_size: usize) -> Slab { + let num_of_blocks = slab_size / BLK_SIZE; + Slab { + free_block_list: FreeBlockList::new(start_addr, BLK_SIZE, num_of_blocks), + total_blocks: num_of_blocks, + } + } + + pub fn total_blocks(&self) -> usize { + self.total_blocks + } + + pub fn used_blocks(&self) -> usize { + self.total_blocks - self.free_block_list.len() + } + + pub unsafe fn grow(&mut self, start_addr: usize, slab_size: usize) { + let num_of_blocks = slab_size / BLK_SIZE; + self.total_blocks += num_of_blocks; + let mut block_list = FreeBlockList::::new(start_addr, BLK_SIZE, num_of_blocks); + while let Some(block) = block_list.pop() { + self.free_block_list.push(block); + } + } + + pub fn allocate( + &mut self, + _layout: Layout, + buddy: &mut buddy_system_allocator::Heap<32>, + ) -> Result { + match self.free_block_list.pop() { + Some(block) => Ok(block.addr()), + None => { + let layout = + unsafe { Layout::from_size_align_unchecked(SET_SIZE * BLK_SIZE, 4096) }; + if let Ok(ptr) = buddy.alloc(layout) { + unsafe { + self.grow(ptr.as_ptr() as usize, SET_SIZE * BLK_SIZE); + } + Ok(self.free_block_list.pop().unwrap().addr()) + } else { + Err(AllocError) + } + } + } + } + + pub fn deallocate(&mut self, ptr: usize) { + let ptr = ptr as *mut FreeBlock; + unsafe { + self.free_block_list.push(&mut *ptr); + } + } +} + +struct FreeBlockList { + len: usize, + head: Option<&'static mut FreeBlock>, +} + +impl FreeBlockList { + unsafe fn new( + start_addr: usize, + block_size: usize, + num_of_blocks: usize, + ) -> FreeBlockList { + let mut new_list = FreeBlockList::new_empty(); + for i in (0..num_of_blocks).rev() { + let new_block = (start_addr + i * block_size) as *mut FreeBlock; + new_list.push(&mut *new_block); + } + new_list + } + + fn new_empty() -> FreeBlockList { + FreeBlockList { len: 0, head: None } + } + + fn len(&self) -> usize { + self.len + } + + fn pop(&mut self) -> Option<&'static mut FreeBlock> { + self.head.take().map(|node| { + self.head = node.next.take(); + self.len -= 1; + node + }) + } + + fn push(&mut self, free_block: &'static mut FreeBlock) { + free_block.next = self.head.take(); + self.len += 1; + self.head = Some(free_block); + } + + #[allow(dead_code)] + fn is_empty(&self) -> bool { + self.head.is_none() + } +} + +struct FreeBlock { + next: Option<&'static mut FreeBlock>, +} + +impl FreeBlock { + fn addr(&self) -> usize { + self as *const _ as usize + } +} diff --git a/crates/slab_allocator/src/tests.rs b/crates/slab_allocator/src/tests.rs new file mode 100644 index 000000000..e0a5a14a5 --- /dev/null +++ b/crates/slab_allocator/src/tests.rs @@ -0,0 +1,163 @@ +use super::*; +use alloc::alloc::Layout; +use core::mem::{align_of, size_of}; + +const HEAP_SIZE: usize = 16 * 4096; +const BIG_HEAP_SIZE: usize = HEAP_SIZE * 10; + +#[repr(align(4096))] +struct TestHeap { + heap_space: [u8; HEAP_SIZE], +} + +#[repr(align(4096))] +struct TestBigHeap { + heap_space: [u8; BIG_HEAP_SIZE], +} + +fn new_heap() -> Heap { + let test_heap = TestHeap { + heap_space: [0u8; HEAP_SIZE], + }; + let heap = unsafe { Heap::new(&test_heap.heap_space[0] as *const u8 as usize, HEAP_SIZE) }; + heap +} + +fn new_big_heap() -> Heap { + let test_heap = TestBigHeap { + heap_space: [0u8; BIG_HEAP_SIZE], + }; + let heap = unsafe { + Heap::new( + &test_heap.heap_space[0] as *const u8 as usize, + BIG_HEAP_SIZE, + ) + }; + heap +} + +#[test] +fn oom() { + let mut heap = new_heap(); + let layout = Layout::from_size_align(HEAP_SIZE + 1, align_of::()); + let addr = heap.allocate(layout.unwrap()); + assert!(addr.is_err()); +} + +#[test] +fn allocate_double_usize() { + let mut heap = new_heap(); + let size = size_of::() * 2; + let layout = Layout::from_size_align(size, align_of::()); + let addr = heap.allocate(layout.unwrap()); + assert!(addr.is_ok()); +} + +#[test] +fn allocate_and_free_double_usize() { + let mut heap = new_heap(); + let layout = Layout::from_size_align(size_of::() * 2, align_of::()).unwrap(); + let addr = heap.allocate(layout.clone()); + assert!(addr.is_ok()); + let addr = addr.unwrap(); + unsafe { + *(addr as *mut (usize, usize)) = (0xdeafdeadbeafbabe, 0xdeafdeadbeafbabe); + + heap.deallocate(addr, layout.clone()); + } +} + +#[test] +fn reallocate_double_usize() { + let mut heap = new_heap(); + + let layout = Layout::from_size_align(size_of::() * 2, align_of::()).unwrap(); + + let x = heap.allocate(layout.clone()).unwrap(); + unsafe { + heap.deallocate(x, layout.clone()); + } + + let y = heap.allocate(layout.clone()).unwrap(); + unsafe { + heap.deallocate(y, layout.clone()); + } + + assert_eq!(x as usize, y as usize); +} + +#[test] +fn allocate_multiple_sizes() { + let mut heap = new_heap(); + let base_size = size_of::(); + let base_align = align_of::(); + + let layout_1 = Layout::from_size_align(base_size * 2, base_align).unwrap(); + let layout_2 = Layout::from_size_align(base_size * 3, base_align).unwrap(); + let layout_3 = Layout::from_size_align(base_size * 3, base_align * 8).unwrap(); + let layout_4 = Layout::from_size_align(base_size * 10, base_align).unwrap(); + + let x = heap.allocate(layout_1.clone()).unwrap(); + let y = heap.allocate(layout_2.clone()).unwrap(); + let z = heap.allocate(layout_3.clone()).unwrap(); + + unsafe { + heap.deallocate(x, layout_1.clone()); + } + + let a = heap.allocate(layout_4.clone()).unwrap(); + let b = heap.allocate(layout_1.clone()).unwrap(); + + unsafe { + heap.deallocate(y, layout_2); + heap.deallocate(z, layout_3); + heap.deallocate(a, layout_4); + heap.deallocate(b, layout_1); + } +} + +#[test] +fn allocate_one_4096_block() { + let mut heap = new_big_heap(); + let base_size = size_of::(); + let base_align = align_of::(); + + let layout = Layout::from_size_align(base_size * 512, base_align).unwrap(); + + let x = heap.allocate(layout.clone()).unwrap(); + + unsafe { + heap.deallocate(x, layout.clone()); + } +} + +#[test] +fn allocate_multiple_4096_blocks() { + let mut heap = new_big_heap(); + let base_size = size_of::(); + let base_align = align_of::(); + + let layout = Layout::from_size_align(base_size * 512, base_align).unwrap(); + let layout_2 = Layout::from_size_align(base_size * 1024, base_align).unwrap(); + + let _x = heap.allocate(layout.clone()).unwrap(); + let y = heap.allocate(layout.clone()).unwrap(); + let z = heap.allocate(layout.clone()).unwrap(); + + unsafe { + heap.deallocate(y, layout.clone()); + } + + let a = heap.allocate(layout.clone()).unwrap(); + let _b = heap.allocate(layout.clone()).unwrap(); + + unsafe { + heap.deallocate(a, layout.clone()); + heap.deallocate(z, layout.clone()); + } + let c = heap.allocate(layout_2.clone()).unwrap(); + let _d = heap.allocate(layout.clone()).unwrap(); + unsafe { + *(c as *mut (usize, usize)) = (0xdeafdeadbeafbabe, 0xdeafdeadbeafbabe); + } +} diff --git a/crates/spinlock/Cargo.toml b/crates/spinlock/Cargo.toml new file mode 100644 index 000000000..1dd75e3d2 --- /dev/null +++ b/crates/spinlock/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "spinlock" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "`no_std` spin lock implementation that can disable kernel local IRQs or preemption while locking" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/spinlock" +documentation = "https://rcore-os.github.io/arceos/spinlock/index.html" + +[features] +# To use in the multi-core environment +smp = [] +default = [] + +[dependencies] +cfg-if = "1.0" +kernel_guard = { path = "../kernel_guard" } diff --git a/crates/spinlock/src/base.rs b/crates/spinlock/src/base.rs new file mode 100644 index 000000000..dc00880f7 --- /dev/null +++ b/crates/spinlock/src/base.rs @@ -0,0 +1,393 @@ +//! A naïve spinning mutex. +//! +//! Waiting threads hammer an atomic variable until it becomes available. Best-case latency is low, but worst-case +//! latency is theoretically infinite. +//! +//! Based on [`spin::Mutex`](https://docs.rs/spin/latest/src/spin/mutex/spin.rs.html). + +use core::cell::UnsafeCell; +use core::fmt; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; + +#[cfg(feature = "smp")] +use core::sync::atomic::{AtomicBool, Ordering}; + +use kernel_guard::BaseGuard; + +/// A [spin lock](https://en.m.wikipedia.org/wiki/Spinlock) providing mutually +/// exclusive access to data. +/// +/// This is a base struct, the specific behavior depends on the generic +/// parameter `G` that implements [`BaseGuard`], such as whether to disable +/// local IRQs or kernel preemption before acquiring the lock. +/// +/// For single-core environment (without the "smp" feature), we remove the lock +/// state, CPU can always get the lock if we follow the proper guard in use. +pub struct BaseSpinLock { + _phantom: PhantomData, + #[cfg(feature = "smp")] + lock: AtomicBool, + data: UnsafeCell, +} + +/// A guard that provides mutable data access. +/// +/// When the guard falls out of scope it will release the lock. +pub struct BaseSpinLockGuard<'a, G: BaseGuard, T: ?Sized + 'a> { + _phantom: &'a PhantomData, + irq_state: G::State, + data: *mut T, + #[cfg(feature = "smp")] + lock: &'a AtomicBool, +} + +// Same unsafe impls as `std::sync::Mutex` +unsafe impl Sync for BaseSpinLock {} +unsafe impl Send for BaseSpinLock {} + +impl BaseSpinLock { + /// Creates a new [`BaseSpinLock`] wrapping the supplied data. + #[inline(always)] + pub const fn new(data: T) -> Self { + Self { + _phantom: PhantomData, + data: UnsafeCell::new(data), + #[cfg(feature = "smp")] + lock: AtomicBool::new(false), + } + } + + /// Consumes this [`BaseSpinLock`] and unwraps the underlying data. + #[inline(always)] + pub fn into_inner(self) -> T { + // We know statically that there are no outstanding references to + // `self` so there's no need to lock. + let BaseSpinLock { data, .. } = self; + data.into_inner() + } +} + +impl BaseSpinLock { + /// Locks the [`BaseSpinLock`] and returns a guard that permits access to the inner data. + /// + /// The returned value may be dereferenced for data access + /// and the lock will be dropped when the guard falls out of scope. + #[inline(always)] + pub fn lock(&self) -> BaseSpinLockGuard { + let irq_state = G::acquire(); + #[cfg(feature = "smp")] + { + // Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock` + // when called in a loop. + while self + .lock + .compare_exchange_weak(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + // Wait until the lock looks unlocked before retrying + while self.is_locked() { + core::hint::spin_loop(); + } + } + } + BaseSpinLockGuard { + _phantom: &PhantomData, + irq_state, + data: unsafe { &mut *self.data.get() }, + #[cfg(feature = "smp")] + lock: &self.lock, + } + } + + /// Returns `true` if the lock is currently held. + /// + /// # Safety + /// + /// This function provides no synchronization guarantees and so its result should be considered 'out of date' + /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. + #[inline(always)] + pub fn is_locked(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(feature = "smp")] { + self.lock.load(Ordering::Relaxed) + } else { + false + } + } + } + + /// Try to lock this [`BaseSpinLock`], returning a lock guard if successful. + #[inline(always)] + pub fn try_lock(&self) -> Option> { + let irq_state = G::acquire(); + + cfg_if::cfg_if! { + if #[cfg(feature = "smp")] { + // The reason for using a strong compare_exchange is explained here: + // https://github.com/Amanieu/parking_lot/pull/207#issuecomment-575869107 + let is_unlocked = self + .lock + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_ok(); + } else { + let is_unlocked = true; + } + } + + if is_unlocked { + Some(BaseSpinLockGuard { + _phantom: &PhantomData, + irq_state, + data: unsafe { &mut *self.data.get() }, + #[cfg(feature = "smp")] + lock: &self.lock, + }) + } else { + None + } + } + + /// Force unlock this [`BaseSpinLock`]. + /// + /// # Safety + /// + /// This is *extremely* unsafe if the lock is not held by the current + /// thread. However, this can be useful in some instances for exposing the + /// lock to FFI that doesn't know how to deal with RAII. + #[inline(always)] + pub unsafe fn force_unlock(&self) { + #[cfg(feature = "smp")] + self.lock.store(false, Ordering::Release); + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the [`BaseSpinLock`] mutably, and a mutable reference is guaranteed to be exclusive in + /// Rust, no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As + /// such, this is a 'zero-cost' operation. + #[inline(always)] + pub fn get_mut(&mut self) -> &mut T { + // We know statically that there are no other references to `self`, so + // there's no need to lock the inner mutex. + unsafe { &mut *self.data.get() } + } +} + +impl Default for BaseSpinLock { + #[inline(always)] + fn default() -> Self { + Self::new(Default::default()) + } +} + +impl fmt::Debug for BaseSpinLock { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.try_lock() { + Some(guard) => write!(f, "SpinLock {{ data: ") + .and_then(|()| (*guard).fmt(f)) + .and_then(|()| write!(f, "}}")), + None => write!(f, "SpinLock {{ }}"), + } + } +} + +impl<'a, G: BaseGuard, T: ?Sized> Deref for BaseSpinLockGuard<'a, G, T> { + type Target = T; + #[inline(always)] + fn deref(&self) -> &T { + // We know statically that only we are referencing data + unsafe { &*self.data } + } +} + +impl<'a, G: BaseGuard, T: ?Sized> DerefMut for BaseSpinLockGuard<'a, G, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut T { + // We know statically that only we are referencing data + unsafe { &mut *self.data } + } +} + +impl<'a, G: BaseGuard, T: ?Sized + fmt::Debug> fmt::Debug for BaseSpinLockGuard<'a, G, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, G: BaseGuard, T: ?Sized> Drop for BaseSpinLockGuard<'a, G, T> { + /// The dropping of the [`BaseSpinLockGuard`] will release the lock it was + /// created from. + #[inline(always)] + fn drop(&mut self) { + #[cfg(feature = "smp")] + self.lock.store(false, Ordering::Release); + G::release(self.irq_state); + } +} + +#[cfg(test)] +mod tests { + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::mpsc::channel; + use std::sync::Arc; + use std::thread; + + type SpinMutex = crate::SpinRaw; + + #[derive(Eq, PartialEq, Debug)] + struct NonCopy(i32); + + #[test] + fn smoke() { + let m = SpinMutex::<_>::new(()); + drop(m.lock()); + drop(m.lock()); + } + + #[test] + #[cfg(feature = "smp")] + fn lots_and_lots() { + static M: SpinMutex<()> = SpinMutex::<_>::new(()); + static mut CNT: u32 = 0; + const J: u32 = 1000; + const K: u32 = 3; + + fn inc() { + for _ in 0..J { + unsafe { + let _g = M.lock(); + CNT += 1; + } + } + } + + let (tx, rx) = channel(); + let mut ts = Vec::new(); + for _ in 0..K { + let tx2 = tx.clone(); + ts.push(thread::spawn(move || { + inc(); + tx2.send(()).unwrap(); + })); + let tx2 = tx.clone(); + ts.push(thread::spawn(move || { + inc(); + tx2.send(()).unwrap(); + })); + } + + drop(tx); + for _ in 0..2 * K { + rx.recv().unwrap(); + } + assert_eq!(unsafe { CNT }, J * K * 2); + + for t in ts { + t.join().unwrap(); + } + } + + #[test] + #[cfg(feature = "smp")] + fn try_lock() { + let mutex = SpinMutex::<_>::new(42); + + // First lock succeeds + let a = mutex.try_lock(); + assert_eq!(a.as_ref().map(|r| **r), Some(42)); + + // Additional lock fails + let b = mutex.try_lock(); + assert!(b.is_none()); + + // After dropping lock, it succeeds again + ::core::mem::drop(a); + let c = mutex.try_lock(); + assert_eq!(c.as_ref().map(|r| **r), Some(42)); + } + + #[test] + fn test_into_inner() { + let m = SpinMutex::<_>::new(NonCopy(10)); + assert_eq!(m.into_inner(), NonCopy(10)); + } + + #[test] + fn test_into_inner_drop() { + struct Foo(Arc); + impl Drop for Foo { + fn drop(&mut self) { + self.0.fetch_add(1, Ordering::SeqCst); + } + } + let num_drops = Arc::new(AtomicUsize::new(0)); + let m = SpinMutex::<_>::new(Foo(num_drops.clone())); + assert_eq!(num_drops.load(Ordering::SeqCst), 0); + { + let _inner = m.into_inner(); + assert_eq!(num_drops.load(Ordering::SeqCst), 0); + } + assert_eq!(num_drops.load(Ordering::SeqCst), 1); + } + + #[test] + fn test_mutex_arc_nested() { + // Tests nested mutexes and access + // to underlying data. + let arc = Arc::new(SpinMutex::<_>::new(1)); + let arc2 = Arc::new(SpinMutex::<_>::new(arc)); + let (tx, rx) = channel(); + let t = thread::spawn(move || { + let lock = arc2.lock(); + let lock2 = lock.lock(); + assert_eq!(*lock2, 1); + tx.send(()).unwrap(); + }); + rx.recv().unwrap(); + t.join().unwrap(); + } + + #[test] + fn test_mutex_arc_access_in_unwind() { + let arc = Arc::new(SpinMutex::<_>::new(1)); + let arc2 = arc.clone(); + let _ = thread::spawn(move || { + struct Unwinder { + i: Arc>, + } + impl Drop for Unwinder { + fn drop(&mut self) { + *self.i.lock() += 1; + } + } + let _u = Unwinder { i: arc2 }; + panic!(); + }) + .join(); + let lock = arc.lock(); + assert_eq!(*lock, 2); + } + + #[test] + fn test_mutex_unsized() { + let mutex: &SpinMutex<[i32]> = &SpinMutex::<_>::new([1, 2, 3]); + { + let b = &mut *mutex.lock(); + b[0] = 4; + b[2] = 5; + } + let comp: &[i32] = &[4, 2, 5]; + assert_eq!(&*mutex.lock(), comp); + } + + #[test] + fn test_mutex_force_lock() { + let lock = SpinMutex::<_>::new(()); + ::std::mem::forget(lock.lock()); + unsafe { + lock.force_unlock(); + } + assert!(lock.try_lock().is_some()); + } +} diff --git a/crates/spinlock/src/lib.rs b/crates/spinlock/src/lib.rs new file mode 100644 index 000000000..ad961d570 --- /dev/null +++ b/crates/spinlock/src/lib.rs @@ -0,0 +1,45 @@ +//! `no_std` spin lock implementation that can disable kernel local IRQs or +//! preemption while locking. +//! +//! # Cargo Features +//! +//! - `smp`: Use in the **multi-core** environment. For **single-core** +//! environment (without this feature), the lock state is unnecessary and +//! optimized out. CPU can always get the lock if we follow the proper guard +//! in use. By default, this feature is disabled. + +#![cfg_attr(not(test), no_std)] + +mod base; + +use kernel_guard::{NoOp, NoPreempt, NoPreemptIrqSave}; + +pub use self::base::{BaseSpinLock, BaseSpinLockGuard}; + +/// A spin lock that disables kernel preemption while trying to lock, and +/// re-enables it after unlocking. +/// +/// It must be used in the local IRQ-disabled context, or never be used in +/// interrupt handlers. +pub type SpinNoPreempt = BaseSpinLock; + +/// A guard that provides mutable data access for [`SpinNoPreempt`]. +pub type SpinNoPreemptGuard<'a, T> = BaseSpinLockGuard<'a, NoPreempt, T>; + +/// A spin lock that disables kernel preemption and local IRQs while trying to +/// lock, and re-enables it after unlocking. +/// +/// It can be used in the IRQ-enabled context. +pub type SpinNoIrq = BaseSpinLock; + +/// A guard that provides mutable data access for [`SpinNoIrq`]. +pub type SpinNoIrqGuard<'a, T> = BaseSpinLockGuard<'a, NoPreemptIrqSave, T>; + +/// A raw spin lock that does nothing while trying to lock. +/// +/// It must be used in the preemption-disabled and local IRQ-disabled context, +/// or never be used in interrupt handlers. +pub type SpinRaw = BaseSpinLock; + +/// A guard that provides mutable data access for [`SpinRaw`]. +pub type SpinRawGuard<'a, T> = BaseSpinLockGuard<'a, NoOp, T>; diff --git a/crates/timer_list/Cargo.toml b/crates/timer_list/Cargo.toml new file mode 100644 index 000000000..edaef6878 --- /dev/null +++ b/crates/timer_list/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "timer_list" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "A list of timed events that will be triggered sequentially when the timer expires" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/timer_list" +documentation = "https://rcore-os.github.io/arceos/timer_list/index.html" + +[dependencies] diff --git a/crates/timer_list/src/lib.rs b/crates/timer_list/src/lib.rs new file mode 100644 index 000000000..b73f84868 --- /dev/null +++ b/crates/timer_list/src/lib.rs @@ -0,0 +1,234 @@ +//! A list of timed events that will be triggered sequentially when the timer +//! expires. +//! +//! # Examples +//! +//! ``` +//! use timer_list::{TimerEvent, TimerEventFn, TimerList}; +//! use std::time::{Duration, Instant}; +//! +//! let mut timer_list = TimerList::new(); +//! +//! // set a timer that will be triggered after 1 second +//! let start_time = Instant::now(); +//! timer_list.set(Duration::from_secs(1), TimerEventFn::new(|now| { +//! println!("timer event after {:?}", now); +//! })); +//! +//! while !timer_list.is_empty() { +//! // check if there is any event that is expired +//! let now = Instant::now().duration_since(start_time); +//! if let Some((deadline, event)) = timer_list.expire_one(now) { +//! // trigger the event, will print "timer event after 1.00s" +//! event.callback(now); +//! break; +//! } +//! std::thread::sleep(Duration::from_millis(10)); // relax the CPU +//! } +//! ``` + +#![cfg_attr(not(test), no_std)] + +extern crate alloc; + +use alloc::{boxed::Box, collections::BinaryHeap}; +use core::cmp::{Ord, Ordering, PartialOrd}; +use core::time::Duration; + +/// The type of the time value. +/// +/// Currently it is just an alias of [`core::time::Duration`]. +pub type TimeValue = Duration; + +/// A trait that all timed events must implement. +pub trait TimerEvent { + /// Callback function that will be called when the timer expires. + fn callback(self, now: TimeValue); +} + +struct TimerEventWrapper { + deadline: TimeValue, + event: E, +} + +/// A list of timed events. +/// +/// It internally uses a min-heap to store the events by deadline, make it +/// possible to trigger these events sequentially. +pub struct TimerList { + events: BinaryHeap>, +} + +impl PartialOrd for TimerEventWrapper { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) // reverse ordering for Min-heap + } +} + +impl Ord for TimerEventWrapper { + fn cmp(&self, other: &Self) -> Ordering { + other.deadline.cmp(&self.deadline) // reverse ordering for Min-heap + } +} + +impl PartialEq for TimerEventWrapper { + fn eq(&self, other: &Self) -> bool { + self.deadline.eq(&other.deadline) + } +} + +impl Eq for TimerEventWrapper {} + +impl TimerList { + /// Creates a new empty timer list. + pub fn new() -> Self { + Self { + events: BinaryHeap::new(), + } + } + + /// Whether there is no timed event. + #[inline] + pub fn is_empty(&self) -> bool { + self.events.is_empty() + } + + /// Set a timed event that will be triggered at `deadline`. + pub fn set(&mut self, deadline: TimeValue, event: E) { + self.events.push(TimerEventWrapper { deadline, event }); + } + + /// Cancel all events that meet the condition. + pub fn cancel(&mut self, condition: F) + where + F: Fn(&E) -> bool, + { + // TODO: performance optimization + self.events.retain(|e| !condition(&e.event)); + } + + /// Get the deadline of the most recent event. + #[inline] + pub fn next_deadline(&self) -> Option { + self.events.peek().map(|e| e.deadline) + } + + /// Try to expire the earliest event that passed the deadline at the given + /// time. + /// + /// Returns `None` if no event is expired. + pub fn expire_one(&mut self, now: TimeValue) -> Option<(TimeValue, E)> { + if let Some(e) = self.events.peek() { + if e.deadline <= now { + return self.events.pop().map(|e| (e.deadline, e.event)); + } + } + None + } +} + +impl Default for TimerList { + fn default() -> Self { + Self::new() + } +} + +/// A simple wrapper of a closure that implements the [`TimerEvent`] trait. +/// +/// So that it can be used as in the [`TimerList`]. +pub struct TimerEventFn(Box); + +impl TimerEventFn { + /// Constructs a new [`TimerEventFn`] from a closure. + pub fn new(f: F) -> Self + where + F: FnOnce(TimeValue) + 'static, + { + Self(Box::new(f)) + } +} + +impl TimerEvent for TimerEventFn { + fn callback(self, now: TimeValue) { + (self.0)(now) + } +} + +#[cfg(test)] +mod tests { + use super::{TimeValue, TimerEvent, TimerEventFn, TimerList}; + use core::sync::atomic::{AtomicUsize, Ordering}; + use std::time::{Duration, Instant}; + + #[test] + fn test_timer_list() { + const EVENT_ORDER: &[usize; 4] = &[1, 4, 3, 0]; // timer 2 was canceled + static COUNT: AtomicUsize = AtomicUsize::new(0); + + struct TestTimerEvent(usize, TimeValue); + + impl TimerEvent for TestTimerEvent { + fn callback(self, now: TimeValue) { + let idx = COUNT.fetch_add(1, Ordering::SeqCst); + assert_eq!(self.0, EVENT_ORDER[idx]); + println!( + "timer {} expired at {:?}, delta = {:?}", + self.0, + now, + now - self.1 + ); + } + } + + let mut timer_list = TimerList::new(); + let start_time = Instant::now(); + let deadlines = [ + Duration::new(3, 0), // 3.0 sec + Duration::from_micros(500_000), // 0.5 sec + Duration::from_secs(4), // 4.0 sec, canceled + Duration::new(2, 990_000_000), // 2.99 sec + Duration::from_millis(1000), // 1.0 sec, + ]; + + for (i, &ddl) in deadlines.iter().enumerate() { + timer_list.set(ddl, TestTimerEvent(i, ddl)); + } + + while !timer_list.is_empty() { + let now = Instant::now().duration_since(start_time); + if now.as_secs() > 3 { + timer_list.cancel(|e| e.0 == 2); + } + while let Some((_deadline, event)) = timer_list.expire_one(now) { + event.callback(now); + } + std::thread::sleep(Duration::from_millis(10)); // sleep 10 ms + } + + assert_eq!(COUNT.load(Ordering::SeqCst), 4); + } + + #[test] + fn test_timer_list_fn() { + let mut timer_list = TimerList::new(); + let start_time = Instant::now(); + let deadlines = [ + Duration::new(1, 1_000_000), // 1.001 sec + Duration::from_micros(750_000), // 0.75 sec + ]; + + for ddl in deadlines { + timer_list.set( + ddl, + TimerEventFn::new(|now| println!("timer fn expired at {:?}", now)), + ); + } + + while !timer_list.is_empty() { + let now = Instant::now().duration_since(start_time); + while let Some((_deadline, event)) = timer_list.expire_one(now) { + event.callback(now); + } + } + } +} diff --git a/crates/tuple_for_each/Cargo.toml b/crates/tuple_for_each/Cargo.toml new file mode 100644 index 000000000..0412dcedf --- /dev/null +++ b/crates/tuple_for_each/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "tuple_for_each" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Provides macros and methods to iterate over the fields of a tuple struct" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/crates/tuple_for_each" +documentation = "https://rcore-os.github.io/arceos/tuple_for_each/index.html" + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = "2.0" + +[lib] +proc-macro = true diff --git a/crates/tuple_for_each/src/lib.rs b/crates/tuple_for_each/src/lib.rs new file mode 100644 index 000000000..fa361d09f --- /dev/null +++ b/crates/tuple_for_each/src/lib.rs @@ -0,0 +1,165 @@ +//! Provides macros and methods to iterate over the fields of a tuple struct. +//! +//! Added methods: +//! - `is_empty() -> bool`: Whether the tuple has no field. +//! - `len() -> usize`: Number of items in the tuple. +//! +//! Generated macros: +//! +//! - `_for_each!(x in tuple { ... })` +//! - `_enumerate!((i, x) in tuple { ... })` +//! +//! # Examples +//! +//! ``` +//! use tuple_for_each::TupleForEach; +//! +//! #[derive(TupleForEach)] +//! struct FooBar(u32, &'static str, bool); +//! +//! let tup = FooBar(23, "hello", true); +//! assert!(!tup.is_empty()); +//! assert_eq!(tup.len(), 3); +//! +//! // prints "23", "hello", "true" line by line +//! foo_bar_for_each!(x in tup { +//! println!("{}", x); +//! }); +//! +//! // prints "0: 23", "1: hello", "2: true" line by line +//! foo_bar_enumerate!((i, x) in tup { +//! println!("{}: {}", i, x); +//! }); +//! ``` + +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{Data, DataStruct, DeriveInput, Error, Fields, Index}; + +/// Provides macros and methods to iterate over the fields of a tuple struct. +/// +/// See the [crate-level documentation](crate). +#[proc_macro_derive(TupleForEach)] +pub fn tuple_for_each(item: TokenStream) -> TokenStream { + let ast = syn::parse_macro_input!(item as DeriveInput); + if let Data::Struct(strct) = &ast.data { + if let Fields::Unnamed(_) = strct.fields { + return impl_for_each(&ast, strct).into(); + } + } + Error::new_spanned( + ast, + "attribute `tuple_for_each` can only be attached to tuple structs", + ) + .to_compile_error() + .into() +} + +fn gen_doc(kind: &str, tuple_name: &str, macro_name: &str) -> String { + match kind { + "for_each" => format!( + " + Iterates over the fields of a instance of [`{tuple_name}`]. + + This macro is generated by [`#[derive(tuple_for_each::TupleForEach)]`](tuple_for_each). + # Examples + ```ignore + let tuple: {tuple_name}; + {macro_name}_for_each!(field in tuple {{ + // do something with `field` + }}); + ```" + ), + "enumerate" => format!( + " + Iterates over the fields of a instance of [`{tuple_name}`], with the field index. + + This macro is generated by [`#[derive(tuple_for_each::TupleForEach)]`](tuple_for_each). + # Examples + ```ignore + let tuple: {tuple_name}; + {macro_name}_enumerate!((i, field) in tuple {{ + // do something with the index `i` and `field` + }}); + ```" + ), + _ => unimplemented!(), + } +} + +fn impl_for_each(ast: &DeriveInput, strct: &DataStruct) -> proc_macro2::TokenStream { + let tuple_name = &ast.ident; + let macro_name = pascal_to_snake(tuple_name.to_string()); + let macro_for_each = format_ident!("{}_for_each", macro_name); + let macro_enumerate = format_ident!("{}_enumerate", macro_name); + + let field_num = strct.fields.len(); + let mut for_each = vec![]; + let mut for_each_mut = vec![]; + let mut enumerate = vec![]; + let mut enumerate_mut = vec![]; + for i in 0..field_num { + let idx = Index::from(i); + for_each.push(quote!( { let $item = &$tuple.#idx; $code } )); + for_each_mut.push(quote!( { let $item = &mut $tuple.#idx; $code } )); + enumerate.push(quote!({ + let $idx = #idx; + let $item = &$tuple.#idx; + $code + })); + enumerate_mut.push(quote!({ + let $idx = #idx; + let $item = &mut $tuple.#idx; + $code + })); + } + + let macro_for_each_doc = gen_doc("for_each", &tuple_name.to_string(), ¯o_name); + let macro_enumerate_doc = gen_doc("enumerate", &tuple_name.to_string(), ¯o_name); + quote! { + impl #tuple_name { + /// Number of items in the tuple. + pub const fn len(&self) -> usize { + #field_num + } + + /// Whether the tuple has no field. + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } + } + + #[doc = #macro_for_each_doc] + #[macro_export] + macro_rules! #macro_for_each { + ($item:ident in $tuple:ident $code:block) => { + #(#for_each)* + }; + ($item:ident in mut $tuple:ident $code:block) => { + #(#for_each_mut)* + }; + } + + #[doc = #macro_enumerate_doc] + #[macro_export] + macro_rules! #macro_enumerate { + (($idx:ident, $item:ident) in $tuple:ident $code:block) => { + #(#enumerate)* + }; + (($idx:ident, $item:ident) in mut $tuple:ident $code:block) => { + #(#enumerate_mut)* + }; + } + } +} + +fn pascal_to_snake(pascal: String) -> String { + let mut ret = String::new(); + for ch in pascal.chars() { + if ch.is_ascii_uppercase() && !ret.is_empty() { + ret.push('_') + } + ret.push(ch.to_ascii_lowercase()); + } + ret +} diff --git a/crates/tuple_for_each/tests/test_tuple_for_each.rs b/crates/tuple_for_each/tests/test_tuple_for_each.rs new file mode 100644 index 000000000..4f194c737 --- /dev/null +++ b/crates/tuple_for_each/tests/test_tuple_for_each.rs @@ -0,0 +1,106 @@ +use tuple_for_each::*; + +trait Base { + type Item; + fn foo(&self) -> Self::Item; + fn bar(&self); + fn bar_mut(&mut self) {} +} + +struct A; +struct B; +struct C; + +impl Base for A { + type Item = u32; + fn foo(&self) -> Self::Item { + 1 + } + fn bar(&self) { + println!("I'am A") + } +} + +impl Base for B { + type Item = f32; + fn foo(&self) -> Self::Item { + 2.333 + } + fn bar(&self) { + println!("I'am B") + } +} + +impl Base for C { + type Item = &'static str; + fn foo(&self) -> Self::Item { + "hello" + } + fn bar(&self) { + println!("I'am C") + } +} + +#[derive(TupleForEach)] +struct Pair(A, B); + +#[derive(TupleForEach)] +struct Tuple(A, B, C); + +#[test] +fn test_for_each() { + let t = Pair(A, B); + assert_eq!(t.len(), 2); + + let mut i = 0; + pair_for_each!(x in t { + println!("for_each {}: {}", i, x.foo()); + x.bar(); + i += 1; + }); + assert_eq!(i, 2); +} + +#[test] +fn test_for_each_mut() { + let mut t = Tuple(A, B, C); + assert_eq!(t.len(), 3); + + let mut i = 0; + tuple_for_each!(x in mut t { + println!("for_each_mut {}: {}", i, x.foo()); + x.bar_mut(); + i += 1; + }); + assert_eq!(i, 3); +} + +#[test] +fn test_enumerate() { + let t = Tuple(A, B, C); + assert_eq!(t.len(), 3); + + let mut real_idx = 0; + tuple_enumerate!((i, x) in t { + println!("enumerate {}: {}", i, x.foo()); + x.bar(); + assert_eq!(i, real_idx); + real_idx += 1; + }); + assert_eq!(real_idx, 3); +} + +#[test] +fn test_enumerate_mut() { + let mut t = Pair(A, B); + assert_eq!(t.len(), 2); + + let mut real_idx = 0; + pair_enumerate!((i, x) in mut t { + println!("enumerate_mut {}: {}", i, x.foo()); + x.bar_mut(); + assert_eq!(i, real_idx); + real_idx += 1; + }); + assert_eq!(real_idx, 2); +} diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 000000000..40603ab39 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,264 @@ +# ArceOS Architecture Overview + +## Rustdoc + +* https://rcore-os.github.io/arceos/ + +## ArceOS Modules + +* [axalloc](../modules/axalloc): ArceOS global memory allocator. +* [axconfig](../modules/axconfig): Platform-specific constants and parameters for ArceOS. +* [axdisplay](../modules/axdisplay): ArceOS graphics module. +* [axdriver](../modules/axdriver): ArceOS device drivers. +* [axfs](../modules/axfs): ArceOS filesystem module. +* [axhal](../modules/axhal): ArceOS hardware abstraction layer, provides unified APIs for platform-specific operations. +* [axlog](../modules/axlog): Macros for multi-level formatted logging used by ArceOS. +* [axnet](../modules/axnet): ArceOS network module. +* [axruntime](../modules/axruntime): Runtime library of ArceOS. +* [axsync](../modules/axsync): ArceOS synchronization primitives. +* [axtask](../modules/axtask): ArceOS task management module. + +## Crates + +* [allocator](../crates/allocator): Various allocator algorithms in a unified interface. +* [arm_gic](../crates/arm_gic): ARM Generic Interrupt Controller (GIC) register definitions and basic operations. +* [axerrno](../crates/axerrno): Error code definition used by ArceOS. +* [axfs_devfs](../crates/axfs_devfs): Device filesystem used by ArceOS. +* [axfs_vfs](../crates/axfs_vfs): Virtual filesystem interfaces used by ArceOS. +* [axio](../crates/axio): `std::io`-like I/O traits for `no_std` environment. +* [capability](../crates/capability): Provide basic capability-based security. +* [crate_interface](../crates/crate_interface): Provides a way to define an interface (trait) in a crate, but can implement or use it in any crate. [![Crates.io](https://img.shields.io/crates/v/crate_interface)](https://crates.io/crates/crate_interface) +* [driver_block](../crates/driver_block): Common traits and types for block storage drivers. +* [driver_common](../crates/driver_common): Device driver interfaces used by ArceOS. +* [driver_display](../crates/driver_display): Common traits and types for graphics device drivers. +* [driver_net](../crates/driver_net): Common traits and types for network device (NIC) drivers. +* [driver_pci](../crates/driver_pci): Structures and functions for PCI bus operations. +* [driver_virtio](../crates/driver_virtio): Wrappers of some devices in the `virtio-drivers` crate, that implement traits in the `driver_common` series crates. +* [flatten_objects](../crates/flatten_objects): A container that stores numbered objects. Each object can be assigned with a unique ID. +* [handler_table](../crates/handler_table): A lock-free table of event handlers. [![Crates.io](https://img.shields.io/crates/v/handler_table)](https://crates.io/crates/handler_table) +* [kernel_guard](../crates/kernel_guard): RAII wrappers to create a critical section with local IRQs or preemption disabled. [![Crates.io](https://img.shields.io/crates/v/kernel_guard)](https://crates.io/crates/kernel_guard) +* [lazy_init](../crates/lazy_init): A wrapper for lazy initialized values without concurrency safety but more efficient. +* [linked_list](../crates/linked_list): Linked lists that supports arbitrary removal in constant time. +* [memory_addr](../crates/memory_addr): Wrappers and helper functions for physical and virtual addresses. [![Crates.io](https://img.shields.io/crates/v/memory_addr)](https://crates.io/crates/memory_addr) +* [page_table](../crates/page_table): Generic page table structures for various hardware architectures. +* [page_table_entry](../crates/page_table_entry): Page table entry definition for various hardware architectures. +* [percpu](../crates/percpu): Define and access per-CPU data structures. +* [percpu_macros](../crates/percpu_macros): Macros to define and access a per-CPU data structure. +* [ratio](../crates/ratio): The type of ratios and related operations. +* [scheduler](../crates/scheduler): Various scheduler algorithms in a unified interface. +* [slab_allocator](../crates/slab_allocator): Slab allocator for `no_std` systems. Uses multiple slabs with blocks of different sizes and a linked list for blocks larger than 4096 bytes. +* [spinlock](../crates/spinlock): `no_std` spin lock implementation that can disable kernel local IRQs or preemption while locking. +* [timer_list](../crates/timer_list): A list of timed events that will be triggered sequentially when the timer expires. +* [tuple_for_each](../crates/tuple_for_each): Provides macros and methods to iterate over the fields of a tuple struct. + +## Applications (Rust) + +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [helloworld](../apps/helloworld/) | | | A minimal app that just prints a string | +| [exception](../apps/exception/) | | paging | Exception handling test | +| [memtest](../apps/memtest/) | axalloc | alloc, paging | Dynamic memory allocation test | +| [display](../apps/display/) | axalloc, axdisplay | alloc, paging, display | Graphic/GUI test | +| [yield](../apps/task/yield/) | axalloc, axtask | alloc, paging, multitask, sched_fifo | Multi-threaded yielding test | +| [parallel](../apps/task/parallel/) | axalloc, axtask | alloc, paging, multitask, sched_fifo, irq | Parallel computing test (to test synchronization & mutex) | +| [sleep](../apps/task/sleep/) | axalloc, axtask | alloc, paging, multitask, sched_fifo, irq | Thread sleeping test | +| [priority](../apps/task/priority/) | axalloc, axtask | alloc, paging, multitask, sched_cfs | Thread priority test | +| [shell](../apps/fs/shell/) | axalloc, axdriver, axfs | alloc, paging, fs | A simple shell that responds to filesystem operations | +| [httpclient](../apps/net/httpclient/) | axalloc, axdriver, axnet | alloc, paging, net | A simple client that sends an HTTP request and then prints the response | +| [echoserver](../apps/net/echoserver/) | axalloc, axdriver, axnet, axtask | alloc, paging, net, multitask | A multi-threaded TCP server that reverses messages sent by the client | +| [httpserver](../apps/net/httpserver/) | axalloc, axdriver, axnet, axtask | alloc, paging, net, multitask | A multi-threaded HTTP server that serves a static web page | +| [udpserver](../apps/net/udpserver/) | axalloc, axdriver, axnet | alloc, paging, net | A simple echo server using UDP protocol | + +## Applications (C) +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [helloworld](../apps/c/helloworld/) | | | A minimal C app that just prints a string | +| [memtest](../apps/c/memtest/) | axalloc | alloc, paging | Dynamic memory allocation test in C | +| [sqlite3](../apps/c/sqlite3/) | axalloc, axdriver, axfs | alloc, paging, fp_simd, fs | Porting of [SQLite3](https://sqlite.org/index.html) | +| [iperf](../apps/c/iperf/) | axalloc, axdriver, axfs, axnet | alloc, paging, fp_simd, fs, net, select | Porting of [iPerf3](https://iperf.fr/) | +| [redis](../apps/c/redis/) | axalloc, axdriver, axtask, axfs, axnet | alloc, paging, fp_simd, irq, multitask, fs, net, pipe, epoll | Porting of [Redis](https://redis.io/) | + +## Dependencies + +```mermaid +graph TD; +subgraph "User Apps" +A["Rust App"] +C["C App"] +end +subgraph "ArceOS ulib" +B["rust_libax"] +D["c_libax"] +E("rust_std") +F("c_musl") +end +A --> B; +C --> D; +A --> E; +D --> B; +E --> B; +E --> F; +C --> F; +subgraph "System compatible layer" +G["aeceos"] +H("linux") +end +B --> G; +F --> H; +subgraph "ArceOS modules" +I[axruntime] +J[axlog] +K[axsync] +L[axhal] +M[axconfig] +N[axalloc] +O[axtask] +P[axdriver] +Q[axnet] +Q1[axdisplay] +M1[axfs] +end +G --> I; +H --> I; +I --> IN6; +I --> IN3; +I --> IN9; +I --> IN4; +I --> IN15; +I --> N; +I --> M; +I --> P; +I --> L; +I --> J; +I --> Q; +I --> Q1; +I --> O; +Q1 --> P; +Q1 --> IN4; +Q1 --> K; +Q1 --> T5; +P --> IN11; +P --> T0; +P --> T1; +P --> T2; +P --> T3; +P --> T5; +P --> N; +P --> L; +P --> M; +N --> IN9; +N --> IN5; +N --> R; +N --> IN13; +L --> M; +L --> N; +L --> IN3; +L --> IN9; +L --> IN8; +L --> IN4; +L --> P1; +L --> P11; +L --> IN6; +L --> IN5; +L --> IN2; +L --> IN15; +J --> IN9; +J --> IN15; +Q --> T0; +Q --> T2; +Q --> IN4; +Q --> IN13; +Q --> L; +Q --> K; +Q --> O; +Q --> P; +K --> IN9; +K --> O; +O --> L; +O --> M; +O --> IN6; +O --> IN9; +O --> IN4; +O --> IN5; +O --> S; +O --> IN10; +O --> IN3; +O --> IN15; +M1 --> IN4; +M1 --> F3; +M1 --> T0; +M1 --> T1; +M1 --> IN14; +M1 --> IN13; +M1 --> F1; +M1 --> F2; +M1 --> P; +M1 --> K; +subgraph "ArceOS crates" +R[allocator] +IN12[arm_gic] +IN13[axerrno] +F1[axfs_devfs] +F2[axfs_vfs] +IN14[axio] +F3[capability] +IN15[crate_interface] +T1[driver_blk] +T0[driver_common] +T5[driver_display] +T2[driver_net] +T3[driver_virtio] +IN2[handler_table] +IN3[kernel_guard] +IN4[lazy_init] +Z[linked_list] +IN5[memory_addr] +P1[page_table] +P11[page_table_entry] +IN6[percpu] +IN7[percpu_macros] +IN8[ratio] +S[scheduler-FIFO_RR] +IN1[slab_allocator] +IN9[spinlock] +IN10[timer_list] +IN11[tuple_for_each] +T4(e1000) +V(smoltcp) +W(lwip_rust) +OUT2(bitmap-allocator) +Y(slab_allocator) +S1(FIFO) +S2(RR) +OUT1(buddy_system_allocator) +OUT3(virtio-drivers) +end +R --> OUT1; +R --> OUT2; +R --> Y; +IN4 --> IN13; +S --> Z; +OUT1 --> Z; +OUT2 --> Z; +IN1 --> OUT1; +Y --> Z; +T3 --> T1; +T3 --> T2; +T3 --> T5; +T3 --> OUT3; +T3 --> T0; +T1 --> T0; +T2 --> T0; +T4 --> T0; +T5 --> T0; +IN3 --> IN15; +P1 --> IN5; +P1 --> P11; +P11 --> IN5; +IN6 --> IN3; +IN6 --> IN7; +IN9 --> IN3; +F3 --> IN13; +F2 --> IN13; +F1 --> F2; +``` diff --git a/doc/apps_display.md b/doc/apps_display.md new file mode 100644 index 000000000..c4f04e980 --- /dev/null +++ b/doc/apps_display.md @@ -0,0 +1,106 @@ +# INTRODUCTION +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [display](../apps/display/) | embedded-graphics, axdisplay, axdriver | alloc, paging, display | Display some graphics in a new window | + +# RUN + +```bash +make A=apps/display GRAPHIC=y LOG=debug run +``` + +# RESULT + +```text +... +[ 0.408067 axdriver:59] Initialize device drivers... +[ 0.410941 driver_virtio:50] Detected virtio MMIO device with vendor id: 0x554D4551, device type: GPU, version: Legacy +[ 0.414473 virtio_drivers::device::gpu:47] Device features EDID | NOTIFY_ON_EMPTY | ANY_LAYOUT | RING_INDIRECT_DESC | RING_EVENT_IDX +[ 0.418886 virtio_drivers::device::gpu:57] events_read: 0x0, num_scanouts: 0x1 +[ 0.423408 virtio_drivers::device::gpu:102] => RespDisplayInfo { header: CtrlHeader { hdr_type: Command(4353), flags: 0, fence_id: 0, ctx_id: 0, _padding: 0 }, rect: Rect { x: 0, y: 0, width: 1280, height: 800 }, enabled: 1, flags: 0 } +[ 0.452037 axdriver::virtio:88] created a new Display device: "virtio-gpu" +[ 0.455473 axdisplay:17] Initialize Display subsystem... +[ 0.458124 axdisplay:19] number of Displays: 1 +... +... +... +(never end) +``` +![display](figures/display.png) + +# STEPS + +## step1 + +``` rust +let mut board = DrawingBoard::new(); +board.disp.clear(Rgb888::BLACK).unwrap(); +``` + +**flow chart** +```mermaid +graph TD; + A["DrawingBoard::new()"] --> B["display::Display::new()"]; + A --> C["embedded_graphics::prelude::Point::new(INIT_X, INIT_Y)"]; + B --> D["libax::display::framebuffer_info"] + B --> E["core::slice::from_raw_parts_mut"] + B --> F["embedded_graphics::prelude::Size::new"] + D --> G["axsync::Mutex(axdriver::DisplayDevices)::lock"] + D --> H["axdriver::DisplayDevices::info"] +``` + +## step2 +``` rust +for _ in 0..5 { + board.latest_pos.x += RECT_SIZE as i32 + 20; + board.paint(); + framebuffer_flush(); + } +... +impl DrawingBoard { + ... + fn paint(&mut self) { + Rectangle::with_center(self.latest_pos, Size::new(RECT_SIZE, RECT_SIZE)) + .into_styled(PrimitiveStyle::with_stroke(Rgb888::RED, 10)) + .draw(&mut self.disp) + .ok(); + Circle::new(self.latest_pos + Point::new(-70, -300), 150) + .into_styled(PrimitiveStyle::with_fill(Rgb888::BLUE)) + .draw(&mut self.disp) + .ok(); + Triangle::new( + self.latest_pos + Point::new(0, 150), + self.latest_pos + Point::new(80, 200), + self.latest_pos + Point::new(-120, 300), + ) + .into_styled(PrimitiveStyle::with_stroke(Rgb888::GREEN, 10)) + .draw(&mut self.disp) + .ok(); + let text = "ArceOS"; + Text::with_alignment( + text, + self.latest_pos + Point::new(0, 300), + MonoTextStyle::new(&FONT_10X20, Rgb888::YELLOW), + Alignment::Center, + ) + .draw(&mut self.disp) + .ok(); + } +} +``` + +**flow chart** +```mermaid +graph TD; + A["DrawingBoard::paint"] --> B["embedded_graphics::primitives::{Circle, PrimitiveStyle, Rectangle, Triangle}"]; + A --> C["embedded_graphics::text::{Alignment, Text}"] + B --> D["impl embedded_graphics::draw_target::DrawTarget, embedded_graphics::prelude::OriginDimensions for Display"] + C --> D +``` + +## step3 +``` rust +loop { + core::hint::spin_loop(); +} +``` diff --git a/doc/apps_echoserver.md b/doc/apps_echoserver.md new file mode 100644 index 000000000..10b327a6b --- /dev/null +++ b/doc/apps_echoserver.md @@ -0,0 +1,106 @@ +# INTRODUCTION + +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [echoserver](../apps/net/echoserver/) | axalloc, axdriver, axnet, axtask | alloc, paging, net, multitask | A multi-threaded TCP server that reverses messages sent by the client | + +# RUN + +```console +$ make A=apps/net/echoserver NET=y run +... +Hello, echo server! +listen on: 10.0.2.15:5555 +``` + +In another shell, use `telnet` (or `nc`) to connect to localhost (`127.0.0.1`) to view the reversed echo message: + +```console +$ telnet localhost 5555 +Trying 127.0.0.1... +Connected to localhost. +Escape character is '^]'. +hello +olleh +12345 +54321 +``` + +```console +$ nc 127.0.0.1 5555 +hello +olleh +12345 +54321 +``` + +# STEPS + +## step1 + +[init](./init.md) + +After executed all initial actions, then arceos calls `main` function in `echoserver` app. + +## step2 + +`main` calls `accept_loop()`, which will keep processing incoming tcp connection. + +```rust +let (addr, port) = (IpAddr::from_str(LOCAL_IP).unwrap(), LOCAL_PORT); +let mut listener = TcpListener::bind((addr, port).into())?; +println!("listen on: {}", listener.local_addr().unwrap()); + +let mut i = 0; +loop { + match listener.accept() { + ... + } + Err(e) => return Err(e), + } + i += 1; +} +``` + +## step3 + +Once it receives a tcp connection. It will get a `(stream, addr)` pair from `libax::net`. +`main` task will spawn a task to reverse every package it receives. + +```rust +Ok((stream, addr)) => { + info!("new client {}: {}", i, addr); + task::spawn(move || match echo_server(stream) { + Err(e) => error!("client connection error: {:?}", e), + Ok(()) => info!("client {} closed successfully", i), + }); +} +``` + +## step4 + +Reverse bytes in package it receives. + +```rust +fn reverse(buf: &[u8]) -> Vec { + let mut lines = buf + .split(|&b| b == b'\n') + .map(Vec::from) + .collect::>(); + for line in lines.iter_mut() { + line.reverse(); + } + lines.join(&b'\n') +} + +fn echo_server(mut stream: TcpStream) -> io::Result { + let mut buf = [0u8; 1024]; + loop { + let n = stream.read(&mut buf)?; + if n == 0 { + return Ok(()); + } + stream.write_all(reverse(&buf[..n]).as_slice())?; + } +} +``` diff --git a/doc/apps_exception.md b/doc/apps_exception.md new file mode 100644 index 000000000..a9f59e321 --- /dev/null +++ b/doc/apps_exception.md @@ -0,0 +1,66 @@ +# INTRODUCTION + +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [exception](../apps/exception/) | | paging | Exception handling test | + +# RUN + +```console +$ make A=apps/exception LOG=debug run +... +Running exception tests... +[ 0.249873 0 axhal::arch::riscv::trap:13] Exception(Breakpoint) @ 0xffffffc0802001e8 +Exception tests run OK! +[ 0.068358 0 axtask::api:6] main task exited: exit_code=0 +[ 0.069128 0 axhal::platform::qemu_virt_riscv::misc:2] Shutting down... +``` + +# STEPS + +## step1 + +[init](./init.md) + +After executed all initial actions, then arceos calls `main` function in `exception` app. + +## step2 + +``` Rust +fn raise_break_exception() { + unsafe { + #[cfg(target_arch = "x86_64")] + asm!("int3"); + #[cfg(target_arch = "aarch64")] + asm!("brk #0"); + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + asm!("ebreak"); + } +} + +#[no_mangle] +fn main() { + println!("Running exception tests..."); + raise_break_exception(); + println!("Exception tests run OK!"); +} +``` + +**flow chart** + +```mermaid +graph TD; + A[" asm!(ebreak)"] --> B["raise exception"]; + B --> C["axhal::arch::riscv::trap.S::trap_vector_base"]; + C --> D["switch sscratch and sp"]; + C -- "from U mode" --> E["Ltrap_entry_u: SAVE_REGS 1; a1 <-- 1"]; + C -- "from S mode" --> F["Ltrap_entry_s: SAVE_REGS 0; a1 <-- 0"]; + E --> G[axhal::arch::riscv::trap::riscv_trap_handler]; + F --> G; + G -- "Trap::Exception(E::Breakpoint)" --> H["handle_breakpoint(&mut tf.sepc)"]; + H --> I["debug!(Exception(Breakpoint) @ {:#x} , sepc);*sepc += 2;"]; + I -- "from U mode" --> J["Ltrap_entry_u: RESTORE_REGS 1"]; + I -- "from S mode" --> K["Ltrap_entry_s: RESTORE_REGS 0"]; + J --> L[sret]; + K --> L; +``` diff --git a/doc/apps_fs_shell.md b/doc/apps_fs_shell.md new file mode 100644 index 000000000..251bec1b2 --- /dev/null +++ b/doc/apps_fs_shell.md @@ -0,0 +1,310 @@ +# INTRODUCTION + +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [shell](../apps/fs/shell/) | axalloc, axdriver, axfs | alloc, paging, fs | A simple shell that responds to filesystem operations | + +# RUN + +Before running the app, make an image of FAT32: + +```shell +make disk_img +``` + +Run the app: + +```shell +make A=apps/fs/shell ARCH=aarch64 LOG=debug BLK=y run +``` + +# RESULT + +``` +... +[ 0.006204 0 axdriver:64] Initialize device drivers... +[ 0.006396 0 driver_virtio:50] Detected virtio MMIO device with vendor id: 0x554D4551, device type: Block, version: Legacy +[ 0.006614 0 virtio_drivers::device::blk:55] device features: SEG_MAX | GEOMETRY | BLK_SIZE | SCSI | FLUSH | TOPOLOGY | CONFIG_WCE | DISCARD | WRITE_ZEROES | NOTIFY_ON_EMPTY | RING_INDIRECT_DESC | RING_EVENT_IDX +[ 0.007094 0 virtio_drivers::device::blk:64] config: 0xffff00000a003f00 +[ 0.007270 0 virtio_drivers::device::blk:69] found a block device of size 34000KB +[ 0.007956 0 axdriver::virtio:88] created a new Block device: "virtio-blk" +[ 0.008488 0 axfs:25] Initialize filesystems... +[ 0.008584 0 axfs:26] use block device: "virtio-blk" +[ 0.025432 0 axalloc:57] expand heap memory: [0xffff00004012f000, 0xffff00004013f000) +[ 0.025680 0 axalloc:57] expand heap memory: [0xffff00004013f000, 0xffff00004015f000) +[ 0.026510 0 axfs::fs::fatfs:122] create Dir at fatfs: /dev +[ 0.043112 0 axfs::fs::fatfs:102] lookup at fatfs: /dev +[ 0.049562 0 fatfs::dir:140] Is a directory +[ 0.057550 0 axruntime:137] Initialize interrupt handlers... +[ 0.057870 0 axruntime:143] Primary CPU 0 init OK. +Available commands: + cat + cd + echo + exit + help + ls + mkdir + pwd + rm + uname +arceos:/$ +``` + +# STEPS + +## Step1 + +[init](./init.md) + +After executed all initial actions, then arceos calls `main` function in `shell` app. + +## Step2 + +The program reads one command from `stdin` each time and pass it to `cmd::run_cmd`. + +```rust +fn main() { + let mut stdin = libax::io::stdin(); + let mut stdout = libax::io::stdout(); + + let mut buf = [0; MAX_CMD_LEN]; + let mut cursor = 0; + cmd::run_cmd("help".as_bytes()); + print_prompt(); + + loop { + if stdin.read(&mut buf[cursor..cursor + 1]).ok() != Some(1) { + continue; + } + if buf[cursor] == b'\x1b' { + buf[cursor] = b'^'; + } + match buf[cursor] { + CR | LF => { + println!(); + if cursor > 0 { + cmd::run_cmd(&buf[..cursor]); + cursor = 0; + } + print_prompt(); + } + BS | DL => { + if cursor > 0 { + stdout.write(&[BS, SPACE, BS]).unwrap(); + cursor -= 1; + } + } + 0..=31 => {} + c => { + if cursor < MAX_CMD_LEN - 1 { + stdout.write(&[c]).unwrap(); + cursor += 1; + } + } + } + } +} +``` + +## Step3 + +The commands are parsed and executed in `cmd::run_cmd`. + +```rust +pub fn run_cmd(line: &[u8]) { + let line_str = unsafe { core::str::from_utf8_unchecked(line) }; + let (cmd, args) = split_whitespace(line_str); + if !cmd.is_empty() { + for (name, func) in CMD_TABLE { + if cmd == *name { + func(args); + return; + } + } + println!("{}: command not found", cmd); + } +} +``` + +**flowchart** + +```mermaid +graph TD + run_cmd["cmd::run_cmd"] + + cat["cmd::do_cat"] + cd["cmd:do_cd"] + echo["cmd::do_echo"] + exit["cmd::do_exit"] + help["cmd::do_help"] + ls["cmd::do_ls"] + mkdir["cmd::do_mkdir"] + pwd["cmd::do_pwd"] + rm["cmd::do_rm"] + uname["cmd::do_uname"] + + run_cmd --> cat + run_cmd --> cd + run_cmd --> echo + run_cmd --> exit + run_cmd --> help + run_cmd --> ls + run_cmd --> mkdir + run_cmd --> pwd + run_cmd --> rm + run_cmd --> uname + + stdout_w["libax::io::stdout().write()"] + fopen["libax::fs::File::open"] + fread["libax::fs::file::File::read"] + fcreate["libax::fs::File::create"] + fwrite["libax::fs::file::File::write"] + fs_meta[libax::fs::metadata] + fs_readdir[libax::fs::read_dir] + fs_createdir[libax::fs::create_dir] + fs_rmdir[libax::fs::remove_dir] + fs_rmfile[libax::fs::remove_file] + + cat --> fopen + cat --> fread + cat --> stdout_w + cd --> set_dir["libax::env::set_current_dir"] + echo --> fcreate + echo --> fwrite + exit --> lib_exit["libax::task::exit"] + ls --> get_dir["libax::env::current_dir"] + ls --> fs_meta + ls --> fs_readdir + mkdir --> fs_createdir + pwd --> get_dir + rm --> fs_meta + rm --> fs_rmdir + rm --> fs_rmfile +``` + +For the details of the file system APIs included in the chart, see the section below. + +# File system APIs + +> **Notes for the flow charts below**: normal lines denote the calling stack, while dashed lines denote the returning of results. + +### Create files, open files, and get metadata + +```mermaid +graph TD + lib_create[libax::fs::File::create] --> |WRITE/CREATE/TRUNCATE| open_opt + lib_open[libax::fs::File::open] --> |READ ONLY| open_opt + lib_meta[libax::fs::metadata] --> lib_open1[libax::fs::File::open] --> |READ ONLY| open_opt + lib_meta --> f_meta[axfs::fops::File::get_attr] --> vfs_getattr + lib_meta -..-> |not found/permission denied| err(Return error) + open_opt["axfs::api::file::OpenOptions::open"] --> fops_open + + fops_open["axfs::fops::File::open"] --> fops_openat + fops_openat["axfs::fops::File::_open_at"] + fops_openat --> lookup + fops_openat --> |w/ CREATE flag| create_file + fops_openat --> vfs_getattr + + lookup["axfs::root::lookup"] --> vfs_lookup + create_file["axfs::root::create_file"] + create_file --> vfs_lookup + create_file --> vfs_create + create_file --> vfs_truncate + create_file --> vfs_open + + vfs_lookup["axfs_vfs::VfsNodeOps::lookup"] --> fs_impl + vfs_create["axfs_vfs::VfsNodeOps::create"] --> fs_impl + vfs_getattr["axfs_vfs::VfsNodeOps::get_attr"] --> fs_impl + vfs_truncate[axfs_vfs::VfsNodeOps::truncate] --> fs_impl + vfs_open[axfs_vfs::VfsNodeOps::open] --> fs_impl + fs_impl[[FS implementation]] +``` + + + +### Create directories + +```mermaid +graph TD + lib_mkdir[libax::fs::create_dir] --> builder_create["axfs::api::DirBuilder::create"] + builder_create --> root_create[axfs::root::create_dir] --> lookup[axfs::root::lookup] + lookup -..-> |exists/other error| err(Return error) + root_create -->|type=VfsNodeType::Dir| node_create[axfs_vfs::VfsNodeOps::create] --> fs_impl[[FS implementation]] + + lookup --> vfs_lookup["axfs_vfs::VfsNodeOps::lookup"] --> fs_impl[[FS implementation]] +``` + + + +### Read and write files + +```mermaid +graph LR + lib_read[libax::fs::File::read] --> fops_read + fops_read[axfs::fops::File::read] ---> |w/ read permission| vfs_read_at + vfs_read_at[axfs_vfs::VfsNodeOps::read] --> fs_impl[[FS implementation]] + + lib_write[libax::fs::File::write] --> fops_write + fops_write[axfs::fops::File::write] ---> |w/ write permission| vfs_write_at + vfs_write_at[axfs_vfs::VfsNodeOps::write] --> fs_impl[[FS implementation]] + + fops_read -.-> |else| err1(Return error) + fops_write -.-> |else| err(Return error) +``` + +### Get current directory + +```mermaid +graph LR + lib_gwd[libax::fs::current_dir] --> root_gwd[axfs::root::current_dir] + lib_gwd -.-> return("Return path") +``` + +### Set current directory + +```mermaid +graph TD + lib_cd[libax::fs::set_current_dir] --> root_cd[axfs::root::set_current_dir] + + root_cd -..-> |is root| change[Set CURRENT_DIR and CURRENT_DIR_PATH] + root_cd --> |else| lookup["axfs::root::lookup"] + + vfs_lookup["axfs_vfs::VfsNodeOps::lookup"] + lookup --> vfs_lookup --> fs_impl[[FS implementation]] + lookup -..-> |found & is directory & has permission| change + +``` + +### Remove directory + +```mermaid +graph TD + lib_rmdir[libax::fs::remove_dir] --> root_rmdir[axfs::root::remove_dir] + + root_rmdir -.-> |empty/is root/invalid/permission denied| ret_err(Return error) + + root_rmdir --> lookup[axfs::root::lookup] --> vfs_lookup["axfs_vfs::VfsNodeOps::lookup"] ---> fs_impl[[FS implementation]] + lookup -...-> |not found| ret_err + + root_rmdir --> meta[axfs_vfs::VfsNodeOps::get_attr] --> fs_impl + meta -..-> |not a dir/permission denied| ret_err + + root_rmdir --> remove_[axfs_vfs::VfsNodeOps::remove] ---> fs_impl + +``` + +### Remove file + +```mermaid +graph TD + lib_rm[libax::fs::remove_file] --> root_rm[axfs::root::remove_file] + + root_rm --> lookup[axfs::root::lookup] --> vfs_lookup["axfs_vfs::VfsNodeOps::lookup"] + ---> fs_impl[[FS implementation]] + lookup -.-> |not found| ret_err + root_rm ---> meta[axfs_vfs::VfsNodeOps::get_attr] ---> fs_impl + meta -..-> |not a file/permission denied| ret_err(Return error) + root_rm --> remove_[axfs_vfs::VfsNodeOps::remove] ---> fs_impl + +``` diff --git a/doc/apps_helloworld.md b/doc/apps_helloworld.md new file mode 100644 index 000000000..502ccfd64 --- /dev/null +++ b/doc/apps_helloworld.md @@ -0,0 +1,59 @@ +# INTRODUCTION +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [helloworld](../apps/helloworld/) | | | A minimal app that just prints a string | + +# RUN + +```shell +make A=apps/helloworld SMP=4 LOG=debug run +``` + +# STEPS + +## step1 +[init](./init.md) + +After executed all initial actions, then arceos calls `main` function in `helloworld` app. + +## step2 + +```Rust +fn main() { + libax::println!("Hello, world!"); +} +``` + +**flow chart** + +```mermaid +graph TD; + A[main] --> B["libax::println!(Hello, world!)"]; + B --> C[libax:io::__print_impl]; + C --> D[INLINE_LOCK=Mutex::new]; + C --> _guard=INLINE_LOCK.lock; + C --> E["stdout().write_fmt(args)"]; +``` + +### step2.1 + +```mermaid +graph TD; + T["stdout()"] --> A["libax::io::stdio.rs::stdout()"]; + A --> B["INSTANCE: Mutex = Mutex::new(StdoutRaw)"]; + A --> C["return Stdout { inner: &INSTANCE }"]; +``` + +### step2.2 + +```mermaid +graph TD; + T["stdout().write_fmt(args)"] --> A["Stdout::write"]; + A --> B["self.inner.lock().write(buf)"]; + B --> C["StdoutRaw::write"]; + C --> D["axhal::console::write_bytes(buf);"]; + C --> E["Ok(buf.len())"]; + D --> F["putchar"]; + F --> G["axhal::platform::qemu_virt_riscv::console::putchar"]; + G --> H["sbi_rt::legacy::console_putchar"]; +``` diff --git a/doc/apps_httpserver.md b/doc/apps_httpserver.md new file mode 100644 index 000000000..8a2841bdc --- /dev/null +++ b/doc/apps_httpserver.md @@ -0,0 +1,177 @@ +# INTRODUCTION +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [httpserver](../apps/net/httpserver) | axalloc, axnet, axdriver, axtask | alloc, paging, net, multitask | A multi-threaded HTTP server that serves a static web page | + +# RUN + +```shell +make A=apps/net/httpserver SMP=4 NET=y LOG=info run +``` + +# RESULT + +``` +... +[ 0.101078 axtask:75] use FIFO scheduler. +[ 0.102733 axdriver:59] Initialize device drivers... +[ 0.104475 driver_virtio:50] Detected virtio MMIO device with vendor id: 0x554D4551, device type: Network, version: Legacy +[ 0.107095 virtio_drivers::device::net:117] Device features CTRL_GUEST_OFFLOADS | MAC | GSO | MRG_RXBUF | STATUS | CTRL_VQ | CTRL_RX | CTRL_VLAN | CTRL_RX_EXTRA | GUEST_ANNOUNCE | CTL_MAC_ADDR | RING_INDIRECT_DESC | RING_EVENT_IDX +[ 0.113234 virtio_drivers::device::net:127] Got MAC=[52, 54, 00, 12, 34, 56], status=LINK_UP +[ 0.116326 axdriver::virtio:88] created a new Net device: "virtio-net" +[ 0.118063 axnet:22] Initialize network subsystem... +[ 0.119247 axnet:24] number of NICs: 1 +[ 0.120442 axnet:27] NIC 0: "virtio-net" +[ 0.121819 axalloc:57] expand heap memory: [0xffffffc080654000, 0xffffffc080a54000) +[ 0.124142 axalloc:57] expand heap memory: [0xffffffc080a54000, 0xffffffc081254000) +[ 0.127314 axnet::smoltcp_impl:273] created net interface "eth0": +[ 0.128951 axnet::smoltcp_impl:275] ether: 52-54-00-12-34-56 +[ 0.130706 axnet::smoltcp_impl:277] ip: 10.0.2.15/24 +[ 0.132189 axnet::smoltcp_impl:278] gateway: 10.0.2.2 +[ 0.133746 axruntime:134] Initialize interrupt handlers... +... +Hello, ArceOS HTTP server! +... +[ 0.148419 0:2 axnet::smoltcp_impl:67] socket #0: created +[ 0.148850 0:2 axnet::smoltcp_impl::tcp:111] socket listening on 10.0.2.15:5555 +[ 0.149305 0:2 axnet::smoltcp_impl:95] socket #0: destroyed +listen on: http://10.0.2.15:5555/ +``` + +Open http://127.0.0.1:5555/ in your browser, or use command lines in another shell to view the web page: + +```console +$ curl http://127.0.0.1:5555/ + + + Hello, ArceOS + + + +
+
+ Powered by ArceOS example HTTP server v0.1.0 +
+ + +``` + +# STEPS + +## step 1 +[init](./init.md) + +After executed all initial actions, then arceos calls `main` function in `memtest` app. + +## step 2 +```Rust +fn http_server(mut stream: TcpStream) -> io::Result { + ... + stream.read(&mut buf)?; + ... + stream.write_all(reponse.as_bytes())?; + ... +} + +fn accept_loop() -> io::Result { + let (addr, port) = (IpAddr::from_str(LOCAL_IP).unwrap(), LOCAL_PORT); + let mut listener = TcpListener::bind((addr, port).into())?; + ... + loop { + match listener.accept() { + Ok((stream, addr)) => { + task::spawn(move || match http_server(stream) { + Err(e) => error!("client connection error: {:?}", e), + Ok(()) => info!("client {} closed successfully", i), + }); + } + Err(e) => return Err(e), + } + i += 1; + } +} + +#[no_mangle] +fn main() { + println!("Hello, ArceOS HTTP server!"); + accept_loop().expect("test HTTP server failed"); +} +``` + +### step 2.1 + +```Rust +let (addr, port) = (IpAddr::from_str(LOCAL_IP).unwrap(), LOCAL_PORT); +let mut listener = TcpListener::bind((addr, port).into())?; +``` + +**flow chart** + +```mermaid +graph TD; +T["libax::net::tcp::TcpStream::bind()"] +T-->A["smoltcp::wire::ip::IpAddr::from_str()"] +T-->B["axnet::smoltcp_impl::tcp::TcpSocket::new()"] +T-->C["axnet::smoltcp_impl::tcp::TcpSocket::bind()"] +T-->D["axnet::smoltcp_impl::tcp::TcpSocket::listen()"] +B-->E["axnet::smoltcp_impl::SocketSetWrapper::new_tcp_socket()"] +E-->F["axsync::mutex< smoltcp::iface::socket_set::SocketSet >"] +F-->G["managed::slice::ManagedSlice< smoltcp::iface::socket_set::SocketStorage >"] +G-->H["smoltcp::iface::socket_meta::Meta + smoltcp::socket::Socket"] +H-->I["SocketHandle + NeighborState"] +H-->J["Raw + Icmp + udp + tcp"] + +D-->K["ListenTable: Box< [Mutex< Option< Box< ListenTableEntry > > >] >(tcp)"] +K-->L["ListenTableEntry: VecDeque< SocketHandle >(syn_queue)"] +L-->M["Tcp is a multi-wrapped sync queue of SocketHandle"] +``` + +### step 2.2 + +```Rust +match listener.accept() { + Ok((stream, addr)) => { + ... + } + Err(e) => return Err(e), +} +``` + +**flow chart** + +```mermaid +graph TD; +T["libax::net::tcp::TcpListener::accept()"] +T-->A["axnet::smoltcp_impl::tcp::TcpSocket::accept()"] +A-->B["Mutex< SocketSet >.poll_interfaces()"] +B-->C["axnet::smoltcp_impl::InterfaceWrapper< axdriver::VirtIoNetDev >.poll"] +C-->Z["many things"] +A-->D["axnet::smoltcp_impl::listen_table::ListenTable::accept()"] +D-->E["check the sync queue"] + +``` + + + + + +### step 2.3 + +```Rust +stream.read(&mut buf)?; +stream.write_all(reponse.as_bytes())?; +``` + +**flow chart** + +```mermaid +graph TD; + A["impl Read, Write for libax::TcpStream"] --> B["libax::TcpStream::read"] + A["impl Read, Write for libax::TcpStream"] --> C["libax::TcpStream::write"] + B --> D["smoltcp_impl::TcpSocket::recv(buf)"] + C --> E["smoltcp_impl::TcpSocket::send(buf)"] + +``` + diff --git a/doc/apps_memtest.md b/doc/apps_memtest.md new file mode 100644 index 000000000..86be3bb5a --- /dev/null +++ b/doc/apps_memtest.md @@ -0,0 +1,55 @@ +# INTRODUCTION +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [memtest](../apps/memtest/) | axalloc | alloc, paging | Dynamic memory allocation test | + +# RUN + +```console +$ make A=apps/memtest SMP=4 LOG=info run +... +Running memory tests... +test_vec() OK! +test_btree_map() OK! +Memory tests run OK! +[ 0.523323 3 axhal::platform::qemu_virt_riscv::misc:2] Shutting down... +... +``` + +# STEPS + +## step1 + +[init](./init.md) + +After executed all initial actions, then arceos calls `main` function in `memtest` app. + +## step2 +```Rust +fn test_vec() { +... + let mut v = Vec::with_capacity(N); +... +} +fn test_btree_map() { +... + let mut m = BTreeMap::new(); +... +} +fn main() { + println!("Running memory tests..."); + test_vec(); + test_btree_map(); + println!("Memory tests run OK!"); +} +``` + +**flow chart** + +```mermaid +graph TD; +A["rust.alloc runtime"] --> B["impl GlobalAlloc for axalloc::GlobalAllocator alloc()"]; +B --> C["axalloc::GlobalAllocator::alloc()"]; +C --> D["simple two-level allocator: if no heap memory, allocate from the page allocator"]; + +``` diff --git a/doc/apps_net-httpclient.md b/doc/apps_net-httpclient.md new file mode 100644 index 000000000..3100406a2 --- /dev/null +++ b/doc/apps_net-httpclient.md @@ -0,0 +1,88 @@ +# INTRODUCTION +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [httpclient](../apps/net/httpclient/) | axalloc, axdriver, axnet | alloc, paging, net | A simple client that sends an HTTP request and then prints the response | + +# RUN +```bash +make A=apps/net/httpclient SMP=1 NET=y LOG=debug run +``` + +# RESULT +```text +... +[ 0.065494 0 axalloc:128] initialize global allocator at: [0xffffffc080286000, 0xffffffc088000000) +[ 0.068109 0 axruntime:115] Initialize kernel page table... +[ 0.070549 0 axdriver:59] Initialize device drivers... +[ 0.072131 0 driver_virtio:50] Detected virtio MMIO device with vendor id: 0x554D4551, device type: Network, version: Legacy +[ 0.074003 0 virtio_drivers::device::net:117] Device features CTRL_GUEST_OFFLOADS | MAC | GSO | MRG_RXBUF | STATUS | CTRL_VQ | CTRL_RX | CTRL_VLAN | CTRL_RX_EXTRA | GUEST_ANNOUNCE | CTL_MAC_ADDR | RING_INDIRECT_DESC | RING_EVENT_IDX +[ 0.077999 0 virtio_drivers::device::net:127] Got MAC=[52, 54, 00, 12, 34, 56], status=LINK_UP +[ 0.080748 0 axalloc:57] expand heap memory: [0xffffffc080298000, 0xffffffc0802a8000) +[ 0.082357 0 axalloc:57] expand heap memory: [0xffffffc0802a8000, 0xffffffc0802c8000) +[ 0.083769 0 axalloc:57] expand heap memory: [0xffffffc0802c8000, 0xffffffc080308000) +[ 0.085864 0 axdriver::virtio:88] created a new Net device: "virtio-net" +[ 0.087057 0 axnet:22] Initialize network subsystem... +[ 0.087859 0 axnet:24] number of NICs: 1 +[ 0.088517 0 axnet:27] NIC 0: "virtio-net" +[ 0.089360 0 axalloc:57] expand heap memory: [0xffffffc080308000, 0xffffffc080408000) +[ 0.092033 0 axnet::smoltcp_impl:273] created net interface "eth0": +[ 0.093315 0 axnet::smoltcp_impl:275] ether: 52-54-00-12-34-56 +[ 0.094667 0 axnet::smoltcp_impl:277] ip: 10.0.2.15/24 +[ 0.095947 0 axnet::smoltcp_impl:278] gateway: 10.0.2.2 +[ 0.097154 0 axruntime:134] Initialize interrupt handlers... +[ 0.098892 0 axruntime:140] Primary CPU 0 init OK. +Hello, simple http client! +[ 0.100960 0 axnet::smoltcp_impl:67] socket #0: created +[ 0.103106 0 smoltcp::iface::interface:1599] address 10.0.2.2 not in neighbor cache, sending ARP request +HTTP/1.1 200 OK +Server: nginx +Date: Sat, 08 Apr 2023 18:58:27 GMT +Content-Type: text/plain +Content-Length: 13 +Connection: keep-alive +Access-Control-Allow-Origin: * +Cache-Control: no-cache, no-store, must-revalidate + +183.172.74.58 +[ 0.585681 0 axnet::smoltcp_impl::tcp:148] socket #0: shutting down +[ 0.587517 0 axnet::smoltcp_impl:95] socket #0: destroyed +... +``` + +# STEPS + +## step1 +``` rust +let (addr, port) = (IpAddr::from_str(DEST_IP).unwrap(), 80); +let mut stream = TcpStream::connect((addr, port).into())?; +``` + +**flow chart** +```mermaid +graph TD; + A["libax::tcp::TcpStream::connect"] --> B["smoltcp::wire::IpEndpoint::From(addr, port)"] + A --> C["axnet::smoltcp_impl::TcpSocket::new"] + A --> D["axnet::smoltcp_impl::TcpSocket::connect(addr)"] + C --> E["axsync::Mutex(smoltcp::iface::SocketSet)::new"] +``` + +## step2 +``` rust +stream.write(REQUEST.as_bytes())?; + +let mut buf = [0; 1024]; +let n = stream.read(&mut buf)?; +let response = core::str::from_utf8(&buf[..n]).unwrap(); +println!("{}", response); +``` + +**flow chart** +```mermaid +graph TD; + A["impl Read, Write for libax::TcpStream"] --> B["libax::TcpStream::read"] + A["impl Read, Write for libax::TcpStream"] --> C["libax::TcpStream::write"] + B --> D["smoltcp_impl::TcpSocket::recv(buf)"] + C --> E["smoltcp_impl::TcpSocket::send(buf)"] + D --> F["libax::println"] + E --> F +``` diff --git a/doc/apps_parallel.md b/doc/apps_parallel.md new file mode 100644 index 000000000..7dde8d5c2 --- /dev/null +++ b/doc/apps_parallel.md @@ -0,0 +1,184 @@ +# INTRODUCTION + +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [parallel](../apps/task/parallel/) | axalloc, axtask | alloc, paging, multitask, sched_fifo | Parallel computing test (to test synchronization & mutex) | + +# RUN + +## Without preemption (FIFO scheduler) + +```shell +make A=apps/task/parallel LOG=info run +``` + +## With preemption (RR scheduler) + +```shell +make A=apps/task/parallel LOG=info APP_FEATURES=preempt run +``` + +## Using multicore + +```shell +make A=apps/task/parallel LOG=info SMP=4 run +``` + +# RESULT + +```console +$ make A=apps/task/parallel APP_FEATURES=preempt SMP=4 run +... +part 0: TaskId(7) [0, 125000) +part 3: TaskId(10) [375000, 500000) +part 1: TaskId(8) [125000, 250000) +part 2: TaskId(9) [250000, 375000) +part 4: TaskId(11) [500000, 625000) +part 5: TaskId(12) [625000, 750000) +part 6: TaskId(13) [750000, 875000) +part 7: TaskId(14) [875000, 1000000) +part 8: TaskId(15) [1000000, 1125000) +part 9: TaskId(16) [1125000, 1250000) +part 10: TaskId(17) [1250000, 1375000) +part 11: TaskId(18) [1375000, 1500000) +part 12: TaskId(19) [1500000, 1625000) +part 13: TaskId(20) [1625000, 1750000) +part 14: TaskId(21) [1750000, 1875000) +part 15: TaskId(22) [1875000, 2000000) +part 15: TaskId(22) finished +part 3: TaskId(10) finished +part 2: TaskId(9) finished +part 1: TaskId(8) finished +part 0: TaskId(7) finished +part 7: TaskId(14) finished +part 4: TaskId(11) finished +part 6: TaskId(13) finished +part 5: TaskId(12) finished +part 8: TaskId(15) finished +part 10: TaskId(17) finished +part 9: TaskId(16) finished +part 11: TaskId(18) finished +part 13: TaskId(20) finished +part 14: TaskId(21) finished +part 12: TaskId(19) finished +main task woken up! timeout=false +sum = 61783189038 +Parallel summation tests run OK! +[ 1.219708 3:2 axhal::platform::qemu_virt_aarch64::psci:25] Shutting down... +``` + +# PROCESS + +`main`使用`MAIN_WQ`睡眠 500ms,并检查`main`的唤醒是因为时间到(而非其他`task`的`notify()`)。 + +`main`调用`task::spawn`产生`NUM_TASKS`个`task`,分别进行计算。计算完毕后,使用一个`WaitQueue`(`static BARRIER_WQ`)以等待其他`task`的完成。 +在全部`task`完成后,执行`BARRIER_WQ.notify_all(true)`,继续各`task`的执行。 + +`main`在生成`task`后,调用`MAIN_WQ.wait_timeout()`等待 600ms,随后检查`task`的计算结果。 + +# FUNCTIONS + +## barrier + +`BARRIER_COUNT += 1`,记录已经完成计算的`task`数量。 + +`BARRIER_WQ.wait_until()`,block 至所有`task`均完成计算。 + +`BARRIER_WQ.notify_all()`,唤醒`BARRIER_WQ`内的所有 task 继续执行。 + +# STEPS + +## step1 + +[init](./init.md) + +After executed all initial actions, then arceos calls `main` function in `parallel` app. + +## step2 + +Calculate expected value from tasks. + +```rust +let vec = Arc::new( + (0..NUM_DATA) + .map(|_| rand::rand_u32() as u64) + .collect::>(), +); +let expect: u64 = vec.iter().map(sqrt).sum(); +``` + +## step3 + +Sleep `main` task in `MAIN_WQ` for 500ms. `main` **must** be timed out to wake up since there's no other task to `notify()` it. + +```rust +let timeout = MAIN_WQ.wait_timeout(Duration::from_millis(500)); +assert!(timeout); +``` + +## step4 + +`main` task spawn all `NUM_TASKS` tasks. + +```rust +for i in 0..NUM_TASKS { + let vec = vec.clone(); + task::spawn(move || { + ... + }); +} +``` + +Each task will do the calculation, then call `barrier()`. + +```rust +// task: +let left = i * (NUM_DATA / NUM_TASKS); +let right = (left + (NUM_DATA / NUM_TASKS)).min(NUM_DATA); +println!( + "part {}: {:?} [{}, {})", + i, + task::current().id(), + left, + right +); + +RESULTS.lock()[i] = vec[left..right].iter().map(sqrt).sum(); + +barrier(); + +println!("part {}: {:?} finished", i, task::current().id()); +let n = FINISHED_TASKS.fetch_add(1, Ordering::Relaxed); +if n == NUM_TASKS - 1 { + MAIN_WQ.notify_one(true); +} + +fn barrier() { + static BARRIER_WQ: WaitQueue = WaitQueue::new(); + static BARRIER_COUNT: AtomicUsize = AtomicUsize::new(0); + BARRIER_COUNT.fetch_add(1, Ordering::Relaxed); + BARRIER_WQ.wait_until(|| BARRIER_COUNT.load(Ordering::Relaxed) == NUM_TASKS); + BARRIER_WQ.notify_all(true); +} +``` + +`barrier()` will keep track of how many tasks have finished calculation in `BARRIER_COUNT`. + +Task will sleep in `BARRIER_WQ` until all tasks have finished. Then, the first awake task will `notify_all()` tasks to wake up. + +Task will print some info, add 1 to `FINISHED_TASKS`. The last task (`n == NUM_TASKS - 1`) will notify the `main` task to wake up. + +## step5 + +`main` will sleep 600ms in `MAIN_WQ` after spawning all the tasks. Once awake, `main` will check the actual calculation results. + +```rust +let timeout = MAIN_WQ.wait_timeout(Duration::from_millis(600)); +println!("main task woken up! timeout={}", timeout); + +let actual = RESULTS.lock().iter().sum(); +println!("sum = {}", actual); +assert_eq!(expect, actual); + +println!("Parallel summation tests run OK!"); +``` diff --git a/doc/apps_priority.md b/doc/apps_priority.md new file mode 100644 index 000000000..0b9238e63 --- /dev/null +++ b/doc/apps_priority.md @@ -0,0 +1,41 @@ +# INTRODUCTION + +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [priority](../apps/task/priority/) | axalloc, axtask | alloc, paging, multitask, sched_fifo, sched_rr, sched_cfs | test priority according to cfs::nice| + +# RUN +```shell +make A=apps/task/priority ARCH=riscv64 SMP=1 APP_FEATURES=sched_cfs run LOG=info +``` +Other choises of APP_FEATURES: sched_fifo, sched_rr +## Using multicore +```shell +make A=apps/task/sched-realtime ARCH=riscv64 SMP=4 APP_FEATURES=sched_cfs run LOG=info +``` +Other choises of APP_FEATURES: sched_fifo, sched_rr + +# RESULT +``` +make A=apps/task/priority ARCH=riscv64 SMP=1 APP_FEATURES=sched_cfs run LOG=info +... +part 0: TaskId(4) [0, 40) +part 1: TaskId(5) [0, 40) +part 2: TaskId(6) [0, 40) +part 3: TaskId(7) [0, 40) +part 4: TaskId(8) [0, 4) +part 3: TaskId(7) finished +part 4: TaskId(8) finished +part 2: TaskId(6) finished +part 1: TaskId(5) finished +part 0: TaskId(4) finished +sum = 3318102132 +leave time: +task 0 = 614ms +task 1 = 479ms +task 2 = 374ms +task 3 = 166ms +task 4 = 371ms +Priority tests run OK! +[ 1.274073 0:2 axhal::platform::qemu_virt_riscv::misc:3] Shutting down... +``` \ No newline at end of file diff --git a/doc/apps_sleep.md b/doc/apps_sleep.md new file mode 100644 index 000000000..02cd14600 --- /dev/null +++ b/doc/apps_sleep.md @@ -0,0 +1,122 @@ +# INTRODUCTION +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [sleep](../apps/task/sleep) |axalloc,axtask |alloc, paging, multitask, sched_fifo | Thread sleeping test | + +# RUN + +``` +make A=apps/task/sleep SMP=4 LOG=debug run +``` + +# RESULT +``` +... + +[ 0.077898 axruntime:109] Initialize global memory allocator... +[ 0.080584 axalloc:128] initialize global allocator at: [0xffffffc08033c000, 0xffffffc088000000) +[ 0.084562 axruntime:115] Initialize kernel page table... +[ 0.088103 axtask:69] Initialize scheduling... +[ 0.090130 axtask::task:113] new task: Task(1, "idle") +[ 0.091833 axalloc:57] expand heap memory: [0xffffffc08034a000, 0xffffffc08035a000) + +... + +[ 9.205714 1:2 axtask::run_queue:127] task sleep: Task(2, "main"), deadline=9.2151238s +[ 9.207086 1:6 axtask:144] idle task: waiting for IRQs... +[ 9.207282 2:1 axtask::run_queue:49] task yield: Task(1, "idle") +[ 9.208362 2:1 axtask:144] idle task: waiting for IRQs... +[ 9.209530 0:4 axtask::run_queue:114] task unblock: Task(11, "") +[ 9.210129 0:4 axtask::run_queue:49] task yield: Task(4, "idle") +[ 9.210775 0:11 arceos_sleep:40] task 3 actual sleep 4.0016563s seconds (1). +task 3 sleep 4 seconds (2) ... + +... + +Sleep tests run OK! +[ 16.228092 2:2 axtask::run_queue:80] task exit: Task(2, "main"), exit_code=0 +[ 16.228823 2:2 axhal::platform::qemu_virt_riscv::misc:2] Shutting down... +``` + +# STEPS + +## step1 +[init](./init.md) +After executed all initial actions, then arceos calls `main` function in `helloworld` app. + +## step2 +```Rust +fn main(){ +... +} +``` + +### step2.1 +```Rust + println!("Hello, main task!"); + let now = Instant::now(); + task::sleep(Duration::from_secs(1)); + let elapsed = now.elapsed(); + println!("main task sleep for {:?}", elapsed); +``` + +**flow chart** + +```mermaid +graph TD; + S["libax::task::sleep()"] + + S-->arg["libax::time::Duration::from_secs()"] + arg-->argA["libax::time::Duration::new(secs)"] + + S-->A["axhal::time::current_time()"] + A-->AA["axhal::time::TimeValue::from_nanos()"] + + S-->B["axtask::run_queue::AxRunQueue::sleep_until()"] + B-->B.lock["SpinNoIrq< axtask::run_queue::AxRunQueue >"] + B-->BA["axtask::timers::set_alarm_wakeup()"] + BA-->BAA["SpinNoIrq < timer_list::TimeList< axtask::timers::TaskWakeupEvent>>"] + BAA-->BAAA["BinaryHeap < timer_list::TimerEventWrapper >"] + BAA-->BAAB["AxTaskRef = Arc< AxTask >"] + BAAB-->BAABA["scheduler::FifoTask< axtask::task::TaskInner >"] + B-->BB["axtask::run_queue::resched_inner()"] + BB-->BBA["axtask::task::set_state()"] +``` + +### step2.2 + +```Rust + task::spawn(|| { + for i in 0..30 { + info!(" tick {}", i); + task::sleep(Duration::from_millis(500)); + } + }); +``` + +**flow chart** + +```mermaid +graph TD; + T["axtask::task::spawn(closure fn)"] + T-->A["axtask::task::TaskInner::new(entry, name, axconfig::TASK_STACK_SIZE)"] + A-->B["axtask::task::TaskInner::new_common(axtask::task::TaskId, name)"] + A-->C["axtask::task::TaskStack::alloc(size)"] +``` + +### step2.3 + +```Rust + while FINISHED_TASKS.load(Ordering::Relaxed) < NUM_TASKS { + task::sleep(Duration::from_millis(10)); + } +``` + +**flow chart** + +```mermaid +graph TD; + T["FINISHED_TASKS.load(Ordering::Relaxed)"] + T --> A["core::sync::atomic::AtomicUsize::new(num)"] + T --> B["core::sync::atomic::Ordering::Relaxed"] +``` diff --git a/doc/apps_yield.md b/doc/apps_yield.md new file mode 100644 index 000000000..3b037ae74 --- /dev/null +++ b/doc/apps_yield.md @@ -0,0 +1,91 @@ +# INTRODUCTION +| App | Extra modules | Enabled features | Description | +|-|-|-|-| +| [yield](../apps/task/yield/) | axalloc, axtask | alloc, paging, multitask, sched_fifo | Multi-threaded yielding test | + +# RUN + +## Without preemption (FIFO scheduler) + +```shell +make A=apps/task/yield ARCH=riscv64 LOG=info NET=y SMP=1 run +``` + +## With preemption (RR scheduler) + +```shell +make A=apps/task/yield ARCH=riscv64 LOG=info NET=y SMP=1 APP_FEATURES=preempt run +``` + +## RESULT + +``` +Hello, main task! +Hello, task 0! id = TaskId(4) +Hello, task 1! id = TaskId(5) +Hello, task 2! id = TaskId(6) +Hello, task 3! id = TaskId(7) +Hello, task 4! id = TaskId(8) +Hello, task 5! id = TaskId(9) +Hello, task 6! id = TaskId(10) +Hello, task 7! id = TaskId(11) +Hello, task 8! id = TaskId(12) +Hello, task 9! id = TaskId(13) +Task yielding tests run OK! +``` + +# STEPS + +## step1 + +* OS init +* After executed all initial actions, then arceos call main function in `yield` app. + +## step2 + +* Use the `task::spawn` cycle to generate `NUM_TASKS` tasks (similar to threads). +* Each task executes a function, just print its ID. +* If preemption is disabled, the task voluntarily executes `yield` to give up the CPU. +* If SMP is not enabled, the execution order of tasks must be FIFO. +* `main task` will wait for all other tasks to complete. If not, continue to execute `yield` and wait. + +```rust +fn main() { + for i in 0..NUM_TASKS { + task::spawn(move || { + println!("Hello, task {}! id = {:?}", i, task::current().id()); + // 此时已经启动了yield + // 因为preempt所需要的依赖libax/sched_rr并没有被包含进去 + #[cfg(not(feature = "preempt"))] + task::yield_now(); + + let order = FINISHED_TASKS.fetch_add(1, Ordering::Relaxed); + if option_env!("SMP") == Some("1") { + assert!(order == i); // FIFO scheduler + } + }); + } + println!("Hello, main task{}!"); + while FINISHED_TASKS.load(Ordering::Relaxed) < NUM_TASKS { + #[cfg(not(feature = "preempt"))] + task::yield_now(); + } + println!("Task yielding tests run OK!"); +} +``` + +**flow chart** + +```mermaid +graph TD; + T["main task"] --> A["axtask::lib::spawn"]; + A -- "task_i" --> B["axtask::run_queue::AxRunqQueue.scheduler.push_back(tasak_i)"]; + A --> C["RUN_QUEUE.lock()"]; + B -- "repeat n times" --> A; + B -- "main task" --> D["axtask::lib::yield_now"]; + D --> E["axtask::run_queue::AxRunqQueue::resched_inner"]; + E -- "prev" --> F["axtask::run_queue::AxRunqQueue.scheduler.push_back(prev)"]; + E -- "next=axtask::run_queue::AxRunqQueue.scheduler.pop_front()" --> G["axtask::run_queue::AxRunqQueue:: switch_to(prev,next)"]; + G -- "repeat n times" --> D; + G -- "all n subtask finished" --> H["finished"] +``` diff --git a/doc/build.md b/doc/build.md new file mode 100644 index 000000000..8cc302a65 --- /dev/null +++ b/doc/build.md @@ -0,0 +1,91 @@ +# ArceOS Build Flow + +We will provide an example to illustrate the process of building and running ArceOS: + +**Examples:** + +What happens when "make A=apps/net/httpserver ARCH=aarch64 LOG=info NET=y SMP=1 run" is executed? + +- How ArceOS build? + - Firstly check Makefile: Based on different parameters, select whether FS/NET/GRAPHIC param is yes or not. If it is y, it will be compiled in conditional compilation. + - `cargo.mk` determines whether to add the corresponding feature based on whether FS/NET/GRAPHIC is set to y. + ``` + features-$(FS) += libax/fs + features-$(NET) += libax/net + features-$(GRAPHIC) += libax/display + ``` + + - `_cargo_build`: The `_cargo_build` method is defined in cargo.mk. Different compilation methods are selected based on the language. For example, for Rust, when `cargo_build,--manifest-path $(APP)/Cargo.toml` is called, where $(APP) represents the current application to be run. + - Taking httpserver as an example, let's see how ArceOS are conditionally compiled. First, in the `Cargo.toml` file of httpserver, the dependency is specified as: `libax = { path = "../../../ulib/libax", features = ["paging", "multitask", "net"] }`. This indicates that libax needs to be compiled and has the three features mentioned above. + - After checking libax, the following three features were found: + - `paging = ["axruntime/paging"]` + - `multitask = ["axruntime/multitask", "axtask/multitask", "axsync/multitask"]` + - `net = ["axruntime/net", "dep:axnet"]` + + This involves modules such as axruntime, axtask, axsync, etc., and conditional compilation is performed on these modules. + - The above are some modules required for compilation, next we will look at how to perform conditional compilation. The `cargo.mk` file describes how to use the cargo method for conditional compilation, with the following build parameters: + ``` + build_args := \ + -Zbuild-std=core,alloc -Zbuild-std-features=compiler-builtins-mem \ + --config "build.rustflags='-Clink-arg=-T$(LD_SCRIPT)'" \ + --target $(TARGET) \ + --target-dir $(CURDIR)/target \ + --features "$(features-y)" \ + ``` + Note that the -Zbuild-std option is mentioned here, indicating the replacement of the standard library for the application and the use of libraries provided by ArceOS. + + - Therefore, to summarize: choose conditions in Makefile and select the corresponding app directory for conditional compilation in `cargo.mk`. +- Next, describe how ArceOS run: + - Firstly, examining the Makefile reveals that in addition to building, running an application also requires `justrun`. + - Following this, it was found that the `qemu.mk` file would call run_qemu. Similar to the build process, the execution process would also use conditional selection and run. + - At runtime, Arceos first performs some boot operations, such as executing in the riscv64 environment: + ```rust + #[naked] + #[no_mangle] + #[link_section = ".text.boot"] + unsafe extern "C" fn _start() -> ! { + extern "C" { + fn rust_main(); + } + // PC = 0x8020_0000 + // a0 = hartid + // a1 = dtb + core::arch::asm!(" + mv s0, a0 // save hartid + mv s1, a1 // save DTB pointer + la sp, {boot_stack} + li t0, {boot_stack_size} + add sp, sp, t0 // setup boot stack + + call {init_boot_page_table} + call {init_mmu} // setup boot page table and enabel MMU + + li s2, {phys_virt_offset} // fix up virtual high address + add sp, sp, s2 + + mv a0, s0 + mv a1, s1 + la a2, {platform_init} + add a2, a2, s2 + jalr a2 // call platform_init(hartid, dtb) + + mv a0, s0 + mv a1, s1 + la a2, {rust_main} + add a2, a2, s2 + jalr a2 // call rust_main(hartid, dtb) + j .", + phys_virt_offset = const PHYS_VIRT_OFFSET, + boot_stack_size = const TASK_STACK_SIZE, + boot_stack = sym BOOT_STACK, + init_boot_page_table = sym init_boot_page_table, + init_mmu = sym init_mmu, + platform_init = sym super::platform_init, + rust_main = sym rust_main, + options(noreturn), + ) + } + ``` + - Later, it jumps to `rust_main` in `axruntime` to run. After some conditional initialization, `rust_main` executes `main()`. Since this main is defined by the application, symbol linkage should be established and jumped to (no context switch is needed since it's a single address space). + + - Then, the user program begins executing through `libax`'s API. The application runs in kernel mode, without the need for syscall and context switching, resulting in higher efficiency. diff --git a/doc/figures/ArceOS.svg b/doc/figures/ArceOS.svg new file mode 100644 index 000000000..a5d2b1c64 --- /dev/null +++ b/doc/figures/ArceOS.svg @@ -0,0 +1,4 @@ + + + +
ArceOS modules
ArceOS modules
ArceOS crates
ArceOS crates
User Apps
User Apps
Hypervisor
Hypervisor
axnet
axnet
axtask
axtask
axconfig
axconfig
axruntime
axruntime
axhal
axhal
axdriver
axdriver
allocator
allocator
axalloc
axalloc
scheduler
scheduler
page_table
page_table
linked_list
linked_list
driver_blk
driver_blk
axasync
axasync
smoltcp
smoltcp
buddy
buddy
slab
slab
FIFO
FIFO
CFS
CFS
ixgbe
ixgbe
lwip_rust
lwip_rust
driver_virtio
driver_virtio
driver_net
driver_net
axfs
axfs
page_table_entry
page_table_entry
ArceOS API
ArceOS API
arceos_posix_api
arceos_posix_api
arceos_rust_api
arceos_rust_api
ArceOS ulib
ArceOS ulib
axstd
axstd
axlibc
axlibc
rust std
rust std
Rust App
Rust App
C App
C App
Rust std App
Rust std App
C App
C App
musl libc
musl libc
axfeat
axfeat
feature selection
feature selection
Text is not SVG - cannot display
\ No newline at end of file diff --git a/doc/figures/display.png b/doc/figures/display.png new file mode 100644 index 0000000000000000000000000000000000000000..3bfab41f0921a8e672550868009426b01b2d781d GIT binary patch literal 124401 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 z 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%0DR~c}%KotL`1}Q1+(Wd@YN639v)}oL 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 z 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 HF6@5*p5e_) literal 0 HcmV?d00001 diff --git a/doc/init.md b/doc/init.md new file mode 100644 index 000000000..3218fe7a3 --- /dev/null +++ b/doc/init.md @@ -0,0 +1,44 @@ +```mermaid +graph TD; + A[axhal::platform::qemu_virt_riscv::boot.rs::_boot] --> init_boot_page_table; + A --> init_mmu; + A --> P[platform_init]; + A --> B[axruntime::rust_main]; + P --> P1["axhal::mem::clear_bss()"]; + P --> P2["axhal::arch::riscv::set_trap_vector_base()"]; + P --> P3["axhal::cpu::init_percpu()"]; + P --> P4["axhal::platform::qemu_virt_riscv::irq.rs::init()"]; + P --> P5["axhal::platform::qemu_virt_riscv::time.rs::init()"]; + B --> axlog::init; + B --> D[init_allocator]; + B --> remap_kernel_memory; + B --> axtask::init_scheduler; + B --> axdriver::init_drivers; + B --> Q[axfs::init_filesystems]; + B --> axnet::init_network; + B --> axdisplay::init_display; + B --> init_interrupt; + B --> mp::start_secondary_cpus; + B --> C[main]; + Q --> Q1["disk=axfs::dev::Disk::new()"]; + Q --> Q2["axfs::root::init_rootfs(disk)"]; + Q2 --fatfs--> Q21["main_fs=axfs::fs::fatfs::FatFileSystem::new()"]; + Q2 --> Q22["MAIN_FS.init_by(main_fs); MAIN_FS.init()"]; + Q2 --> Q23["root_dir = RootDirectory::new(MAIN_FS)"]; + Q2 --devfs--> Q24["axfs_devfs::DeviceFileSystem::new()"]; + Q2 --devfs--> Q25["devfs.add(null, zero, bar)"]; + Q2 -->Q26["root_dir.mount(devfs)"]; + Q2 -->Q27["init ROOT_DIR, CURRENT_DIR"]; + D --> E["In free memory_regions: axalloc::global_init"]; + D --> F["In free memory_regions: axalloc::global_add_memory"]; + E --> G[axalloc::GLOBAL_ALLOCATOR.init]; + F --> H[axalloc::GLOBAL_ALLOCATOR.add_memory]; + G --> I["PAGE: self.palloc.lock().init"]; + G --> J["BYTE: self.balloc.lock().init"]; + H --> K["BYTE: self.balloc.lock().add_memory"]; + I --> M["allocator::bitmap::BitmapPageAllocator::init()"]; + J -->L["allocator::slab::SlabByteAllocator::init() self.inner = unsafe { Some(Heap::new(start, size))"]; + K --> N["allocator::slab::SlabByteAllocator::add_memory: self.inner_mut().add_memory(start, size);"]; + +``` + diff --git a/doc/ixgbe.md b/doc/ixgbe.md new file mode 100644 index 000000000..cba58e3b6 --- /dev/null +++ b/doc/ixgbe.md @@ -0,0 +1,15 @@ +# How to run arceos with ixgbe NIC? + +You need to specify the platform that owns this network card. For example, we defined a toml file named `x86_64-pc-oslab`` under the platforms directory to describe the platform characteristics. + +You can use the following command to compile an 'httpserver' app application: + +```shell +make A=apps/net/httpserver PLATFORM=x86_64-pc-oslab FEATURES=driver-ixgbe +``` + +You can also use the following command to start the iperf application: + +```shell +make A=apps/c/iperf PLATFORM=x86_64-pc-oslab FEATURES=driver-ixgbe,driver-ramdisk +``` diff --git a/doc/platform_raspi4.md b/doc/platform_raspi4.md new file mode 100644 index 000000000..88f1a8166 --- /dev/null +++ b/doc/platform_raspi4.md @@ -0,0 +1,28 @@ +# How to run ArceOS on raspi4 + +Recommand you download this tutorial first: + +https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials + +And follow this tutorial to run the 06_uart_chainloader chapter. +It will help you to connect your raspi4 to your computer and it will also teach you how to use `make chainboot` to run your code on raspi4. + +Then run with features `ARCH=aarch64 PLATFORM = raspi4-aarch64` and use the command `make chainboot` to transmit the xxxx_raspi4-aarch64.bin to your raspi4. + +# How to debug ArceOS on raspi4 + +Recommand you download this tutorial first: + +https://github.com/rust-embedded/rust-raspberrypi-OS-tutorials + +And follow this tutorial to run the 08_hw_debug_JTAG chapter. +It will help you to connect your raspi4 to the JTAG and connect your JTAG to your computer and it will also teach you how to use `make jtagboot` to debug your raspi4. + +Because the JTAG only support the first line of code be loaded at `0x80000` and only support single core, so you have to +1. Change the file: /modules/axconfig/src/platform/raspi4_aarch64, replace the "kernel-base-vaddr" with "0x8_0000" and replace the "phys-virt-offset" with "0x0" +2. set the feature `SMP=1` + +Then run with features `ARCH=aarch64 PLATFORM = raspi4-aarch64` and +1. use the command `make jtagboot` to run a halt program on your raspi4 +2. start a new terminal, and run `make openocd` to connect your PC with the JTAG +3. start a new terminal, and run `make gdb` to start a gdb, and type `target remote :3333` to connect with your openocd, and type `load` to load the xxxx_raspi4-aarch64.bin to your raspi4 and start to debug. diff --git a/modules/axalloc/Cargo.toml b/modules/axalloc/Cargo.toml new file mode 100644 index 000000000..40eedf404 --- /dev/null +++ b/modules/axalloc/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "axalloc" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "ArceOS global memory allocator" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axalloc" +documentation = "https://rcore-os.github.io/arceos/axalloc/index.html" + +[features] +default = ["tlsf"] +tlsf = ["allocator/tlsf"] +slab = ["allocator/slab"] +buddy = ["allocator/buddy"] + +[dependencies] +log = "0.4" +cfg-if = "1.0" +spinlock = { path = "../../crates/spinlock" } +memory_addr = { path = "../../crates/memory_addr" } +allocator = { path = "../../crates/allocator", features = ["bitmap"] } +axerrno = { path = "../../crates/axerrno" } diff --git a/modules/axalloc/src/lib.rs b/modules/axalloc/src/lib.rs new file mode 100644 index 000000000..096ce95ae --- /dev/null +++ b/modules/axalloc/src/lib.rs @@ -0,0 +1,233 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) global memory allocator. +//! +//! It provides [`GlobalAllocator`], which implements the trait +//! [`core::alloc::GlobalAlloc`]. A static global variable of type +//! [`GlobalAllocator`] is defined with the `#[global_allocator]` attribute, to +//! be registered as the standard library’s default allocator. + +#![no_std] + +#[macro_use] +extern crate log; +extern crate alloc; + +mod page; + +use allocator::{AllocResult, BaseAllocator, BitmapPageAllocator, ByteAllocator, PageAllocator}; +use core::alloc::{GlobalAlloc, Layout}; +use core::ptr::NonNull; +use spinlock::SpinNoIrq; + +const PAGE_SIZE: usize = 0x1000; +const MIN_HEAP_SIZE: usize = 0x8000; // 32 K + +pub use page::GlobalPage; + +cfg_if::cfg_if! { + if #[cfg(feature = "slab")] { + use allocator::SlabByteAllocator as DefaultByteAllocator; + } else if #[cfg(feature = "buddy")] { + use allocator::BuddyByteAllocator as DefaultByteAllocator; + } else if #[cfg(feature = "tlsf")] { + use allocator::TlsfByteAllocator as DefaultByteAllocator; + } +} + +/// The global allocator used by ArceOS. +/// +/// It combines a [`ByteAllocator`] and a [`PageAllocator`] into a simple +/// two-level allocator: firstly tries allocate from the byte allocator, if +/// there is no memory, asks the page allocator for more memory and adds it to +/// the byte allocator. +/// +/// Currently, [`TlsfByteAllocator`] is used as the byte allocator, while +/// [`BitmapPageAllocator`] is used as the page allocator. +/// +/// [`TlsfByteAllocator`]: allocator::TlsfByteAllocator +pub struct GlobalAllocator { + balloc: SpinNoIrq, + palloc: SpinNoIrq>, +} + +impl GlobalAllocator { + /// Creates an empty [`GlobalAllocator`]. + pub const fn new() -> Self { + Self { + balloc: SpinNoIrq::new(DefaultByteAllocator::new()), + palloc: SpinNoIrq::new(BitmapPageAllocator::new()), + } + } + + /// Returns the name of the allocator. + pub const fn name(&self) -> &'static str { + cfg_if::cfg_if! { + if #[cfg(feature = "slab")] { + "slab" + } else if #[cfg(feature = "buddy")] { + "buddy" + } else if #[cfg(feature = "tlsf")] { + "TLSF" + } + } + } + + /// Initializes the allocator with the given region. + /// + /// It firstly adds the whole region to the page allocator, then allocates + /// a small region (32 KB) to initialize the byte allocator. Therefore, + /// the given region must be larger than 32 KB. + pub fn init(&self, start_vaddr: usize, size: usize) { + assert!(size > MIN_HEAP_SIZE); + let init_heap_size = MIN_HEAP_SIZE; + self.palloc.lock().init(start_vaddr, size); + let heap_ptr = self + .alloc_pages(init_heap_size / PAGE_SIZE, PAGE_SIZE) + .unwrap(); + self.balloc.lock().init(heap_ptr, init_heap_size); + } + + /// Add the given region to the allocator. + /// + /// It will add the whole region to the byte allocator. + pub fn add_memory(&self, start_vaddr: usize, size: usize) -> AllocResult { + self.balloc.lock().add_memory(start_vaddr, size) + } + + /// Allocate arbitrary number of bytes. Returns the left bound of the + /// allocated region. + /// + /// It firstly tries to allocate from the byte allocator. If there is no + /// memory, it asks the page allocator for more memory and adds it to the + /// byte allocator. + /// + /// `align_pow2` must be a power of 2, and the returned region bound will be + /// aligned to it. + pub fn alloc(&self, layout: Layout) -> AllocResult> { + // simple two-level allocator: if no heap memory, allocate from the page allocator. + let mut balloc = self.balloc.lock(); + loop { + if let Ok(ptr) = balloc.alloc(layout) { + return Ok(ptr); + } else { + let old_size = balloc.total_bytes(); + let expand_size = old_size + .max(layout.size()) + .next_power_of_two() + .max(PAGE_SIZE); + let heap_ptr = self.alloc_pages(expand_size / PAGE_SIZE, PAGE_SIZE)?; + debug!( + "expand heap memory: [{:#x}, {:#x})", + heap_ptr, + heap_ptr + expand_size + ); + balloc.add_memory(heap_ptr, expand_size)?; + } + } + } + + /// Gives back the allocated region to the byte allocator. + /// + /// The region should be allocated by [`alloc`], and `align_pow2` should be + /// the same as the one used in [`alloc`]. Otherwise, the behavior is + /// undefined. + /// + /// [`alloc`]: GlobalAllocator::alloc + pub fn dealloc(&self, pos: NonNull, layout: Layout) { + self.balloc.lock().dealloc(pos, layout) + } + + /// Allocates contiguous pages. + /// + /// It allocates `num_pages` pages from the page allocator. + /// + /// `align_pow2` must be a power of 2, and the returned region bound will be + /// aligned to it. + pub fn alloc_pages(&self, num_pages: usize, align_pow2: usize) -> AllocResult { + self.palloc.lock().alloc_pages(num_pages, align_pow2) + } + + /// Gives back the allocated pages starts from `pos` to the page allocator. + /// + /// The pages should be allocated by [`alloc_pages`], and `align_pow2` + /// should be the same as the one used in [`alloc_pages`]. Otherwise, the + /// behavior is undefined. + /// + /// [`alloc_pages`]: GlobalAllocator::alloc_pages + pub fn dealloc_pages(&self, pos: usize, num_pages: usize) { + self.palloc.lock().dealloc_pages(pos, num_pages) + } + + /// Returns the number of allocated bytes in the byte allocator. + pub fn used_bytes(&self) -> usize { + self.balloc.lock().used_bytes() + } + + /// Returns the number of available bytes in the byte allocator. + pub fn available_bytes(&self) -> usize { + self.balloc.lock().available_bytes() + } + + /// Returns the number of allocated pages in the page allocator. + pub fn used_pages(&self) -> usize { + self.palloc.lock().used_pages() + } + + /// Returns the number of available pages in the page allocator. + pub fn available_pages(&self) -> usize { + self.palloc.lock().available_pages() + } +} + +unsafe impl GlobalAlloc for GlobalAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + if let Ok(ptr) = GlobalAllocator::alloc(self, layout) { + ptr.as_ptr() + } else { + alloc::alloc::handle_alloc_error(layout) + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + GlobalAllocator::dealloc(self, NonNull::new(ptr).expect("dealloc null ptr"), layout) + } +} + +#[cfg_attr(all(target_os = "none", not(test)), global_allocator)] +static GLOBAL_ALLOCATOR: GlobalAllocator = GlobalAllocator::new(); + +/// Returns the reference to the global allocator. +pub fn global_allocator() -> &'static GlobalAllocator { + &GLOBAL_ALLOCATOR +} + +/// Initializes the global allocator with the given memory region. +/// +/// Note that the memory region bounds are just numbers, and the allocator +/// does not actually access the region. Users should ensure that the region +/// is valid and not being used by others, so that the allocated memory is also +/// valid. +/// +/// This function should be called only once, and before any allocation. +pub fn global_init(start_vaddr: usize, size: usize) { + debug!( + "initialize global allocator at: [{:#x}, {:#x})", + start_vaddr, + start_vaddr + size + ); + GLOBAL_ALLOCATOR.init(start_vaddr, size); +} + +/// Add the given memory region to the global allocator. +/// +/// Users should ensure that the region is valid and not being used by others, +/// so that the allocated memory is also valid. +/// +/// It's similar to [`global_init`], but can be called multiple times. +pub fn global_add_memory(start_vaddr: usize, size: usize) -> AllocResult { + debug!( + "add a memory region to global allocator: [{:#x}, {:#x})", + start_vaddr, + start_vaddr + size + ); + GLOBAL_ALLOCATOR.add_memory(start_vaddr, size) +} diff --git a/modules/axalloc/src/page.rs b/modules/axalloc/src/page.rs new file mode 100644 index 000000000..f4a69129b --- /dev/null +++ b/modules/axalloc/src/page.rs @@ -0,0 +1,108 @@ +use allocator::AllocError; +use axerrno::{AxError, AxResult}; +use memory_addr::{PhysAddr, VirtAddr}; + +use crate::{global_allocator, PAGE_SIZE}; + +/// A RAII wrapper of contiguous 4K-sized pages. +/// +/// It will automatically deallocate the pages when dropped. +#[derive(Debug)] +pub struct GlobalPage { + start_vaddr: VirtAddr, + num_pages: usize, +} + +impl GlobalPage { + /// Allocate one 4K-sized page. + pub fn alloc() -> AxResult { + global_allocator() + .alloc_pages(1, PAGE_SIZE) + .map(|vaddr| Self { + start_vaddr: vaddr.into(), + num_pages: 1, + }) + .map_err(alloc_err_to_ax_err) + } + + /// Allocate one 4K-sized page and fill with zero. + pub fn alloc_zero() -> AxResult { + let mut p = Self::alloc()?; + p.zero(); + Ok(p) + } + + /// Allocate contiguous 4K-sized pages. + pub fn alloc_contiguous(num_pages: usize, align_pow2: usize) -> AxResult { + global_allocator() + .alloc_pages(num_pages, align_pow2) + .map(|vaddr| Self { + start_vaddr: vaddr.into(), + num_pages, + }) + .map_err(alloc_err_to_ax_err) + } + + /// Get the start virtual address of this page. + pub fn start_vaddr(&self) -> VirtAddr { + self.start_vaddr + } + + /// Get the start physical address of this page. + pub fn start_paddr(&self, virt_to_phys: F) -> PhysAddr + where + F: FnOnce(VirtAddr) -> PhysAddr, + { + virt_to_phys(self.start_vaddr) + } + + /// Get the total size (in bytes) of these page(s). + pub fn size(&self) -> usize { + self.num_pages * PAGE_SIZE + } + + /// Convert to a raw pointer. + pub fn as_ptr(&self) -> *const u8 { + self.start_vaddr.as_ptr() + } + + /// Convert to a mutable raw pointer. + pub fn as_mut_ptr(&mut self) -> *mut u8 { + self.start_vaddr.as_mut_ptr() + } + + /// Fill `self` with `byte`. + pub fn fill(&mut self, byte: u8) { + unsafe { core::ptr::write_bytes(self.as_mut_ptr(), byte, self.size()) } + } + + /// Fill `self` with zero. + pub fn zero(&mut self) { + self.fill(0) + } + + /// Forms a slice that can read data. + pub fn as_slice(&self) -> &[u8] { + unsafe { core::slice::from_raw_parts(self.as_ptr(), self.size()) } + } + + /// Forms a mutable slice that can write data. + pub fn as_slice_mut(&mut self) -> &mut [u8] { + unsafe { core::slice::from_raw_parts_mut(self.as_mut_ptr(), self.size()) } + } +} + +impl Drop for GlobalPage { + fn drop(&mut self) { + global_allocator().dealloc_pages(self.start_vaddr.into(), self.num_pages); + } +} + +const fn alloc_err_to_ax_err(e: AllocError) -> AxError { + match e { + AllocError::InvalidParam | AllocError::MemoryOverlap | AllocError::NotAllocated => { + AxError::InvalidInput + } + AllocError::NoMemory => AxError::NoMemory, + } +} diff --git a/modules/axconfig/Cargo.toml b/modules/axconfig/Cargo.toml new file mode 100644 index 000000000..c0ffe34b4 --- /dev/null +++ b/modules/axconfig/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "axconfig" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Platform-specific constants and parameters for ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axconfig" +documentation = "https://rcore-os.github.io/arceos/axconfig/index.html" + +[build-dependencies] +toml_edit = "0.19" +serde = "1.0" diff --git a/modules/axconfig/build.rs b/modules/axconfig/build.rs new file mode 100644 index 000000000..4949a00b5 --- /dev/null +++ b/modules/axconfig/build.rs @@ -0,0 +1,172 @@ +use std::io::{Result, Write}; +use std::path::{Path, PathBuf}; +use toml_edit::{Decor, Document, Item, Table, Value}; + +fn resolve_config_path(platform: Option<&str>) -> Result { + let mut root_dir = PathBuf::from(std::env!("CARGO_MANIFEST_DIR")); + root_dir.extend(["..", ".."]); + let config_dir = root_dir.join("platforms"); + + let builtin_platforms = std::fs::read_dir(&config_dir)? + .filter_map(|e| { + e.unwrap() + .file_name() + .to_str()? + .strip_suffix(".toml") + .map(String::from) + }) + .collect::>(); + + let path = match platform { + None | Some("") => "defconfig.toml".into(), + Some(plat) if builtin_platforms.contains(&plat.to_string()) => { + config_dir.join(format!("{plat}.toml")) + } + Some(plat) => { + let path = PathBuf::from(&plat); + if path.is_absolute() { + path + } else { + root_dir.join(plat) + } + } + }; + + Ok(path) +} + +fn get_comments<'a>(config: &'a Table, key: &str) -> Option<&'a str> { + config + .key_decor(key) + .and_then(|d| d.prefix()) + .and_then(|s| s.as_str()) + .map(|s| s.trim()) +} + +fn add_config(config: &mut Table, key: &str, item: Item, comments: Option<&str>) { + config.insert(key, item); + if let Some(comm) = comments { + if let Some(dst) = config.key_decor_mut(key) { + *dst = Decor::new(comm, ""); + } + } +} + +fn load_config_toml(config_path: &Path) -> Result { + let config_content = std::fs::read_to_string(config_path)?; + let toml = config_content + .parse::() + .expect("failed to parse config file") + .as_table() + .clone(); + Ok(toml) +} + +fn gen_config_rs(config_path: &Path) -> Result> { + fn is_num(s: &str) -> bool { + let s = s.replace('_', ""); + if s.parse::().is_ok() { + true + } else if let Some(s) = s.strip_prefix("0x") { + usize::from_str_radix(s, 16).is_ok() + } else { + false + } + } + + // Load TOML config file + let mut config = if config_path == Path::new("defconfig.toml") { + load_config_toml(config_path)? + } else { + // Set default values for missing items + let defconfig = load_config_toml(Path::new("defconfig.toml"))?; + let mut config = load_config_toml(config_path)?; + + for (key, item) in defconfig.iter() { + if !config.contains_key(key) { + add_config( + &mut config, + key, + item.clone(), + get_comments(&defconfig, key), + ); + } + } + config + }; + + add_config( + &mut config, + "smp", + toml_edit::value(std::env::var("AX_SMP").unwrap_or("1".into())), + Some("# Number of CPUs"), + ); + + // Generate config.rs + let mut output = Vec::new(); + writeln!( + output, + "// Platform constants and parameters for {}.", + config["platform"].as_str().unwrap(), + )?; + writeln!(output, "// Generated by build.rs, DO NOT edit!\n")?; + + for (key, item) in config.iter() { + let var_name = key.to_uppercase().replace('-', "_"); + if let Item::Value(value) = item { + let comments = get_comments(&config, key) + .unwrap_or_default() + .replace('#', "///"); + match value { + Value::String(s) => { + writeln!(output, "{comments}")?; + let s = s.value(); + if is_num(s) { + writeln!(output, "pub const {var_name}: usize = {s};")?; + } else { + writeln!(output, "pub const {var_name}: &str = \"{s}\";")?; + } + } + Value::Array(regions) => { + if key != "mmio-regions" && key != "virtio-mmio-regions" && key != "pci-ranges" + { + continue; + } + writeln!(output, "{comments}")?; + writeln!(output, "pub const {var_name}: &[(usize, usize)] = &[")?; + for r in regions.iter() { + let r = r.as_array().unwrap(); + writeln!( + output, + " ({}, {}),", + r.get(0).unwrap().as_str().unwrap(), + r.get(1).unwrap().as_str().unwrap() + )?; + } + writeln!(output, "];")?; + } + _ => {} + } + } + } + + Ok(output) +} + +fn main() -> Result<()> { + let platform = option_env!("AX_PLATFORM"); + let config_path = resolve_config_path(platform)?; + + println!("Reading config file: {:?}", config_path); + let config_rs = gen_config_rs(&config_path)?; + + let out_dir = std::env::var("OUT_DIR").unwrap(); + let out_path = Path::new(&out_dir).join("config.rs"); + println!("Generating config file: {}", out_path.display()); + std::fs::write(out_path, config_rs)?; + + println!("cargo:rerun-if-changed={}", config_path.display()); + println!("cargo:rerun-if-env-changed=AX_PLATFORM"); + println!("cargo:rerun-if-env-changed=AX_SMP"); + Ok(()) +} diff --git a/modules/axconfig/defconfig.toml b/modules/axconfig/defconfig.toml new file mode 100644 index 000000000..e13488d9e --- /dev/null +++ b/modules/axconfig/defconfig.toml @@ -0,0 +1,41 @@ +# Architecture identifier. +arch = "unknown" +# Platform identifier. +platform = "dummy" +# Platform family. +family = "dummy" + +# Base address of the whole physical memory. +phys-memory-base = "0" +# Size of the whole physical memory. +phys-memory-size = "0" +# Base physical address of the kernel image. +kernel-base-paddr = "0" +# Base virtual address of the kernel image. +kernel-base-vaddr = "0" +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0" +# MMIO regions with format (`base_paddr`, `size`). +mmio-regions = [] +# VirtIO MMIO regions with format (`base_paddr`, `size`). +virtio-mmio-regions = [] +# Base physical address of the PCIe ECAM space. +pci-ecam-base = "0" +# End PCI bus number. +pci-bus-end = "0" +# PCI device memory ranges. +pci-ranges = [] + +# Timer interrupt frequency in Hz. +timer-frequency = "0" + +# Stack size of each task. +task-stack-size = "0x40000" # 256 K + +# Number of timer ticks per second (Hz). A timer tick may contain several timer +# interrupts. +ticks-per-sec = "100" + +# Number of CPUs +smp = "1" diff --git a/modules/axconfig/src/lib.rs b/modules/axconfig/src/lib.rs new file mode 100644 index 000000000..8705b803f --- /dev/null +++ b/modules/axconfig/src/lib.rs @@ -0,0 +1,19 @@ +//! Platform-specific constants and parameters for [ArceOS]. +//! +//! Currently supported platforms can be found in the [platforms] directory of +//! the [ArceOS] root. +//! +//! [ArceOS]: https://github.com/rcore-os/arceos +//! [platforms]: https://github.com/rcore-os/arceos/tree/main/platforms + +#![no_std] + +#[rustfmt::skip] +mod config { + include!(concat!(env!("OUT_DIR"), "/config.rs")); +} + +pub use config::*; + +/// End address of the whole physical memory. +pub const PHYS_MEMORY_END: usize = PHYS_MEMORY_BASE + PHYS_MEMORY_SIZE; diff --git a/modules/axdisplay/Cargo.toml b/modules/axdisplay/Cargo.toml new file mode 100644 index 000000000..0ce0bf341 --- /dev/null +++ b/modules/axdisplay/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "axdisplay" +version = "0.1.0" +edition = "2021" +authors = ["Shiping Yuan "] +description = "ArceOS graphics module" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axdisplay" +documentation = "https://rcore-os.github.io/arceos/axdisplay/index.html" + +[dependencies] +log = "0.4" +axdriver = { path = "../axdriver", features = ["display"] } +lazy_init = { path = "../../crates/lazy_init" } +axsync = { path = "../axsync" } +driver_display = { path = "../../crates/driver_display" } diff --git a/modules/axdisplay/src/lib.rs b/modules/axdisplay/src/lib.rs new file mode 100644 index 000000000..26dc42240 --- /dev/null +++ b/modules/axdisplay/src/lib.rs @@ -0,0 +1,36 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) graphics module. +//! +//! Currently only supports direct writing to the framebuffer. + +#![no_std] + +#[macro_use] +extern crate log; + +#[doc(no_inline)] +pub use driver_display::DisplayInfo; + +use axdriver::{prelude::*, AxDeviceContainer}; +use axsync::Mutex; +use lazy_init::LazyInit; + +static MAIN_DISPLAY: LazyInit> = LazyInit::new(); + +/// Initializes the graphics subsystem by underlayer devices. +pub fn init_display(mut display_devs: AxDeviceContainer) { + info!("Initialize graphics subsystem..."); + + let dev = display_devs.take_one().expect("No graphics device found!"); + info!(" use graphics device 0: {:?}", dev.device_name()); + MAIN_DISPLAY.init_by(Mutex::new(dev)); +} + +/// Gets the framebuffer information. +pub fn framebuffer_info() -> DisplayInfo { + MAIN_DISPLAY.lock().info() +} + +/// Flushes the framebuffer, i.e. show on the screen. +pub fn framebuffer_flush() { + MAIN_DISPLAY.lock().flush().unwrap(); +} diff --git a/modules/axdriver/Cargo.toml b/modules/axdriver/Cargo.toml new file mode 100644 index 000000000..87280a27e --- /dev/null +++ b/modules/axdriver/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "axdriver" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia ", "ChengXiang Qi "] +description = "ArceOS device drivers" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axdriver" +documentation = "https://rcore-os.github.io/arceos/axdriver/index.html" + +[features] +dyn = [] +bus-mmio = [] +bus-pci = ["dep:driver_pci", "dep:axhal", "dep:axconfig"] +net = ["driver_net"] +block = ["driver_block"] +display = ["driver_display"] + +# Enabled by features `virtio-*` +virtio = ["driver_virtio", "dep:axalloc", "dep:axhal", "dep:axconfig"] + +# various types of drivers +virtio-blk = ["block", "virtio", "driver_virtio/block"] +virtio-net = ["net", "virtio", "driver_virtio/net"] +virtio-gpu = ["display", "virtio", "driver_virtio/gpu"] +ramdisk = ["block", "driver_block/ramdisk"] +bcm2835-sdhci = ["block", "driver_block/bcm2835-sdhci"] +ixgbe = ["net", "driver_net/ixgbe", "dep:axalloc", "dep:axhal"] +# more devices example: e1000 = ["net", "driver_net/e1000"] + +default = ["bus-mmio"] + +[dependencies] +log = "0.4" +cfg-if = "1.0" +driver_common = { path = "../../crates/driver_common" } +driver_block = { path = "../../crates/driver_block", optional = true } +driver_net = { path = "../../crates/driver_net", optional = true } +driver_display = { path = "../../crates/driver_display", optional = true } +driver_pci = { path = "../../crates/driver_pci", optional = true } +driver_virtio = { path = "../../crates/driver_virtio", optional = true } +axalloc = { path = "../axalloc", optional = true } +axhal = { path = "../axhal", optional = true } +axconfig = { path = "../axconfig", optional = true } diff --git a/modules/axdriver/build.rs b/modules/axdriver/build.rs new file mode 100644 index 000000000..d418d5ec1 --- /dev/null +++ b/modules/axdriver/build.rs @@ -0,0 +1,50 @@ +const NET_DEV_FEATURES: &[&str] = &["ixgbe", "virtio-net"]; +const BLOCK_DEV_FEATURES: &[&str] = &["ramdisk", "bcm2835-sdhci", "virtio-blk"]; +const DISPLAY_DEV_FEATURES: &[&str] = &["virtio-gpu"]; + +fn has_feature(feature: &str) -> bool { + std::env::var(format!( + "CARGO_FEATURE_{}", + feature.to_uppercase().replace('-', "_") + )) + .is_ok() +} + +fn enable_cfg(key: &str, value: &str) { + println!("cargo:rustc-cfg={key}=\"{value}\""); +} + +fn main() { + if has_feature("bus-pci") { + enable_cfg("bus", "pci"); + } else { + enable_cfg("bus", "mmio"); + } + + // Generate cfgs like `net_dev="virtio-net"`. if `dyn` is not enabled, only one device is + // selected for each device category. If no device is selected, `dummy` is selected. + let is_dyn = has_feature("dyn"); + for (dev_kind, feat_list) in [ + ("net", NET_DEV_FEATURES), + ("block", BLOCK_DEV_FEATURES), + ("display", DISPLAY_DEV_FEATURES), + ] { + if !has_feature(dev_kind) { + continue; + } + + let mut selected = false; + for feat in feat_list { + if has_feature(feat) { + enable_cfg(&format!("{dev_kind}_dev"), feat); + selected = true; + if !is_dyn { + break; + } + } + } + if !is_dyn && !selected { + enable_cfg(&format!("{dev_kind}_dev"), "dummy"); + } + } +} diff --git a/modules/axdriver/src/bus/mmio.rs b/modules/axdriver/src/bus/mmio.rs new file mode 100644 index 000000000..5a3573a6a --- /dev/null +++ b/modules/axdriver/src/bus/mmio.rs @@ -0,0 +1,23 @@ +#[allow(unused_imports)] +use crate::{prelude::*, AllDevices}; + +impl AllDevices { + pub(crate) fn probe_bus_devices(&mut self) { + // TODO: parse device tree + #[cfg(feature = "virtio")] + for reg in axconfig::VIRTIO_MMIO_REGIONS { + for_each_drivers!(type Driver, { + if let Some(dev) = Driver::probe_mmio(reg.0, reg.1) { + info!( + "registered a new {:?} device at [PA:{:#x}, PA:{:#x}): {:?}", + dev.device_type(), + reg.0, reg.0 + reg.1, + dev.device_name(), + ); + self.add_device(dev); + continue; // skip to the next device + } + }); + } + } +} diff --git a/modules/axdriver/src/bus/mod.rs b/modules/axdriver/src/bus/mod.rs new file mode 100644 index 000000000..bc7f80f5a --- /dev/null +++ b/modules/axdriver/src/bus/mod.rs @@ -0,0 +1,4 @@ +#[cfg(bus = "mmio")] +mod mmio; +#[cfg(bus = "pci")] +mod pci; diff --git a/modules/axdriver/src/bus/pci.rs b/modules/axdriver/src/bus/pci.rs new file mode 100644 index 000000000..f47b2757e --- /dev/null +++ b/modules/axdriver/src/bus/pci.rs @@ -0,0 +1,122 @@ +use crate::{prelude::*, AllDevices}; +use axhal::mem::phys_to_virt; +use driver_pci::{ + BarInfo, Cam, Command, DeviceFunction, HeaderType, MemoryBarType, PciRangeAllocator, PciRoot, +}; + +const PCI_BAR_NUM: u8 = 6; + +fn config_pci_device( + root: &mut PciRoot, + bdf: DeviceFunction, + allocator: &mut Option, +) -> DevResult { + let mut bar = 0; + while bar < PCI_BAR_NUM { + let info = root.bar_info(bdf, bar).unwrap(); + if let BarInfo::Memory { + address_type, + address, + size, + .. + } = info + { + // if the BAR address is not assigned, call the allocator and assign it. + if size > 0 && address == 0 { + let new_addr = allocator + .as_mut() + .expect("No memory ranges available for PCI BARs!") + .alloc(size as _) + .ok_or(DevError::NoMemory)?; + if address_type == MemoryBarType::Width32 { + root.set_bar_32(bdf, bar, new_addr as _); + } else if address_type == MemoryBarType::Width64 { + root.set_bar_64(bdf, bar, new_addr); + } + } + } + + // read the BAR info again after assignment. + let info = root.bar_info(bdf, bar).unwrap(); + match info { + BarInfo::IO { address, size } => { + if address > 0 && size > 0 { + debug!(" BAR {}: IO [{:#x}, {:#x})", bar, address, address + size); + } + } + BarInfo::Memory { + address_type, + prefetchable, + address, + size, + } => { + if address > 0 && size > 0 { + debug!( + " BAR {}: MEM [{:#x}, {:#x}){}{}", + bar, + address, + address + size as u64, + if address_type == MemoryBarType::Width64 { + " 64bit" + } else { + "" + }, + if prefetchable { " pref" } else { "" }, + ); + } + } + } + + bar += 1; + if info.takes_two_entries() { + bar += 1; + } + } + + // Enable the device. + let (_status, cmd) = root.get_status_command(bdf); + root.set_command( + bdf, + cmd | Command::IO_SPACE | Command::MEMORY_SPACE | Command::BUS_MASTER, + ); + Ok(()) +} + +impl AllDevices { + pub(crate) fn probe_bus_devices(&mut self) { + let base_vaddr = phys_to_virt(axconfig::PCI_ECAM_BASE.into()); + let mut root = unsafe { PciRoot::new(base_vaddr.as_mut_ptr(), Cam::Ecam) }; + + // PCI 32-bit MMIO space + let mut allocator = axconfig::PCI_RANGES + .get(1) + .map(|range| PciRangeAllocator::new(range.0 as u64, range.1 as u64)); + + for bus in 0..=axconfig::PCI_BUS_END as u8 { + for (bdf, dev_info) in root.enumerate_bus(bus) { + debug!("PCI {}: {}", bdf, dev_info); + if dev_info.header_type != HeaderType::Standard { + continue; + } + match config_pci_device(&mut root, bdf, &mut allocator) { + Ok(_) => for_each_drivers!(type Driver, { + if let Some(dev) = Driver::probe_pci(&mut root, bdf, &dev_info) { + info!( + "registered a new {:?} device at {}: {:?}", + dev.device_type(), + bdf, + dev.device_name(), + ); + self.add_device(dev); + continue; // skip to the next device + } + }), + Err(e) => warn!( + "failed to enable PCI device at {}({}): {:?}", + bdf, dev_info, e + ), + } + } + } + } +} diff --git a/modules/axdriver/src/drivers.rs b/modules/axdriver/src/drivers.rs new file mode 100644 index 000000000..5ee6dcacd --- /dev/null +++ b/modules/axdriver/src/drivers.rs @@ -0,0 +1,130 @@ +//! Defines types and probe methods of all supported devices. + +#![allow(unused_imports)] + +use crate::AxDeviceEnum; +use driver_common::DeviceType; + +#[cfg(feature = "virtio")] +use crate::virtio::{self, VirtIoDevMeta}; + +#[cfg(feature = "bus-pci")] +use driver_pci::{DeviceFunction, DeviceFunctionInfo, PciRoot}; + +pub use super::dummy::*; + +pub trait DriverProbe { + fn probe_global() -> Option { + None + } + + #[cfg(bus = "mmio")] + fn probe_mmio(_mmio_base: usize, _mmio_size: usize) -> Option { + None + } + + #[cfg(bus = "pci")] + fn probe_pci( + _root: &mut PciRoot, + _bdf: DeviceFunction, + _dev_info: &DeviceFunctionInfo, + ) -> Option { + None + } +} + +#[cfg(net_dev = "virtio-net")] +register_net_driver!( + ::Driver, + ::Device +); + +#[cfg(block_dev = "virtio-blk")] +register_block_driver!( + ::Driver, + ::Device +); + +#[cfg(display_dev = "virtio-gpu")] +register_display_driver!( + ::Driver, + ::Device +); + +cfg_if::cfg_if! { + if #[cfg(block_dev = "ramdisk")] { + pub struct RamDiskDriver; + register_block_driver!(RamDiskDriver, driver_block::ramdisk::RamDisk); + + impl DriverProbe for RamDiskDriver { + fn probe_global() -> Option { + // TODO: format RAM disk + Some(AxDeviceEnum::from_block( + driver_block::ramdisk::RamDisk::new(0x100_0000), // 16 MiB + )) + } + } + } +} + +cfg_if::cfg_if! { + if #[cfg(block_dev = "bcm2835-sdhci")]{ + pub struct BcmSdhciDriver; + register_block_driver!(MmckDriver, driver_block::bcm2835sdhci::SDHCIDriver); + + impl DriverProbe for BcmSdhciDriver { + fn probe_global() -> Option { + debug!("mmc probe"); + driver_block::bcm2835sdhci::SDHCIDriver::try_new().ok().map(AxDeviceEnum::from_block) + } + } + } +} + +cfg_if::cfg_if! { + if #[cfg(net_dev = "ixgbe")] { + use crate::ixgbe::IxgbeHalImpl; + use axhal::mem::phys_to_virt; + pub struct IxgbeDriver; + register_net_driver!(IxgbeDriver, driver_net::ixgbe::IxgbeNic); + impl DriverProbe for IxgbeDriver { + fn probe_pci( + root: &mut driver_pci::PciRoot, + bdf: driver_pci::DeviceFunction, + dev_info: &driver_pci::DeviceFunctionInfo, + ) -> Option { + use crate::ixgbe::IxgbeHalImpl; + use driver_net::ixgbe::{INTEL_82599, INTEL_VEND, IxgbeNic}; + if dev_info.vendor_id == INTEL_VEND && dev_info.device_id == INTEL_82599 { + // Intel 10Gb Network + info!("ixgbe PCI device found at {:?}", bdf); + + // Initialize the device + // These can be changed according to the requirments specified in the ixgbe init function. + const QN: u16 = 1; + const QS: usize = 1024; + let bar_info = root.bar_info(bdf, 0).unwrap(); + match bar_info { + driver_pci::BarInfo::Memory { + address, + size, + .. + } => { + let ixgbe_nic = IxgbeNic::::init( + phys_to_virt((address as usize).into()).into(), + size as usize + ) + .expect("failed to initialize ixgbe device"); + return Some(AxDeviceEnum::from_net(ixgbe_nic)); + } + driver_pci::BarInfo::IO { .. } => { + error!("ixgbe: BAR0 is of I/O type"); + return None; + } + } + } + None + } + } + } +} diff --git a/modules/axdriver/src/dummy.rs b/modules/axdriver/src/dummy.rs new file mode 100644 index 000000000..7a02fb024 --- /dev/null +++ b/modules/axdriver/src/dummy.rs @@ -0,0 +1,102 @@ +//! Dummy types used if no device of a certain category is selected. + +#![allow(unused_imports)] +#![allow(dead_code)] + +use super::prelude::*; +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(net_dev = "dummy")] { + use driver_net::{EthernetAddress, NetBuf, NetBufBox, NetBufPool, NetBufPtr}; + + pub struct DummyNetDev; + pub struct DummyNetDrvier; + register_net_driver!(DummyNetDriver, DummyNetDev); + + impl BaseDriverOps for DummyNetDev { + fn device_type(&self) -> DeviceType { DeviceType::Net } + fn device_name(&self) -> &str { "dummy-net" } + } + + impl NetDriverOps for DummyNetDev { + fn mac_address(&self) -> EthernetAddress { unreachable!() } + fn can_transmit(&self) -> bool { false } + fn can_receive(&self) -> bool { false } + fn rx_queue_size(&self) -> usize { 0 } + fn tx_queue_size(&self) -> usize { 0 } + fn recycle_rx_buffer(&mut self, _: NetBufPtr) -> DevResult { Err(DevError::Unsupported) } + fn recycle_tx_buffers(&mut self) -> DevResult { Err(DevError::Unsupported) } + fn transmit(&mut self, _: NetBufPtr) -> DevResult { Err(DevError::Unsupported) } + fn receive(&mut self) -> DevResult { Err(DevError::Unsupported) } + fn alloc_tx_buffer(&mut self, _: usize) -> DevResult { Err(DevError::Unsupported) } + } + } +} + +cfg_if! { + if #[cfg(block_dev = "dummy")] { + pub struct DummyBlockDev; + pub struct DummyBlockDriver; + register_block_driver!(DummyBlockDriver, DummyBlockDev); + + impl BaseDriverOps for DummyBlockDev { + fn device_type(&self) -> DeviceType { + DeviceType::Block + } + fn device_name(&self) -> &str { + "dummy-block" + } + } + + impl BlockDriverOps for DummyBlockDev { + fn num_blocks(&self) -> u64 { + 0 + } + fn block_size(&self) -> usize { + 0 + } + fn read_block(&mut self, _: u64, _: &mut [u8]) -> DevResult { + Err(DevError::Unsupported) + } + fn write_block(&mut self, _: u64, _: &[u8]) -> DevResult { + Err(DevError::Unsupported) + } + fn flush(&mut self) -> DevResult { + Err(DevError::Unsupported) + } + } + } +} + +cfg_if! { + if #[cfg(display_dev = "dummy")] { + pub struct DummyDisplayDev; + pub struct DummyDisplayDriver; + register_display_driver!(DummyDisplayDriver, DummyDisplayDev); + + impl BaseDriverOps for DummyDisplayDev { + fn device_type(&self) -> DeviceType { + DeviceType::Display + } + fn device_name(&self) -> &str { + "dummy-display" + } + } + + impl DisplayDriverOps for DummyDisplayDev { + fn info(&self) -> driver_display::DisplayInfo { + unreachable!() + } + fn fb(&self) -> driver_display::FrameBuffer { + unreachable!() + } + fn need_flush(&self) -> bool { + false + } + fn flush(&mut self) -> DevResult { + Err(DevError::Unsupported) + } + } + } +} diff --git a/modules/axdriver/src/ixgbe.rs b/modules/axdriver/src/ixgbe.rs new file mode 100644 index 000000000..34d926719 --- /dev/null +++ b/modules/axdriver/src/ixgbe.rs @@ -0,0 +1,38 @@ +use axalloc::global_allocator; +use axhal::mem::{phys_to_virt, virt_to_phys}; +use core::{alloc::Layout, ptr::NonNull}; +use driver_net::ixgbe::{IxgbeHal, PhysAddr as IxgbePhysAddr}; + +pub struct IxgbeHalImpl; + +unsafe impl IxgbeHal for IxgbeHalImpl { + fn dma_alloc(size: usize) -> (IxgbePhysAddr, NonNull) { + let layout = Layout::from_size_align(size, 8).unwrap(); + let vaddr = if let Ok(vaddr) = global_allocator().alloc(layout) { + vaddr + } else { + return (0, NonNull::dangling()); + }; + let paddr = virt_to_phys((vaddr.as_ptr() as usize).into()); + (paddr.as_usize(), vaddr) + } + + unsafe fn dma_dealloc(_paddr: IxgbePhysAddr, vaddr: NonNull, size: usize) -> i32 { + let layout = Layout::from_size_align(size, 8).unwrap(); + global_allocator().dealloc(vaddr, layout); + 0 + } + + unsafe fn mmio_phys_to_virt(paddr: IxgbePhysAddr, _size: usize) -> NonNull { + NonNull::new(phys_to_virt(paddr.into()).as_mut_ptr()).unwrap() + } + + unsafe fn mmio_virt_to_phys(vaddr: NonNull, _size: usize) -> IxgbePhysAddr { + virt_to_phys((vaddr.as_ptr() as usize).into()).into() + } + + fn wait_until(duration: core::time::Duration) -> Result<(), &'static str> { + axhal::time::busy_wait_until(duration); + Ok(()) + } +} diff --git a/modules/axdriver/src/lib.rs b/modules/axdriver/src/lib.rs new file mode 100644 index 000000000..05b5d9852 --- /dev/null +++ b/modules/axdriver/src/lib.rs @@ -0,0 +1,184 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) device drivers. +//! +//! # Usage +//! +//! All detected devices are composed into a large struct [`AllDevices`] +//! and returned by the [`init_drivers`] function. The upperlayer subsystems +//! (e.g., the network stack) may unpack the struct to get the specified device +//! driver they want. +//! +//! For each device category (i.e., net, block, display, etc.), an unified type +//! is used to represent all devices in that category. Currently, there are 3 +//! categories: [`AxNetDevice`], [`AxBlockDevice`], and [`AxDisplayDevice`]. +//! +//! # Concepts +//! +//! This crate supports two device models depending on the `dyn` feature: +//! +//! - **Static**: The type of all devices is static, it is determined at compile +//! time by corresponding cargo features. For example, [`AxNetDevice`] will be +//! an alias of [`VirtioNetDev`] if the `virtio-net` feature is enabled. This +//! model provides the best performance as it avoids dynamic dispatch. But on +//! limitation, only one device instance is supported for each device category. +//! - **Dynamic**: All device instance is using [trait objects] and wrapped in a +//! `Box`. For example, [`AxNetDevice`] will be [`Box`]. +//! When call a method provided by the device, it uses [dynamic dispatch][dyn] +//! that may introduce a little overhead. But on the other hand, it is more +//! flexible, multiple instances of each device category are supported. +//! +//! # Supported Devices +//! +//! | Device Category | Cargo Feature | Description | +//! |-|-|-| +//! | Block | `ramdisk` | A RAM disk that stores data in a vector | +//! | Block | `virtio-blk` | VirtIO block device | +//! | Network | `virtio-net` | VirtIO network device | +//! | Display | `virtio-gpu` | VirtIO graphics device | +//! +//! # Other Cargo Features +//! +//! - `dyn`: use the dynamic device model (see above). +//! - `bus-mmio`: use device tree to probe all MMIO devices. This feature is +//! enabeld by default. +//! - `bus-pci`: use PCI bus to probe all PCI devices. +//! - `virtio`: use VirtIO devices. This is enabled if any of `virtio-blk`, +//! `virtio-net` or `virtio-gpu` is enabled. +//! - `net`: use network devices. This is enabled if any feature of network +//! devices is selected. If this feature is enabled without any network device +//! features, a dummy struct is used for [`AxNetDevice`]. +//! - `block`: use block storage devices. Similar to the `net` feature. +//! - `display`: use graphics display devices. Similar to the `net` feature. +//! +//! [`VirtioNetDev`]: driver_virtio::VirtIoNetDev +//! [`Box`]: driver_net::NetDriverOps +//! [trait objects]: https://doc.rust-lang.org/book/ch17-02-trait-objects.html +//! [dyn]: https://doc.rust-lang.org/std/keyword.dyn.html + +#![no_std] +#![feature(doc_auto_cfg)] +#![feature(associated_type_defaults)] + +#[macro_use] +extern crate log; + +#[cfg(feature = "dyn")] +extern crate alloc; + +#[macro_use] +mod macros; + +mod bus; +mod drivers; +mod dummy; +mod structs; + +#[cfg(feature = "virtio")] +mod virtio; + +#[cfg(feature = "ixgbe")] +mod ixgbe; + +pub mod prelude; + +#[allow(unused_imports)] +use self::prelude::*; +pub use self::structs::{AxDeviceContainer, AxDeviceEnum}; + +#[cfg(feature = "block")] +pub use self::structs::AxBlockDevice; +#[cfg(feature = "display")] +pub use self::structs::AxDisplayDevice; +#[cfg(feature = "net")] +pub use self::structs::AxNetDevice; + +/// A structure that contains all device drivers, organized by their category. +#[derive(Default)] +pub struct AllDevices { + /// All network device drivers. + #[cfg(feature = "net")] + pub net: AxDeviceContainer, + /// All block device drivers. + #[cfg(feature = "block")] + pub block: AxDeviceContainer, + /// All graphics device drivers. + #[cfg(feature = "display")] + pub display: AxDeviceContainer, +} + +impl AllDevices { + /// Returns the device model used, either `dyn` or `static`. + /// + /// See the [crate-level documentation](crate) for more details. + pub const fn device_model() -> &'static str { + if cfg!(feature = "dyn") { + "dyn" + } else { + "static" + } + } + + /// Probes all supported devices. + fn probe(&mut self) { + for_each_drivers!(type Driver, { + if let Some(dev) = Driver::probe_global() { + info!( + "registered a new {:?} device: {:?}", + dev.device_type(), + dev.device_name(), + ); + self.add_device(dev); + } + }); + + self.probe_bus_devices(); + } + + /// Adds one device into the corresponding container, according to its device category. + #[allow(dead_code)] + fn add_device(&mut self, dev: AxDeviceEnum) { + match dev { + #[cfg(feature = "net")] + AxDeviceEnum::Net(dev) => self.net.push(dev), + #[cfg(feature = "block")] + AxDeviceEnum::Block(dev) => self.block.push(dev), + #[cfg(feature = "display")] + AxDeviceEnum::Display(dev) => self.display.push(dev), + } + } +} + +/// Probes and initializes all device drivers, returns the [`AllDevices`] struct. +pub fn init_drivers() -> AllDevices { + info!("Initialize device drivers..."); + info!(" device model: {}", AllDevices::device_model()); + + let mut all_devs = AllDevices::default(); + all_devs.probe(); + + #[cfg(feature = "net")] + { + debug!("number of NICs: {}", all_devs.net.len()); + for (i, dev) in all_devs.net.iter().enumerate() { + assert_eq!(dev.device_type(), DeviceType::Net); + debug!(" NIC {}: {:?}", i, dev.device_name()); + } + } + #[cfg(feature = "block")] + { + debug!("number of block devices: {}", all_devs.block.len()); + for (i, dev) in all_devs.block.iter().enumerate() { + assert_eq!(dev.device_type(), DeviceType::Block); + debug!(" block device {}: {:?}", i, dev.device_name()); + } + } + #[cfg(feature = "display")] + { + debug!("number of graphics devices: {}", all_devs.display.len()); + for (i, dev) in all_devs.display.iter().enumerate() { + assert_eq!(dev.device_type(), DeviceType::Display); + debug!(" graphics device {}: {:?}", i, dev.device_name()); + } + } + + all_devs +} diff --git a/modules/axdriver/src/macros.rs b/modules/axdriver/src/macros.rs new file mode 100644 index 000000000..b90e813e7 --- /dev/null +++ b/modules/axdriver/src/macros.rs @@ -0,0 +1,68 @@ +//! TODO: generate registered drivers in `for_each_drivers!` automatically. + +#![allow(unused_macros)] + +macro_rules! register_net_driver { + ($driver_type:ty, $device_type:ty) => { + /// The unified type of the NIC devices. + #[cfg(not(feature = "dyn"))] + pub type AxNetDevice = $device_type; + }; +} + +macro_rules! register_block_driver { + ($driver_type:ty, $device_type:ty) => { + /// The unified type of the NIC devices. + #[cfg(not(feature = "dyn"))] + pub type AxBlockDevice = $device_type; + }; +} + +macro_rules! register_display_driver { + ($driver_type:ty, $device_type:ty) => { + /// The unified type of the NIC devices. + #[cfg(not(feature = "dyn"))] + pub type AxDisplayDevice = $device_type; + }; +} + +macro_rules! for_each_drivers { + (type $drv_type:ident, $code:block) => {{ + #[allow(unused_imports)] + use crate::drivers::DriverProbe; + #[cfg(feature = "virtio")] + #[allow(unused_imports)] + use crate::virtio::{self, VirtIoDevMeta}; + + #[cfg(net_dev = "virtio-net")] + { + type $drv_type = ::Driver; + $code + } + #[cfg(block_dev = "virtio-blk")] + { + type $drv_type = ::Driver; + $code + } + #[cfg(display_dev = "virtio-gpu")] + { + type $drv_type = ::Driver; + $code + } + #[cfg(block_dev = "ramdisk")] + { + type $drv_type = crate::drivers::RamDiskDriver; + $code + } + #[cfg(block_dev = "bcm2835-sdhci")] + { + type $drv_type = crate::drivers::BcmSdhciDriver; + $code + } + #[cfg(net_dev = "ixgbe")] + { + type $drv_type = crate::drivers::IxgbeDriver; + $code + } + }}; +} diff --git a/modules/axdriver/src/prelude.rs b/modules/axdriver/src/prelude.rs new file mode 100644 index 000000000..d41f7fb81 --- /dev/null +++ b/modules/axdriver/src/prelude.rs @@ -0,0 +1,10 @@ +//! Device driver prelude that includes some traits and types. + +pub use driver_common::{BaseDriverOps, DevError, DevResult, DeviceType}; + +#[cfg(feature = "block")] +pub use {crate::structs::AxBlockDevice, driver_block::BlockDriverOps}; +#[cfg(feature = "display")] +pub use {crate::structs::AxDisplayDevice, driver_display::DisplayDriverOps}; +#[cfg(feature = "net")] +pub use {crate::structs::AxNetDevice, driver_net::NetDriverOps}; diff --git a/modules/axdriver/src/structs/dyn.rs b/modules/axdriver/src/structs/dyn.rs new file mode 100644 index 000000000..c4d04cb97 --- /dev/null +++ b/modules/axdriver/src/structs/dyn.rs @@ -0,0 +1,85 @@ +#![allow(unused_imports)] + +use crate::prelude::*; +use alloc::{boxed::Box, vec, vec::Vec}; + +/// The unified type of the NIC devices. +#[cfg(feature = "net")] +pub type AxNetDevice = Box; +/// The unified type of the block storage devices. +#[cfg(feature = "block")] +pub type AxBlockDevice = Box; +/// The unified type of the graphics display devices. +#[cfg(feature = "display")] +pub type AxDisplayDevice = Box; + +impl super::AxDeviceEnum { + /// Constructs a network device. + #[cfg(feature = "net")] + pub fn from_net(dev: impl NetDriverOps + 'static) -> Self { + Self::Net(Box::new(dev)) + } + + /// Constructs a block device. + #[cfg(feature = "block")] + pub fn from_block(dev: impl BlockDriverOps + 'static) -> Self { + Self::Block(Box::new(dev)) + } + + /// Constructs a display device. + #[cfg(feature = "display")] + pub fn from_display(dev: impl DisplayDriverOps + 'static) -> Self { + Self::Display(Box::new(dev)) + } +} + +/// A structure that contains all device drivers of a certain category. +/// +/// If the feature `dyn` is enabled, the inner type is [`Vec`]. Otherwise, +/// the inner type is [`Option`] and at most one device can be contained. +pub struct AxDeviceContainer(Vec); + +impl AxDeviceContainer { + /// Returns number of devices in this container. + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns whether the container is empty. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Takes one device out of the container (will remove it from the container). + pub fn take_one(&mut self) -> Option { + if self.is_empty() { + None + } else { + Some(self.0.remove(0)) + } + } + + /// Constructs the container from one device. + pub fn from_one(dev: D) -> Self { + Self(vec![dev]) + } + + /// Adds one device into the container. + #[allow(dead_code)] + pub(crate) fn push(&mut self, dev: D) { + self.0.push(dev); + } +} + +impl core::ops::Deref for AxDeviceContainer { + type Target = Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Default for AxDeviceContainer { + fn default() -> Self { + Self(Default::default()) + } +} diff --git a/modules/axdriver/src/structs/mod.rs b/modules/axdriver/src/structs/mod.rs new file mode 100644 index 000000000..d373100fb --- /dev/null +++ b/modules/axdriver/src/structs/mod.rs @@ -0,0 +1,51 @@ +#[cfg_attr(feature = "dyn", path = "dyn.rs")] +#[cfg_attr(not(feature = "dyn"), path = "static.rs")] +mod imp; + +use driver_common::{BaseDriverOps, DeviceType}; + +pub use imp::*; + +/// A unified enum that represents different categories of devices. +#[allow(clippy::large_enum_variant)] +pub enum AxDeviceEnum { + /// Network card device. + #[cfg(feature = "net")] + Net(AxNetDevice), + /// Block storage device. + #[cfg(feature = "block")] + Block(AxBlockDevice), + /// Graphic display device. + #[cfg(feature = "display")] + Display(AxDisplayDevice), +} + +impl BaseDriverOps for AxDeviceEnum { + #[inline] + #[allow(unreachable_patterns)] + fn device_type(&self) -> DeviceType { + match self { + #[cfg(feature = "net")] + Self::Net(_) => DeviceType::Net, + #[cfg(feature = "block")] + Self::Block(_) => DeviceType::Block, + #[cfg(feature = "display")] + Self::Display(_) => DeviceType::Display, + _ => unreachable!(), + } + } + + #[inline] + #[allow(unreachable_patterns)] + fn device_name(&self) -> &str { + match self { + #[cfg(feature = "net")] + Self::Net(dev) => dev.device_name(), + #[cfg(feature = "block")] + Self::Block(dev) => dev.device_name(), + #[cfg(feature = "display")] + Self::Display(dev) => dev.device_name(), + _ => unreachable!(), + } + } +} diff --git a/modules/axdriver/src/structs/static.rs b/modules/axdriver/src/structs/static.rs new file mode 100644 index 000000000..9adff2c24 --- /dev/null +++ b/modules/axdriver/src/structs/static.rs @@ -0,0 +1,79 @@ +#[cfg(feature = "block")] +pub use crate::drivers::AxBlockDevice; +#[cfg(feature = "display")] +pub use crate::drivers::AxDisplayDevice; +#[cfg(feature = "net")] +pub use crate::drivers::AxNetDevice; + +impl super::AxDeviceEnum { + /// Constructs a network device. + #[cfg(feature = "net")] + pub const fn from_net(dev: AxNetDevice) -> Self { + Self::Net(dev) + } + + /// Constructs a block device. + #[cfg(feature = "block")] + pub const fn from_block(dev: AxBlockDevice) -> Self { + Self::Block(dev) + } + + /// Constructs a display device. + #[cfg(feature = "display")] + pub const fn from_display(dev: AxDisplayDevice) -> Self { + Self::Display(dev) + } +} + +/// A structure that contains all device drivers of a certain category. +/// +/// If the feature `dyn` is enabled, the inner type is [`Vec`]. Otherwise, +/// the inner type is [`Option`] and at most one device can be contained. +pub struct AxDeviceContainer(Option); + +impl AxDeviceContainer { + /// Returns number of devices in this container. + pub const fn len(&self) -> usize { + if self.0.is_some() { + 1 + } else { + 0 + } + } + + /// Returns whether the container is empty. + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Takes one device out of the container (will remove it from the container). + pub fn take_one(&mut self) -> Option { + self.0.take() + } + + /// Constructs the container from one device. + pub const fn from_one(dev: D) -> Self { + Self(Some(dev)) + } + + /// Adds one device into the container. + #[allow(dead_code)] + pub(crate) fn push(&mut self, dev: D) { + if self.0.is_none() { + self.0 = Some(dev); + } + } +} + +impl core::ops::Deref for AxDeviceContainer { + type Target = Option; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Default for AxDeviceContainer { + fn default() -> Self { + Self(Default::default()) + } +} diff --git a/modules/axdriver/src/virtio.rs b/modules/axdriver/src/virtio.rs new file mode 100644 index 000000000..42999b05a --- /dev/null +++ b/modules/axdriver/src/virtio.rs @@ -0,0 +1,172 @@ +use core::marker::PhantomData; +use core::ptr::NonNull; + +use axalloc::global_allocator; +use axhal::mem::{phys_to_virt, virt_to_phys}; +use cfg_if::cfg_if; +use driver_common::{BaseDriverOps, DevResult, DeviceType}; +use driver_virtio::{BufferDirection, PhysAddr, VirtIoHal}; + +use crate::{drivers::DriverProbe, AxDeviceEnum}; + +cfg_if! { + if #[cfg(bus = "pci")] { + use driver_pci::{PciRoot, DeviceFunction, DeviceFunctionInfo}; + type VirtIoTransport = driver_virtio::PciTransport; + } else if #[cfg(bus = "mmio")] { + type VirtIoTransport = driver_virtio::MmioTransport; + } +} + +/// A trait for VirtIO device meta information. +pub trait VirtIoDevMeta { + const DEVICE_TYPE: DeviceType; + + type Device: BaseDriverOps; + type Driver = VirtIoDriver; + + fn try_new(transport: VirtIoTransport) -> DevResult; +} + +cfg_if! { + if #[cfg(net_dev = "virtio-net")] { + pub struct VirtIoNet; + + impl VirtIoDevMeta for VirtIoNet { + const DEVICE_TYPE: DeviceType = DeviceType::Net; + type Device = driver_virtio::VirtIoNetDev; + + fn try_new(transport: VirtIoTransport) -> DevResult { + Ok(AxDeviceEnum::from_net(Self::Device::try_new(transport)?)) + } + } + } +} + +cfg_if! { + if #[cfg(block_dev = "virtio-blk")] { + pub struct VirtIoBlk; + + impl VirtIoDevMeta for VirtIoBlk { + const DEVICE_TYPE: DeviceType = DeviceType::Block; + type Device = driver_virtio::VirtIoBlkDev; + + fn try_new(transport: VirtIoTransport) -> DevResult { + Ok(AxDeviceEnum::from_block(Self::Device::try_new(transport)?)) + } + } + } +} + +cfg_if! { + if #[cfg(display_dev = "virtio-gpu")] { + pub struct VirtIoGpu; + + impl VirtIoDevMeta for VirtIoGpu { + const DEVICE_TYPE: DeviceType = DeviceType::Display; + type Device = driver_virtio::VirtIoGpuDev; + + fn try_new(transport: VirtIoTransport) -> DevResult { + Ok(AxDeviceEnum::from_display(Self::Device::try_new(transport)?)) + } + } + } +} + +/// A common driver for all VirtIO devices that implements [`DriverProbe`]. +pub struct VirtIoDriver(PhantomData); + +impl DriverProbe for VirtIoDriver { + #[cfg(bus = "mmio")] + fn probe_mmio(mmio_base: usize, mmio_size: usize) -> Option { + let base_vaddr = phys_to_virt(mmio_base.into()); + if let Some((ty, transport)) = + driver_virtio::probe_mmio_device(base_vaddr.as_mut_ptr(), mmio_size) + { + if ty == D::DEVICE_TYPE { + match D::try_new(transport) { + Ok(dev) => return Some(dev), + Err(e) => { + warn!( + "failed to initialize MMIO device at [PA:{:#x}, PA:{:#x}): {:?}", + mmio_base, + mmio_base + mmio_size, + e + ); + return None; + } + } + } + } + None + } + + #[cfg(bus = "pci")] + fn probe_pci( + root: &mut PciRoot, + bdf: DeviceFunction, + dev_info: &DeviceFunctionInfo, + ) -> Option { + if dev_info.vendor_id != 0x1af4 { + return None; + } + match (D::DEVICE_TYPE, dev_info.device_id) { + (DeviceType::Net, 0x1000) | (DeviceType::Net, 0x1040) => {} + (DeviceType::Block, 0x1001) | (DeviceType::Block, 0x1041) => {} + (DeviceType::Display, 0x1050) => {} + _ => return None, + } + + if let Some((ty, transport)) = + driver_virtio::probe_pci_device::(root, bdf, dev_info) + { + if ty == D::DEVICE_TYPE { + match D::try_new(transport) { + Ok(dev) => return Some(dev), + Err(e) => { + warn!( + "failed to initialize PCI device at {}({}): {:?}", + bdf, dev_info, e + ); + return None; + } + } + } + } + None + } +} + +pub struct VirtIoHalImpl; + +unsafe impl VirtIoHal for VirtIoHalImpl { + fn dma_alloc(pages: usize, _direction: BufferDirection) -> (PhysAddr, NonNull) { + let vaddr = if let Ok(vaddr) = global_allocator().alloc_pages(pages, 0x1000) { + vaddr + } else { + return (0, NonNull::dangling()); + }; + let paddr = virt_to_phys(vaddr.into()); + let ptr = NonNull::new(vaddr as _).unwrap(); + (paddr.as_usize(), ptr) + } + + unsafe fn dma_dealloc(_paddr: PhysAddr, vaddr: NonNull, pages: usize) -> i32 { + global_allocator().dealloc_pages(vaddr.as_ptr() as usize, pages); + 0 + } + + #[inline] + unsafe fn mmio_phys_to_virt(paddr: PhysAddr, _size: usize) -> NonNull { + NonNull::new(phys_to_virt(paddr.into()).as_mut_ptr()).unwrap() + } + + #[inline] + unsafe fn share(buffer: NonNull<[u8]>, _direction: BufferDirection) -> PhysAddr { + let vaddr = buffer.as_ptr() as *mut u8 as usize; + virt_to_phys(vaddr.into()).into() + } + + #[inline] + unsafe fn unshare(_paddr: PhysAddr, _buffer: NonNull<[u8]>, _direction: BufferDirection) {} +} diff --git a/modules/axfs/Cargo.toml b/modules/axfs/Cargo.toml new file mode 100644 index 000000000..46df7ff33 --- /dev/null +++ b/modules/axfs/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "axfs" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "ArceOS filesystem module" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axfs" +documentation = "https://rcore-os.github.io/arceos/axfs/index.html" + +[features] +devfs = ["dep:axfs_devfs"] +ramfs = ["dep:axfs_ramfs"] +procfs = ["dep:axfs_ramfs"] +sysfs = ["dep:axfs_ramfs"] +fatfs = ["dep:fatfs"] +myfs = ["dep:crate_interface"] +use-ramdisk = [] + +default = ["devfs", "ramfs", "fatfs", "procfs", "sysfs"] + +[dependencies] +log = "0.4" +cfg-if = "1.0" +lazy_init = { path = "../../crates/lazy_init" } +capability = { path = "../../crates/capability" } +driver_block = { path = "../../crates/driver_block" } +axio = { path = "../../crates/axio", features = ["alloc"] } +axerrno = { path = "../../crates/axerrno" } +axfs_vfs = { path = "../../crates/axfs_vfs" } +axfs_devfs = { path = "../../crates/axfs_devfs", optional = true } +axfs_ramfs = { path = "../../crates/axfs_ramfs", optional = true } +axdriver = { path = "../axdriver", features = ["block"] } +axsync = { path = "../axsync" } +crate_interface = { path = "../../crates/crate_interface", optional = true } + +[dependencies.fatfs] +git = "https://github.com/rafalh/rust-fatfs" +rev = "a3a834e" +optional = true +default-features = false +features = [ # no std + "alloc", + "lfn", + "log_level_trace", + "unicode", +] + +[dev-dependencies] +axdriver = { path = "../axdriver", features = ["block", "ramdisk"] } +driver_block = { path = "../../crates/driver_block", features = ["ramdisk"] } +axsync = { path = "../axsync", features = ["multitask"] } +axtask = { path = "../axtask", features = ["test"] } diff --git a/modules/axfs/resources/create_test_img.sh b/modules/axfs/resources/create_test_img.sh new file mode 100755 index 000000000..d51cef84d --- /dev/null +++ b/modules/axfs/resources/create_test_img.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# From https://github.com/rafalh/rust-fatfs/blob/master/scripts/create-test-img.sh + +CUR_DIR=`dirname $0` + +echo $OUT_DIR + +create_test_img() { + local name=$1 + local blkcount=$2 + local fatSize=$3 + dd if=/dev/zero of="$name" bs=1024 count=$blkcount + mkfs.vfat -s 1 -F $fatSize -n "Test!" -i 12345678 "$name" + mkdir -p mnt + sudo mount -o loop "$name" mnt -o rw,uid=$USER,gid=$USER + for i in $(seq 1 1000); do + echo "Rust is cool!" >>"mnt/long.txt" + done + echo "Rust is cool!" >>"mnt/short.txt" + mkdir -p "mnt/very/long/path" + echo "Rust is cool!" >>"mnt/very/long/path/test.txt" + mkdir -p "mnt/very-long-dir-name" + echo "Rust is cool!" >>"mnt/very-long-dir-name/very-long-file-name.txt" + + sudo umount mnt +} + +create_test_img "$CUR_DIR/fat16.img" 2500 16 +create_test_img "$CUR_DIR/fat32.img" 34000 32 diff --git a/modules/axfs/resources/fat16.img b/modules/axfs/resources/fat16.img new file mode 100644 index 0000000000000000000000000000000000000000..6a220b4f3af6d730347ab1b2ce29611073b53069 GIT binary patch literal 2560000 zcmeI*Pj3`u90%}Ut5PiVkANutV~Tk2&`oL#@!-KFsd8w5ly!lam@IZ%Hr;lc?nqi= zh^N*AiM|f?;6YC&#y8M+;jJD#aN-#1QfoZ9@WaE*3!~yR$H7I8eCegx7%rTHJh)mW_c^im(!NHT4OOQl}g#rVD}I$ z_a*T0cXzk8wj!R5=i*Qd#Nl{82IEK^jiDHhkr<5^;#eGy6Y*l4jF;l&I2EtNtMOXA z9&bb{?V0v<2#N?0AV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs zfrl3G`@e@KQ$TUcQf(9!kkdbL;CfAF)@Wy+wOY-wL7zyt z3fvE#1)ho$K^jU!X($b)p){0+(oh;oLun`trJ*#GhSE?PN<(QV4W*$pl!nq!8cIWH zC=I2dG?a$YP#Q`@X($b)p){0+(oh;oLun`trJ*#GhWksyNBw^{^}PbUdDOejr-Lt0 z+8#Q4d-lNbvrG5Buw#2+^Y8fJo$%7uR}u^rdXnWsSK;2+}N{a^IY-u+)(ALTsi(N0%D zDfRz1d90+>h|YCT%qx>uDxLnXT&rZUK5%EPx>k+oEZ>abgU|cKpP~GJ;=Aav9^l^h z|Jm*R?tT9khjqRlHkCJ` z8P&<@>*G_mCw4y{-tv5StUX^e?*9J|knMs10RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk g1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+zyT5X8xqcZg#Z8m literal 0 HcmV?d00001 diff --git a/modules/axfs/src/api/dir.rs b/modules/axfs/src/api/dir.rs new file mode 100644 index 000000000..55ad5ab32 --- /dev/null +++ b/modules/axfs/src/api/dir.rs @@ -0,0 +1,150 @@ +use alloc::string::String; +use axio::Result; +use core::fmt; + +use super::FileType; +use crate::fops; + +/// Iterator over the entries in a directory. +pub struct ReadDir<'a> { + path: &'a str, + inner: fops::Directory, + buf_pos: usize, + buf_end: usize, + end_of_stream: bool, + dirent_buf: [fops::DirEntry; 31], +} + +/// Entries returned by the [`ReadDir`] iterator. +pub struct DirEntry<'a> { + dir_path: &'a str, + entry_name: String, + entry_type: FileType, +} + +/// A builder used to create directories in various manners. +#[derive(Default, Debug)] +pub struct DirBuilder { + recursive: bool, +} + +impl<'a> ReadDir<'a> { + pub(super) fn new(path: &'a str) -> Result { + let mut opts = fops::OpenOptions::new(); + opts.read(true); + let inner = fops::Directory::open_dir(path, &opts)?; + const EMPTY: fops::DirEntry = fops::DirEntry::default(); + let dirent_buf = [EMPTY; 31]; + Ok(ReadDir { + path, + inner, + end_of_stream: false, + buf_pos: 0, + buf_end: 0, + dirent_buf, + }) + } +} + +impl<'a> Iterator for ReadDir<'a> { + type Item = Result>; + + fn next(&mut self) -> Option>> { + if self.end_of_stream { + return None; + } + + loop { + if self.buf_pos >= self.buf_end { + match self.inner.read_dir(&mut self.dirent_buf) { + Ok(n) => { + if n == 0 { + self.end_of_stream = true; + return None; + } + self.buf_pos = 0; + self.buf_end = n; + } + Err(e) => { + self.end_of_stream = true; + return Some(Err(e)); + } + } + } + let entry = &self.dirent_buf[self.buf_pos]; + self.buf_pos += 1; + let name_bytes = entry.name_as_bytes(); + if name_bytes == b"." || name_bytes == b".." { + continue; + } + let entry_name = unsafe { core::str::from_utf8_unchecked(name_bytes).into() }; + let entry_type = entry.entry_type(); + + return Some(Ok(DirEntry { + dir_path: self.path, + entry_name, + entry_type, + })); + } + } +} + +impl<'a> DirEntry<'a> { + /// Returns the full path to the file that this entry represents. + /// + /// The full path is created by joining the original path to `read_dir` + /// with the filename of this entry. + pub fn path(&self) -> String { + String::from(self.dir_path.trim_end_matches('/')) + "/" + &self.entry_name + } + + /// Returns the bare file name of this directory entry without any other + /// leading path component. + pub fn file_name(&self) -> String { + self.entry_name.clone() + } + + /// Returns the file type for the file that this entry points at. + pub fn file_type(&self) -> FileType { + self.entry_type + } +} + +impl fmt::Debug for DirEntry<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("DirEntry").field(&self.path()).finish() + } +} + +impl DirBuilder { + /// Creates a new set of options with default mode/security settings for all + /// platforms and also non-recursive. + pub fn new() -> Self { + Self { recursive: false } + } + + /// Indicates that directories should be created recursively, creating all + /// parent directories. Parents that do not exist are created with the same + /// security and permissions settings. + pub fn recursive(&mut self, recursive: bool) -> &mut Self { + self.recursive = recursive; + self + } + + /// Creates the specified directory with the options configured in this + /// builder. + pub fn create(&self, path: &str) -> Result<()> { + if self.recursive { + self.create_dir_all(path) + } else { + crate::root::create_dir(None, path) + } + } + + fn create_dir_all(&self, _path: &str) -> Result<()> { + axerrno::ax_err!( + Unsupported, + "Recursive directory creation is not supported yet" + ) + } +} diff --git a/modules/axfs/src/api/file.rs b/modules/axfs/src/api/file.rs new file mode 100644 index 000000000..6941c90cb --- /dev/null +++ b/modules/axfs/src/api/file.rs @@ -0,0 +1,187 @@ +use axio::{prelude::*, Result, SeekFrom}; +use core::fmt; + +use crate::fops; + +/// A structure representing a type of file with accessors for each file type. +/// It is returned by [`Metadata::file_type`] method. +pub type FileType = fops::FileType; + +/// Representation of the various permissions on a file. +pub type Permissions = fops::FilePerm; + +/// An object providing access to an open file on the filesystem. +pub struct File { + inner: fops::File, +} + +/// Metadata information about a file. +pub struct Metadata(fops::FileAttr); + +/// Options and flags which can be used to configure how a file is opened. +#[derive(Clone, Debug)] +pub struct OpenOptions(fops::OpenOptions); + +impl OpenOptions { + /// Creates a blank new set of options ready for configuration. + pub const fn new() -> Self { + OpenOptions(fops::OpenOptions::new()) + } + + /// Sets the option for read access. + pub fn read(&mut self, read: bool) -> &mut Self { + self.0.read(read); + self + } + + /// Sets the option for write access. + pub fn write(&mut self, write: bool) -> &mut Self { + self.0.write(write); + self + } + + /// Sets the option for the append mode. + pub fn append(&mut self, append: bool) -> &mut Self { + self.0.append(append); + self + } + + /// Sets the option for truncating a previous file. + pub fn truncate(&mut self, truncate: bool) -> &mut Self { + self.0.truncate(truncate); + self + } + + /// Sets the option to create a new file, or open it if it already exists. + pub fn create(&mut self, create: bool) -> &mut Self { + self.0.create(create); + self + } + + /// Sets the option to create a new file, failing if it already exists. + pub fn create_new(&mut self, create_new: bool) -> &mut Self { + self.0.create_new(create_new); + self + } + + /// Opens a file at `path` with the options specified by `self`. + pub fn open(&self, path: &str) -> Result { + fops::File::open(path, &self.0).map(|inner| File { inner }) + } +} + +impl Metadata { + /// Returns the file type for this metadata. + pub const fn file_type(&self) -> FileType { + self.0.file_type() + } + + /// Returns `true` if this metadata is for a directory. The + /// result is mutually exclusive to the result of + /// [`Metadata::is_file`]. + pub const fn is_dir(&self) -> bool { + self.0.is_dir() + } + + /// Returns `true` if this metadata is for a regular file. The + /// result is mutually exclusive to the result of + /// [`Metadata::is_dir`]. + pub const fn is_file(&self) -> bool { + self.0.is_file() + } + + /// Returns the size of the file, in bytes, this metadata is for. + #[allow(clippy::len_without_is_empty)] + pub const fn len(&self) -> u64 { + self.0.size() + } + + /// Returns the permissions of the file this metadata is for. + pub const fn permissions(&self) -> Permissions { + self.0.perm() + } + + /// Returns the total size of this file in bytes. + pub const fn size(&self) -> u64 { + self.0.size() + } + + /// Returns the number of blocks allocated to the file, in 512-byte units. + pub const fn blocks(&self) -> u64 { + self.0.blocks() + } +} + +impl fmt::Debug for Metadata { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Metadata") + .field("file_type", &self.file_type()) + .field("is_dir", &self.is_dir()) + .field("is_file", &self.is_file()) + .field("permissions", &self.permissions()) + .finish_non_exhaustive() + } +} + +impl File { + /// Attempts to open a file in read-only mode. + pub fn open(path: &str) -> Result { + OpenOptions::new().read(true).open(path) + } + + /// Opens a file in write-only mode. + pub fn create(path: &str) -> Result { + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path) + } + + /// Creates a new file in read-write mode; error if the file exists. + pub fn create_new(path: &str) -> Result { + OpenOptions::new() + .read(true) + .write(true) + .create_new(true) + .open(path) + } + + /// Returns a new OpenOptions object. + pub fn options() -> OpenOptions { + OpenOptions::new() + } + + /// Truncates or extends the underlying file, updating the size of + /// this file to become `size`. + pub fn set_len(&self, size: u64) -> Result<()> { + self.inner.truncate(size) + } + + /// Queries metadata about the underlying file. + pub fn metadata(&self) -> Result { + self.inner.get_attr().map(Metadata) + } +} + +impl Read for File { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.read(buf) + } +} + +impl Write for File { + fn write(&mut self, buf: &[u8]) -> Result { + self.inner.write(buf) + } + + fn flush(&mut self) -> Result<()> { + self.inner.flush() + } +} + +impl Seek for File { + fn seek(&mut self, pos: SeekFrom) -> Result { + self.inner.seek(pos) + } +} diff --git a/modules/axfs/src/api/mod.rs b/modules/axfs/src/api/mod.rs new file mode 100644 index 000000000..c8fb3aa30 --- /dev/null +++ b/modules/axfs/src/api/mod.rs @@ -0,0 +1,89 @@ +//! [`std::fs`]-like high-level filesystem manipulation operations. + +mod dir; +mod file; + +pub use self::dir::{DirBuilder, DirEntry, ReadDir}; +pub use self::file::{File, FileType, Metadata, OpenOptions, Permissions}; + +use alloc::{string::String, vec::Vec}; +use axio::{self as io, prelude::*}; + +/// Returns an iterator over the entries within a directory. +pub fn read_dir(path: &str) -> io::Result { + ReadDir::new(path) +} + +/// Returns the canonical, absolute form of a path with all intermediate +/// components normalized. +pub fn canonicalize(path: &str) -> io::Result { + crate::root::absolute_path(path) +} + +/// Returns the current working directory as a [`String`]. +pub fn current_dir() -> io::Result { + crate::root::current_dir() +} + +/// Changes the current working directory to the specified path. +pub fn set_current_dir(path: &str) -> io::Result<()> { + crate::root::set_current_dir(path) +} + +/// Read the entire contents of a file into a bytes vector. +pub fn read(path: &str) -> io::Result> { + let mut file = File::open(path)?; + let size = file.metadata().map(|m| m.len()).unwrap_or(0); + let mut bytes = Vec::with_capacity(size as usize); + file.read_to_end(&mut bytes)?; + Ok(bytes) +} + +/// Read the entire contents of a file into a string. +pub fn read_to_string(path: &str) -> io::Result { + let mut file = File::open(path)?; + let size = file.metadata().map(|m| m.len()).unwrap_or(0); + let mut string = String::with_capacity(size as usize); + file.read_to_string(&mut string)?; + Ok(string) +} + +/// Write a slice as the entire contents of a file. +pub fn write>(path: &str, contents: C) -> io::Result<()> { + File::create(path)?.write_all(contents.as_ref()) +} + +/// Given a path, query the file system to get information about a file, +/// directory, etc. +pub fn metadata(path: &str) -> io::Result { + File::open(path)?.metadata() +} + +/// Creates a new, empty directory at the provided path. +pub fn create_dir(path: &str) -> io::Result<()> { + DirBuilder::new().create(path) +} + +/// Recursively create a directory and all of its parent components if they +/// are missing. +pub fn create_dir_all(path: &str) -> io::Result<()> { + DirBuilder::new().recursive(true).create(path) +} + +/// Removes an empty directory. +pub fn remove_dir(path: &str) -> io::Result<()> { + crate::root::remove_dir(None, path) +} + +/// Removes a file from the filesystem. +pub fn remove_file(path: &str) -> io::Result<()> { + crate::root::remove_file(None, path) +} + +/// Rename a file or directory to a new name. +/// Delete the original file if `old` already exists. +/// +/// This only works then the new path is in the same mounted fs. +pub fn rename(old: &str, new: &str) -> io::Result<()> { + crate::root::rename(old, new) +} diff --git a/modules/axfs/src/dev.rs b/modules/axfs/src/dev.rs new file mode 100644 index 000000000..47cc2d2f3 --- /dev/null +++ b/modules/axfs/src/dev.rs @@ -0,0 +1,92 @@ +use axdriver::prelude::*; + +const BLOCK_SIZE: usize = 512; + +/// A disk device with a cursor. +pub struct Disk { + block_id: u64, + offset: usize, + dev: AxBlockDevice, +} + +impl Disk { + /// Create a new disk. + pub fn new(dev: AxBlockDevice) -> Self { + assert_eq!(BLOCK_SIZE, dev.block_size()); + Self { + block_id: 0, + offset: 0, + dev, + } + } + + /// Get the size of the disk. + pub fn size(&self) -> u64 { + self.dev.num_blocks() * BLOCK_SIZE as u64 + } + + /// Get the position of the cursor. + pub fn position(&self) -> u64 { + self.block_id * BLOCK_SIZE as u64 + self.offset as u64 + } + + /// Set the position of the cursor. + pub fn set_position(&mut self, pos: u64) { + self.block_id = pos / BLOCK_SIZE as u64; + self.offset = pos as usize % BLOCK_SIZE; + } + + /// Read within one block, returns the number of bytes read. + pub fn read_one(&mut self, buf: &mut [u8]) -> DevResult { + let read_size = if self.offset == 0 && buf.len() >= BLOCK_SIZE { + // whole block + self.dev + .read_block(self.block_id, &mut buf[0..BLOCK_SIZE])?; + self.block_id += 1; + BLOCK_SIZE + } else { + // partial block + let mut data = [0u8; BLOCK_SIZE]; + let start = self.offset; + let count = buf.len().min(BLOCK_SIZE - self.offset); + + self.dev.read_block(self.block_id, &mut data)?; + buf[..count].copy_from_slice(&data[start..start + count]); + + self.offset += count; + if self.offset >= BLOCK_SIZE { + self.block_id += 1; + self.offset -= BLOCK_SIZE; + } + count + }; + Ok(read_size) + } + + /// Write within one block, returns the number of bytes written. + pub fn write_one(&mut self, buf: &[u8]) -> DevResult { + let write_size = if self.offset == 0 && buf.len() >= BLOCK_SIZE { + // whole block + self.dev.write_block(self.block_id, &buf[0..BLOCK_SIZE])?; + self.block_id += 1; + BLOCK_SIZE + } else { + // partial block + let mut data = [0u8; BLOCK_SIZE]; + let start = self.offset; + let count = buf.len().min(BLOCK_SIZE - self.offset); + + self.dev.read_block(self.block_id, &mut data)?; + data[start..start + count].copy_from_slice(&buf[..count]); + self.dev.write_block(self.block_id, &data)?; + + self.offset += count; + if self.offset >= BLOCK_SIZE { + self.block_id += 1; + self.offset -= BLOCK_SIZE; + } + count + }; + Ok(write_size) + } +} diff --git a/modules/axfs/src/fops.rs b/modules/axfs/src/fops.rs new file mode 100644 index 000000000..dea021e2a --- /dev/null +++ b/modules/axfs/src/fops.rs @@ -0,0 +1,403 @@ +//! Low-level filesystem operations. + +use axerrno::{ax_err, ax_err_type, AxResult}; +use axfs_vfs::{VfsError, VfsNodeRef}; +use axio::SeekFrom; +use capability::{Cap, WithCap}; +use core::fmt; + +#[cfg(feature = "myfs")] +pub use crate::dev::Disk; +#[cfg(feature = "myfs")] +pub use crate::fs::myfs::MyFileSystemIf; + +/// Alias of [`axfs_vfs::VfsNodeType`]. +pub type FileType = axfs_vfs::VfsNodeType; +/// Alias of [`axfs_vfs::VfsDirEntry`]. +pub type DirEntry = axfs_vfs::VfsDirEntry; +/// Alias of [`axfs_vfs::VfsNodeAttr`]. +pub type FileAttr = axfs_vfs::VfsNodeAttr; +/// Alias of [`axfs_vfs::VfsNodePerm`]. +pub type FilePerm = axfs_vfs::VfsNodePerm; + +/// An opened file object, with open permissions and a cursor. +pub struct File { + node: WithCap, + is_append: bool, + offset: u64, +} + +/// An opened directory object, with open permissions and a cursor for +/// [`read_dir`](Directory::read_dir). +pub struct Directory { + node: WithCap, + entry_idx: usize, +} + +/// Options and flags which can be used to configure how a file is opened. +#[derive(Clone)] +pub struct OpenOptions { + // generic + read: bool, + write: bool, + append: bool, + truncate: bool, + create: bool, + create_new: bool, + // system-specific + _custom_flags: i32, + _mode: u32, +} + +impl OpenOptions { + /// Creates a blank new set of options ready for configuration. + pub const fn new() -> Self { + Self { + // generic + read: false, + write: false, + append: false, + truncate: false, + create: false, + create_new: false, + // system-specific + _custom_flags: 0, + _mode: 0o666, + } + } + /// Sets the option for read access. + pub fn read(&mut self, read: bool) { + self.read = read; + } + /// Sets the option for write access. + pub fn write(&mut self, write: bool) { + self.write = write; + } + /// Sets the option for the append mode. + pub fn append(&mut self, append: bool) { + self.append = append; + } + /// Sets the option for truncating a previous file. + pub fn truncate(&mut self, truncate: bool) { + self.truncate = truncate; + } + /// Sets the option to create a new file, or open it if it already exists. + pub fn create(&mut self, create: bool) { + self.create = create; + } + /// Sets the option to create a new file, failing if it already exists. + pub fn create_new(&mut self, create_new: bool) { + self.create_new = create_new; + } + + const fn is_valid(&self) -> bool { + if !self.read && !self.write && !self.append { + return false; + } + match (self.write, self.append) { + (true, false) => {} + (false, false) => { + if self.truncate || self.create || self.create_new { + return false; + } + } + (_, true) => { + if self.truncate && !self.create_new { + return false; + } + } + } + true + } +} + +impl File { + fn _open_at(dir: Option<&VfsNodeRef>, path: &str, opts: &OpenOptions) -> AxResult { + debug!("open file: {} {:?}", path, opts); + if !opts.is_valid() { + return ax_err!(InvalidInput); + } + + let node_option = crate::root::lookup(dir, path); + let node = if opts.create || opts.create_new { + match node_option { + Ok(node) => { + // already exists + if opts.create_new { + return ax_err!(AlreadyExists); + } + node + } + // not exists, create new + Err(VfsError::NotFound) => crate::root::create_file(dir, path)?, + Err(e) => return Err(e), + } + } else { + // just open the existing + node_option? + }; + + let attr = node.get_attr()?; + if attr.is_dir() + && (opts.create || opts.create_new || opts.write || opts.append || opts.truncate) + { + return ax_err!(IsADirectory); + } + let access_cap = opts.into(); + if !perm_to_cap(attr.perm()).contains(access_cap) { + return ax_err!(PermissionDenied); + } + + node.open()?; + if opts.truncate { + node.truncate(0)?; + } + Ok(Self { + node: WithCap::new(node, access_cap), + is_append: opts.append, + offset: 0, + }) + } + + /// Opens a file at the path relative to the current directory. Returns a + /// [`File`] object. + pub fn open(path: &str, opts: &OpenOptions) -> AxResult { + Self::_open_at(None, path, opts) + } + + /// Truncates the file to the specified size. + pub fn truncate(&self, size: u64) -> AxResult { + self.node.access(Cap::WRITE)?.truncate(size)?; + Ok(()) + } + + /// Reads the file at the current position. Returns the number of bytes + /// read. + /// + /// After the read, the cursor will be advanced by the number of bytes read. + pub fn read(&mut self, buf: &mut [u8]) -> AxResult { + let node = self.node.access(Cap::READ)?; + let read_len = node.read_at(self.offset, buf)?; + self.offset += read_len as u64; + Ok(read_len) + } + + /// Reads the file at the given position. Returns the number of bytes read. + /// + /// It does not update the file cursor. + pub fn read_at(&self, offset: u64, buf: &mut [u8]) -> AxResult { + let node = self.node.access(Cap::READ)?; + let read_len = node.read_at(offset, buf)?; + Ok(read_len) + } + + /// Writes the file at the current position. Returns the number of bytes + /// written. + /// + /// After the write, the cursor will be advanced by the number of bytes + /// written. + pub fn write(&mut self, buf: &[u8]) -> AxResult { + let node = self.node.access(Cap::WRITE)?; + if self.is_append { + self.offset = self.get_attr()?.size(); + }; + let write_len = node.write_at(self.offset, buf)?; + self.offset += write_len as u64; + Ok(write_len) + } + + /// Writes the file at the given position. Returns the number of bytes + /// written. + /// + /// It does not update the file cursor. + pub fn write_at(&self, offset: u64, buf: &[u8]) -> AxResult { + let node = self.node.access(Cap::WRITE)?; + let write_len = node.write_at(offset, buf)?; + Ok(write_len) + } + + /// Flushes the file, writes all buffered data to the underlying device. + pub fn flush(&self) -> AxResult { + self.node.access(Cap::WRITE)?.fsync()?; + Ok(()) + } + + /// Sets the cursor of the file to the specified offset. Returns the new + /// position after the seek. + pub fn seek(&mut self, pos: SeekFrom) -> AxResult { + let size = self.get_attr()?.size(); + let new_offset = match pos { + SeekFrom::Start(pos) => Some(pos), + SeekFrom::Current(off) => self.offset.checked_add_signed(off), + SeekFrom::End(off) => size.checked_add_signed(off), + } + .ok_or_else(|| ax_err_type!(InvalidInput))?; + self.offset = new_offset; + Ok(new_offset) + } + + /// Gets the file attributes. + pub fn get_attr(&self) -> AxResult { + self.node.access(Cap::empty())?.get_attr() + } +} + +impl Directory { + fn _open_dir_at(dir: Option<&VfsNodeRef>, path: &str, opts: &OpenOptions) -> AxResult { + debug!("open dir: {}", path); + if !opts.read { + return ax_err!(InvalidInput); + } + if opts.create || opts.create_new || opts.write || opts.append || opts.truncate { + return ax_err!(InvalidInput); + } + + let node = crate::root::lookup(dir, path)?; + let attr = node.get_attr()?; + if !attr.is_dir() { + return ax_err!(NotADirectory); + } + let access_cap = opts.into(); + if !perm_to_cap(attr.perm()).contains(access_cap) { + return ax_err!(PermissionDenied); + } + + node.open()?; + Ok(Self { + node: WithCap::new(node, access_cap), + entry_idx: 0, + }) + } + + fn access_at(&self, path: &str) -> AxResult> { + if path.starts_with('/') { + Ok(None) + } else { + Ok(Some(self.node.access(Cap::EXECUTE)?)) + } + } + + /// Opens a directory at the path relative to the current directory. + /// Returns a [`Directory`] object. + pub fn open_dir(path: &str, opts: &OpenOptions) -> AxResult { + Self::_open_dir_at(None, path, opts) + } + + /// Opens a directory at the path relative to this directory. Returns a + /// [`Directory`] object. + pub fn open_dir_at(&self, path: &str, opts: &OpenOptions) -> AxResult { + Self::_open_dir_at(self.access_at(path)?, path, opts) + } + + /// Opens a file at the path relative to this directory. Returns a [`File`] + /// object. + pub fn open_file_at(&self, path: &str, opts: &OpenOptions) -> AxResult { + File::_open_at(self.access_at(path)?, path, opts) + } + + /// Creates an empty file at the path relative to this directory. + pub fn create_file(&self, path: &str) -> AxResult { + crate::root::create_file(self.access_at(path)?, path) + } + + /// Creates an empty directory at the path relative to this directory. + pub fn create_dir(&self, path: &str) -> AxResult { + crate::root::create_dir(self.access_at(path)?, path) + } + + /// Removes a file at the path relative to this directory. + pub fn remove_file(&self, path: &str) -> AxResult { + crate::root::remove_file(self.access_at(path)?, path) + } + + /// Removes a directory at the path relative to this directory. + pub fn remove_dir(&self, path: &str) -> AxResult { + crate::root::remove_dir(self.access_at(path)?, path) + } + + /// Reads directory entries starts from the current position into the + /// given buffer. Returns the number of entries read. + /// + /// After the read, the cursor will be advanced by the number of entries + /// read. + pub fn read_dir(&mut self, dirents: &mut [DirEntry]) -> AxResult { + let n = self + .node + .access(Cap::READ)? + .read_dir(self.entry_idx, dirents)?; + self.entry_idx += n; + Ok(n) + } + + /// Rename a file or directory to a new name. + /// Delete the original file if `old` already exists. + /// + /// This only works then the new path is in the same mounted fs. + pub fn rename(&self, old: &str, new: &str) -> AxResult { + crate::root::rename(old, new) + } +} + +impl Drop for File { + fn drop(&mut self) { + unsafe { self.node.access_unchecked().release().ok() }; + } +} + +impl Drop for Directory { + fn drop(&mut self) { + unsafe { self.node.access_unchecked().release().ok() }; + } +} + +impl fmt::Debug for OpenOptions { + #[allow(unused_assignments)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut written = false; + macro_rules! fmt_opt { + ($field: ident, $label: literal) => { + if self.$field { + if written { + write!(f, " | ")?; + } + write!(f, $label)?; + written = true; + } + }; + } + fmt_opt!(read, "READ"); + fmt_opt!(write, "WRITE"); + fmt_opt!(append, "APPEND"); + fmt_opt!(truncate, "TRUNC"); + fmt_opt!(create, "CREATE"); + fmt_opt!(create_new, "CREATE_NEW"); + Ok(()) + } +} + +impl From<&OpenOptions> for Cap { + fn from(opts: &OpenOptions) -> Cap { + let mut cap = Cap::empty(); + if opts.read { + cap |= Cap::READ; + } + if opts.write | opts.append { + cap |= Cap::WRITE; + } + cap + } +} + +fn perm_to_cap(perm: FilePerm) -> Cap { + let mut cap = Cap::empty(); + if perm.owner_readable() { + cap |= Cap::READ; + } + if perm.owner_writable() { + cap |= Cap::WRITE; + } + if perm.owner_executable() { + cap |= Cap::EXECUTE; + } + cap +} diff --git a/modules/axfs/src/fs/fatfs.rs b/modules/axfs/src/fs/fatfs.rs new file mode 100644 index 000000000..5d0c4c813 --- /dev/null +++ b/modules/axfs/src/fs/fatfs.rs @@ -0,0 +1,283 @@ +use alloc::sync::Arc; +use core::cell::UnsafeCell; + +use axfs_vfs::{VfsDirEntry, VfsError, VfsNodePerm, VfsResult}; +use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps}; +use axsync::Mutex; +use fatfs::{Dir, File, LossyOemCpConverter, NullTimeProvider, Read, Seek, SeekFrom, Write}; + +use crate::dev::Disk; + +const BLOCK_SIZE: usize = 512; + +pub struct FatFileSystem { + inner: fatfs::FileSystem, + root_dir: UnsafeCell>, +} + +pub struct FileWrapper<'a>(Mutex>); +pub struct DirWrapper<'a>(Dir<'a, Disk, NullTimeProvider, LossyOemCpConverter>); + +unsafe impl Sync for FatFileSystem {} +unsafe impl Send for FatFileSystem {} +unsafe impl<'a> Send for FileWrapper<'a> {} +unsafe impl<'a> Sync for FileWrapper<'a> {} +unsafe impl<'a> Send for DirWrapper<'a> {} +unsafe impl<'a> Sync for DirWrapper<'a> {} + +impl FatFileSystem { + #[cfg(feature = "use-ramdisk")] + pub fn new(mut disk: Disk) -> Self { + let opts = fatfs::FormatVolumeOptions::new(); + fatfs::format_volume(&mut disk, opts).expect("failed to format volume"); + let inner = fatfs::FileSystem::new(disk, fatfs::FsOptions::new()) + .expect("failed to initialize FAT filesystem"); + Self { + inner, + root_dir: UnsafeCell::new(None), + } + } + + #[cfg(not(feature = "use-ramdisk"))] + pub fn new(disk: Disk) -> Self { + let inner = fatfs::FileSystem::new(disk, fatfs::FsOptions::new()) + .expect("failed to initialize FAT filesystem"); + Self { + inner, + root_dir: UnsafeCell::new(None), + } + } + + pub fn init(&'static self) { + // must be called before later operations + unsafe { *self.root_dir.get() = Some(Self::new_dir(self.inner.root_dir())) } + } + + fn new_file(file: File<'_, Disk, NullTimeProvider, LossyOemCpConverter>) -> Arc { + Arc::new(FileWrapper(Mutex::new(file))) + } + + fn new_dir(dir: Dir<'_, Disk, NullTimeProvider, LossyOemCpConverter>) -> Arc { + Arc::new(DirWrapper(dir)) + } +} + +impl VfsNodeOps for FileWrapper<'static> { + axfs_vfs::impl_vfs_non_dir_default! {} + + fn get_attr(&self) -> VfsResult { + let size = self.0.lock().seek(SeekFrom::End(0)).map_err(as_vfs_err)?; + let blocks = (size + BLOCK_SIZE as u64 - 1) / BLOCK_SIZE as u64; + // FAT fs doesn't support permissions, we just set everything to 755 + let perm = VfsNodePerm::from_bits_truncate(0o755); + Ok(VfsNodeAttr::new(perm, VfsNodeType::File, size, blocks)) + } + + fn read_at(&self, offset: u64, buf: &mut [u8]) -> VfsResult { + let mut file = self.0.lock(); + file.seek(SeekFrom::Start(offset)).map_err(as_vfs_err)?; // TODO: more efficient + file.read(buf).map_err(as_vfs_err) + } + + fn write_at(&self, offset: u64, buf: &[u8]) -> VfsResult { + let mut file = self.0.lock(); + file.seek(SeekFrom::Start(offset)).map_err(as_vfs_err)?; // TODO: more efficient + file.write(buf).map_err(as_vfs_err) + } + + fn truncate(&self, size: u64) -> VfsResult { + let mut file = self.0.lock(); + file.seek(SeekFrom::Start(size)).map_err(as_vfs_err)?; // TODO: more efficient + file.truncate().map_err(as_vfs_err) + } +} + +impl VfsNodeOps for DirWrapper<'static> { + axfs_vfs::impl_vfs_dir_default! {} + + fn get_attr(&self) -> VfsResult { + // FAT fs doesn't support permissions, we just set everything to 755 + Ok(VfsNodeAttr::new( + VfsNodePerm::from_bits_truncate(0o755), + VfsNodeType::Dir, + BLOCK_SIZE as u64, + 1, + )) + } + + fn parent(&self) -> Option { + self.0 + .open_dir("..") + .map_or(None, |dir| Some(FatFileSystem::new_dir(dir))) + } + + fn lookup(self: Arc, path: &str) -> VfsResult { + debug!("lookup at fatfs: {}", path); + let path = path.trim_matches('/'); + if path.is_empty() || path == "." { + return Ok(self.clone()); + } + if let Some(rest) = path.strip_prefix("./") { + return self.lookup(rest); + } + + // TODO: use `fatfs::Dir::find_entry`, but it's not public. + if let Ok(file) = self.0.open_file(path) { + Ok(FatFileSystem::new_file(file)) + } else if let Ok(dir) = self.0.open_dir(path) { + Ok(FatFileSystem::new_dir(dir)) + } else { + Err(VfsError::NotFound) + } + } + + fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { + debug!("create {:?} at fatfs: {}", ty, path); + let path = path.trim_matches('/'); + if path.is_empty() || path == "." { + return Ok(()); + } + if let Some(rest) = path.strip_prefix("./") { + return self.create(rest, ty); + } + + match ty { + VfsNodeType::File => { + self.0.create_file(path).map_err(as_vfs_err)?; + Ok(()) + } + VfsNodeType::Dir => { + self.0.create_dir(path).map_err(as_vfs_err)?; + Ok(()) + } + _ => Err(VfsError::Unsupported), + } + } + + fn remove(&self, path: &str) -> VfsResult { + debug!("remove at fatfs: {}", path); + let path = path.trim_matches('/'); + assert!(!path.is_empty()); // already check at `root.rs` + if let Some(rest) = path.strip_prefix("./") { + return self.remove(rest); + } + self.0.remove(path).map_err(as_vfs_err) + } + + fn read_dir(&self, start_idx: usize, dirents: &mut [VfsDirEntry]) -> VfsResult { + let mut iter = self.0.iter().skip(start_idx); + for (i, out_entry) in dirents.iter_mut().enumerate() { + let x = iter.next(); + match x { + Some(Ok(entry)) => { + let ty = if entry.is_dir() { + VfsNodeType::Dir + } else if entry.is_file() { + VfsNodeType::File + } else { + unreachable!() + }; + *out_entry = VfsDirEntry::new(&entry.file_name(), ty); + } + _ => return Ok(i), + } + } + Ok(dirents.len()) + } + + fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult { + // `src_path` and `dst_path` should in the same mounted fs + debug!( + "rename at fatfs, src_path: {}, dst_path: {}", + src_path, dst_path + ); + + self.0 + .rename(src_path, &self.0, dst_path) + .map_err(as_vfs_err) + } +} + +impl VfsOps for FatFileSystem { + fn root_dir(&self) -> VfsNodeRef { + let root_dir = unsafe { (*self.root_dir.get()).as_ref().unwrap() }; + root_dir.clone() + } +} + +impl fatfs::IoBase for Disk { + type Error = (); +} + +impl Read for Disk { + fn read(&mut self, mut buf: &mut [u8]) -> Result { + let mut read_len = 0; + while !buf.is_empty() { + match self.read_one(buf) { + Ok(0) => break, + Ok(n) => { + let tmp = buf; + buf = &mut tmp[n..]; + read_len += n; + } + Err(_) => return Err(()), + } + } + Ok(read_len) + } +} + +impl Write for Disk { + fn write(&mut self, mut buf: &[u8]) -> Result { + let mut write_len = 0; + while !buf.is_empty() { + match self.write_one(buf) { + Ok(0) => break, + Ok(n) => { + buf = &buf[n..]; + write_len += n; + } + Err(_) => return Err(()), + } + } + Ok(write_len) + } + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl Seek for Disk { + fn seek(&mut self, pos: SeekFrom) -> Result { + let size = self.size(); + let new_pos = match pos { + SeekFrom::Start(pos) => Some(pos), + SeekFrom::Current(off) => self.position().checked_add_signed(off), + SeekFrom::End(off) => size.checked_add_signed(off), + } + .ok_or(())?; + if new_pos > size { + warn!("Seek beyond the end of the block device"); + } + self.set_position(new_pos); + Ok(new_pos) + } +} + +const fn as_vfs_err(err: fatfs::Error<()>) -> VfsError { + use fatfs::Error::*; + match err { + AlreadyExists => VfsError::AlreadyExists, + CorruptedFileSystem => VfsError::InvalidData, + DirectoryIsNotEmpty => VfsError::DirectoryNotEmpty, + InvalidInput | InvalidFileNameLength | UnsupportedFileNameCharacter => { + VfsError::InvalidInput + } + NotEnoughSpace => VfsError::StorageFull, + NotFound => VfsError::NotFound, + UnexpectedEof => VfsError::UnexpectedEof, + WriteZero => VfsError::WriteZero, + Io(_) => VfsError::Io, + _ => VfsError::Io, + } +} diff --git a/modules/axfs/src/fs/mod.rs b/modules/axfs/src/fs/mod.rs new file mode 100644 index 000000000..77aad60f2 --- /dev/null +++ b/modules/axfs/src/fs/mod.rs @@ -0,0 +1,13 @@ +cfg_if::cfg_if! { + if #[cfg(feature = "myfs")] { + pub mod myfs; + } else if #[cfg(feature = "fatfs")] { + pub mod fatfs; + } +} + +#[cfg(feature = "devfs")] +pub use axfs_devfs as devfs; + +#[cfg(feature = "ramfs")] +pub use axfs_ramfs as ramfs; diff --git a/modules/axfs/src/fs/myfs.rs b/modules/axfs/src/fs/myfs.rs new file mode 100644 index 000000000..ca24fd5c7 --- /dev/null +++ b/modules/axfs/src/fs/myfs.rs @@ -0,0 +1,16 @@ +use crate::dev::Disk; +use alloc::sync::Arc; +use axfs_vfs::VfsOps; + +/// The interface to define custom filesystems in user apps. +#[crate_interface::def_interface] +pub trait MyFileSystemIf { + /// Creates a new instance of the filesystem with initialization. + /// + /// TODO: use generic disk type + fn new_myfs(disk: Disk) -> Arc; +} + +pub(crate) fn new_myfs(disk: Disk) -> Arc { + crate_interface::call_interface!(MyFileSystemIf::new_myfs(disk)) +} diff --git a/modules/axfs/src/lib.rs b/modules/axfs/src/lib.rs new file mode 100644 index 000000000..07baa2a7d --- /dev/null +++ b/modules/axfs/src/lib.rs @@ -0,0 +1,46 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) filesystem module. +//! +//! It provides unified filesystem operations for various filesystems. +//! +//! # Cargo Features +//! +//! - `fatfs`: Use [FAT] as the main filesystem and mount it on `/`. This feature +//! is **enabled** by default. +//! - `devfs`: Mount [`axfs_devfs::DeviceFileSystem`] on `/dev`. This feature is +//! **enabled** by default. +//! - `ramfs`: Mount [`axfs_ramfs::RamFileSystem`] on `/tmp`. This feature is +//! **enabled** by default. +//! - `myfs`: Allow users to define their custom filesystems to override the +//! default. In this case, [`MyFileSystemIf`] is required to be implemented +//! to create and initialize other filesystems. This feature is **disabled** by +//! by default, but it will override other filesystem selection features if +//! both are enabled. +//! +//! [FAT]: https://en.wikipedia.org/wiki/File_Allocation_Table +//! [`MyFileSystemIf`]: fops::MyFileSystemIf + +#![cfg_attr(all(not(test), not(doc)), no_std)] +#![feature(doc_auto_cfg)] + +#[macro_use] +extern crate log; +extern crate alloc; + +mod dev; +mod fs; +mod mounts; +mod root; + +pub mod api; +pub mod fops; + +use axdriver::{prelude::*, AxDeviceContainer}; + +/// Initializes filesystems by block devices. +pub fn init_filesystems(mut blk_devs: AxDeviceContainer) { + info!("Initialize filesystems..."); + + let dev = blk_devs.take_one().expect("No block device found!"); + info!(" use block device 0: {:?}", dev.device_name()); + self::root::init_rootfs(self::dev::Disk::new(dev)); +} diff --git a/modules/axfs/src/mounts.rs b/modules/axfs/src/mounts.rs new file mode 100644 index 000000000..d3a0e5093 --- /dev/null +++ b/modules/axfs/src/mounts.rs @@ -0,0 +1,80 @@ +use alloc::sync::Arc; +use axfs_vfs::{VfsNodeType, VfsOps, VfsResult}; + +use crate::fs; + +#[cfg(feature = "devfs")] +pub(crate) fn devfs() -> Arc { + let null = fs::devfs::NullDev; + let zero = fs::devfs::ZeroDev; + let bar = fs::devfs::ZeroDev; + let devfs = fs::devfs::DeviceFileSystem::new(); + let foo_dir = devfs.mkdir("foo"); + devfs.add("null", Arc::new(null)); + devfs.add("zero", Arc::new(zero)); + foo_dir.add("bar", Arc::new(bar)); + Arc::new(devfs) +} + +#[cfg(feature = "ramfs")] +pub(crate) fn ramfs() -> Arc { + Arc::new(fs::ramfs::RamFileSystem::new()) +} + +#[cfg(feature = "procfs")] +pub(crate) fn procfs() -> VfsResult> { + let procfs = fs::ramfs::RamFileSystem::new(); + let proc_root = procfs.root_dir(); + + // Create /proc/sys/net/core/somaxconn + proc_root.create("sys", VfsNodeType::Dir)?; + proc_root.create("sys/net", VfsNodeType::Dir)?; + proc_root.create("sys/net/core", VfsNodeType::Dir)?; + proc_root.create("sys/net/core/somaxconn", VfsNodeType::File)?; + let file_somaxconn = proc_root.clone().lookup("./sys/net/core/somaxconn")?; + file_somaxconn.write_at(0, b"4096\n")?; + + // Create /proc/sys/vm/overcommit_memory + proc_root.create("sys/vm", VfsNodeType::Dir)?; + proc_root.create("sys/vm/overcommit_memory", VfsNodeType::File)?; + let file_over = proc_root.clone().lookup("./sys/vm/overcommit_memory")?; + file_over.write_at(0, b"0\n")?; + + // Create /proc/self/stat + proc_root.create("self", VfsNodeType::Dir)?; + proc_root.create("self/stat", VfsNodeType::File)?; + + Ok(Arc::new(procfs)) +} + +#[cfg(feature = "sysfs")] +pub(crate) fn sysfs() -> VfsResult> { + let sysfs = fs::ramfs::RamFileSystem::new(); + let sys_root = sysfs.root_dir(); + + // Create /sys/kernel/mm/transparent_hugepage/enabled + sys_root.create("kernel", VfsNodeType::Dir)?; + sys_root.create("kernel/mm", VfsNodeType::Dir)?; + sys_root.create("kernel/mm/transparent_hugepage", VfsNodeType::Dir)?; + sys_root.create("kernel/mm/transparent_hugepage/enabled", VfsNodeType::File)?; + let file_hp = sys_root + .clone() + .lookup("./kernel/mm/transparent_hugepage/enabled")?; + file_hp.write_at(0, b"always [madvise] never\n")?; + + // Create /sys/devices/system/clocksource/clocksource0/current_clocksource + sys_root.create("devices", VfsNodeType::Dir)?; + sys_root.create("devices/system", VfsNodeType::Dir)?; + sys_root.create("devices/system/clocksource", VfsNodeType::Dir)?; + sys_root.create("devices/system/clocksource/clocksource0", VfsNodeType::Dir)?; + sys_root.create( + "devices/system/clocksource/clocksource0/current_clocksource", + VfsNodeType::File, + )?; + let file_cc = sys_root + .clone() + .lookup("devices/system/clocksource/clocksource0/current_clocksource")?; + file_cc.write_at(0, b"tsc\n")?; + + Ok(Arc::new(sysfs)) +} diff --git a/modules/axfs/src/root.rs b/modules/axfs/src/root.rs new file mode 100644 index 000000000..9bf02dce1 --- /dev/null +++ b/modules/axfs/src/root.rs @@ -0,0 +1,310 @@ +//! Root directory of the filesystem +//! +//! TODO: it doesn't work very well if the mount points have containment relationships. + +use alloc::{string::String, sync::Arc, vec::Vec}; +use axerrno::{ax_err, AxError, AxResult}; +use axfs_vfs::{VfsNodeAttr, VfsNodeOps, VfsNodeRef, VfsNodeType, VfsOps, VfsResult}; +use axsync::Mutex; +use lazy_init::LazyInit; + +use crate::{api::FileType, fs, mounts}; + +static CURRENT_DIR_PATH: Mutex = Mutex::new(String::new()); +static CURRENT_DIR: LazyInit> = LazyInit::new(); + +struct MountPoint { + path: &'static str, + fs: Arc, +} + +struct RootDirectory { + main_fs: Arc, + mounts: Vec, +} + +static ROOT_DIR: LazyInit> = LazyInit::new(); + +impl MountPoint { + pub fn new(path: &'static str, fs: Arc) -> Self { + Self { path, fs } + } +} + +impl Drop for MountPoint { + fn drop(&mut self) { + self.fs.umount().ok(); + } +} + +impl RootDirectory { + pub const fn new(main_fs: Arc) -> Self { + Self { + main_fs, + mounts: Vec::new(), + } + } + + pub fn mount(&mut self, path: &'static str, fs: Arc) -> AxResult { + if path == "/" { + return ax_err!(InvalidInput, "cannot mount root filesystem"); + } + if !path.starts_with('/') { + return ax_err!(InvalidInput, "mount path must start with '/'"); + } + if self.mounts.iter().any(|mp| mp.path == path) { + return ax_err!(InvalidInput, "mount point already exists"); + } + // create the mount point in the main filesystem if it does not exist + self.main_fs.root_dir().create(path, FileType::Dir)?; + fs.mount(path, self.main_fs.root_dir().lookup(path)?)?; + self.mounts.push(MountPoint::new(path, fs)); + Ok(()) + } + + pub fn _umount(&mut self, path: &str) { + self.mounts.retain(|mp| mp.path != path); + } + + pub fn contains(&self, path: &str) -> bool { + self.mounts.iter().any(|mp| mp.path == path) + } + + fn lookup_mounted_fs(&self, path: &str, f: F) -> AxResult + where + F: FnOnce(Arc, &str) -> AxResult, + { + debug!("lookup at root: {}", path); + let path = path.trim_matches('/'); + if let Some(rest) = path.strip_prefix("./") { + return self.lookup_mounted_fs(rest, f); + } + + let mut idx = 0; + let mut max_len = 0; + + // Find the filesystem that has the longest mounted path match + // TODO: more efficient, e.g. trie + for (i, mp) in self.mounts.iter().enumerate() { + // skip the first '/' + if path.starts_with(&mp.path[1..]) && mp.path.len() - 1 > max_len { + max_len = mp.path.len() - 1; + idx = i; + } + } + + if max_len == 0 { + f(self.main_fs.clone(), path) // not matched any mount point + } else { + f(self.mounts[idx].fs.clone(), &path[max_len..]) // matched at `idx` + } + } +} + +impl VfsNodeOps for RootDirectory { + axfs_vfs::impl_vfs_dir_default! {} + + fn get_attr(&self) -> VfsResult { + self.main_fs.root_dir().get_attr() + } + + fn lookup(self: Arc, path: &str) -> VfsResult { + self.lookup_mounted_fs(path, |fs, rest_path| fs.root_dir().lookup(rest_path)) + } + + fn create(&self, path: &str, ty: VfsNodeType) -> VfsResult { + self.lookup_mounted_fs(path, |fs, rest_path| { + if rest_path.is_empty() { + Ok(()) // already exists + } else { + fs.root_dir().create(rest_path, ty) + } + }) + } + + fn remove(&self, path: &str) -> VfsResult { + self.lookup_mounted_fs(path, |fs, rest_path| { + if rest_path.is_empty() { + ax_err!(PermissionDenied) // cannot remove mount points + } else { + fs.root_dir().remove(rest_path) + } + }) + } + + fn rename(&self, src_path: &str, dst_path: &str) -> VfsResult { + self.lookup_mounted_fs(src_path, |fs, rest_path| { + if rest_path.is_empty() { + ax_err!(PermissionDenied) // cannot rename mount points + } else { + fs.root_dir().rename(rest_path, dst_path) + } + }) + } +} + +pub(crate) fn init_rootfs(disk: crate::dev::Disk) { + cfg_if::cfg_if! { + if #[cfg(feature = "myfs")] { // override the default filesystem + let main_fs = fs::myfs::new_myfs(disk); + } else if #[cfg(feature = "fatfs")] { + static FAT_FS: LazyInit> = LazyInit::new(); + FAT_FS.init_by(Arc::new(fs::fatfs::FatFileSystem::new(disk))); + FAT_FS.init(); + let main_fs = FAT_FS.clone(); + } + } + + let mut root_dir = RootDirectory::new(main_fs); + + #[cfg(feature = "devfs")] + root_dir + .mount("/dev", mounts::devfs()) + .expect("failed to mount devfs at /dev"); + + #[cfg(feature = "ramfs")] + root_dir + .mount("/tmp", mounts::ramfs()) + .expect("failed to mount ramfs at /tmp"); + + // Mount another ramfs as procfs + #[cfg(feature = "procfs")] + root_dir // should not fail + .mount("/proc", mounts::procfs().unwrap()) + .expect("fail to mount procfs at /proc"); + + // Mount another ramfs as sysfs + #[cfg(feature = "sysfs")] + root_dir // should not fail + .mount("/sys", mounts::sysfs().unwrap()) + .expect("fail to mount sysfs at /sys"); + + ROOT_DIR.init_by(Arc::new(root_dir)); + CURRENT_DIR.init_by(Mutex::new(ROOT_DIR.clone())); + *CURRENT_DIR_PATH.lock() = "/".into(); +} + +fn parent_node_of(dir: Option<&VfsNodeRef>, path: &str) -> VfsNodeRef { + if path.starts_with('/') { + ROOT_DIR.clone() + } else { + dir.cloned().unwrap_or_else(|| CURRENT_DIR.lock().clone()) + } +} + +pub(crate) fn absolute_path(path: &str) -> AxResult { + if path.starts_with('/') { + Ok(axfs_vfs::path::canonicalize(path)) + } else { + let path = CURRENT_DIR_PATH.lock().clone() + path; + Ok(axfs_vfs::path::canonicalize(&path)) + } +} + +pub(crate) fn lookup(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { + if path.is_empty() { + return ax_err!(NotFound); + } + let node = parent_node_of(dir, path).lookup(path)?; + if path.ends_with('/') && !node.get_attr()?.is_dir() { + ax_err!(NotADirectory) + } else { + Ok(node) + } +} + +pub(crate) fn create_file(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { + if path.is_empty() { + return ax_err!(NotFound); + } else if path.ends_with('/') { + return ax_err!(NotADirectory); + } + let parent = parent_node_of(dir, path); + parent.create(path, VfsNodeType::File)?; + parent.lookup(path) +} + +pub(crate) fn create_dir(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { + match lookup(dir, path) { + Ok(_) => ax_err!(AlreadyExists), + Err(AxError::NotFound) => parent_node_of(dir, path).create(path, VfsNodeType::Dir), + Err(e) => Err(e), + } +} + +pub(crate) fn remove_file(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { + let node = lookup(dir, path)?; + let attr = node.get_attr()?; + if attr.is_dir() { + ax_err!(IsADirectory) + } else if !attr.perm().owner_writable() { + ax_err!(PermissionDenied) + } else { + parent_node_of(dir, path).remove(path) + } +} + +pub(crate) fn remove_dir(dir: Option<&VfsNodeRef>, path: &str) -> AxResult { + if path.is_empty() { + return ax_err!(NotFound); + } + let path_check = path.trim_matches('/'); + if path_check.is_empty() { + return ax_err!(DirectoryNotEmpty); // rm -d '/' + } else if path_check == "." + || path_check == ".." + || path_check.ends_with("/.") + || path_check.ends_with("/..") + { + return ax_err!(InvalidInput); + } + if ROOT_DIR.contains(&absolute_path(path)?) { + return ax_err!(PermissionDenied); + } + + let node = lookup(dir, path)?; + let attr = node.get_attr()?; + if !attr.is_dir() { + ax_err!(NotADirectory) + } else if !attr.perm().owner_writable() { + ax_err!(PermissionDenied) + } else { + parent_node_of(dir, path).remove(path) + } +} + +pub(crate) fn current_dir() -> AxResult { + Ok(CURRENT_DIR_PATH.lock().clone()) +} + +pub(crate) fn set_current_dir(path: &str) -> AxResult { + let mut abs_path = absolute_path(path)?; + if !abs_path.ends_with('/') { + abs_path += "/"; + } + if abs_path == "/" { + *CURRENT_DIR.lock() = ROOT_DIR.clone(); + *CURRENT_DIR_PATH.lock() = "/".into(); + return Ok(()); + } + + let node = lookup(None, &abs_path)?; + let attr = node.get_attr()?; + if !attr.is_dir() { + ax_err!(NotADirectory) + } else if !attr.perm().owner_executable() { + ax_err!(PermissionDenied) + } else { + *CURRENT_DIR.lock() = node; + *CURRENT_DIR_PATH.lock() = abs_path; + Ok(()) + } +} + +pub(crate) fn rename(old: &str, new: &str) -> AxResult { + if parent_node_of(None, new).lookup(new).is_ok() { + warn!("dst file already exist, now remove it"); + remove_file(None, new)?; + } + parent_node_of(None, old).rename(old, new) +} diff --git a/modules/axfs/tests/test_common/mod.rs b/modules/axfs/tests/test_common/mod.rs new file mode 100644 index 000000000..4746e37b4 --- /dev/null +++ b/modules/axfs/tests/test_common/mod.rs @@ -0,0 +1,262 @@ +use axfs::api as fs; +use axio as io; + +use fs::{File, FileType, OpenOptions}; +use io::{prelude::*, Error, Result}; + +macro_rules! assert_err { + ($expr: expr) => { + assert!(($expr).is_err()) + }; + ($expr: expr, $err: ident) => { + assert_eq!(($expr).err(), Some(Error::$err)) + }; +} + +fn test_read_write_file() -> Result<()> { + let fname = "///very/long//.././long//./path/./test.txt"; + println!("read and write file {:?}:", fname); + + // read and write + let mut file = File::options().read(true).write(true).open(fname)?; + let file_size = file.metadata()?.len(); + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + print!("{}", contents); + assert_eq!(contents.len(), file_size as usize); + assert_eq!(file.write(b"Hello, world!\n")?, 14); // append + drop(file); + + // read again and check + let new_contents = fs::read_to_string(fname)?; + print!("{}", new_contents); + assert_eq!(new_contents, contents + "Hello, world!\n"); + + // append and check + let mut file = OpenOptions::new().append(true).open(fname)?; + assert_eq!(file.write(b"new line\n")?, 9); + drop(file); + + let new_contents2 = fs::read_to_string(fname)?; + print!("{}", new_contents2); + assert_eq!(new_contents2, new_contents + "new line\n"); + + // open a non-exist file + assert_err!(File::open("/not/exist/file"), NotFound); + + println!("test_read_write_file() OK!"); + Ok(()) +} + +fn test_read_dir() -> Result<()> { + let dir = "/././//./"; + println!("list directory {:?}:", dir); + for entry in fs::read_dir(dir)? { + let entry = entry?; + println!(" {}", entry.file_name()); + } + println!("test_read_dir() OK!"); + Ok(()) +} + +fn test_file_permission() -> Result<()> { + let fname = "./short.txt"; + println!("test permission {:?}:", fname); + + // write a file that open with read-only mode + let mut buf = [0; 256]; + let mut file = File::open(fname)?; + let n = file.read(&mut buf)?; + assert_err!(file.write(&buf), PermissionDenied); + drop(file); + + // read a file that open with write-only mode + let mut file = File::create(fname)?; + assert_err!(file.read(&mut buf), PermissionDenied); + assert!(file.write(&buf[..n]).is_ok()); + drop(file); + + // open with empty options + assert_err!(OpenOptions::new().open(fname), InvalidInput); + + // read as a directory + assert_err!(fs::read_dir(fname), NotADirectory); + assert_err!(fs::read("short.txt/"), NotADirectory); + assert_err!(fs::metadata("/short.txt/"), NotADirectory); + + // create as a directory + assert_err!(fs::write("error/", "should not create"), NotADirectory); + assert_err!(fs::metadata("error/"), NotFound); + assert_err!(fs::metadata("error"), NotFound); + + // read/write a directory + assert_err!(fs::read_to_string("/dev"), IsADirectory); + assert_err!(fs::write(".", "test"), IsADirectory); + + println!("test_file_permisson() OK!"); + Ok(()) +} + +fn test_create_file_dir() -> Result<()> { + // create a file and test existence + let fname = "././/very-long-dir-name/..///new-file.txt"; + println!("test create file {:?}:", fname); + assert_err!(fs::metadata(fname), NotFound); + let contents = "create a new file!\n"; + fs::write(fname, contents)?; + + let dirents = fs::read_dir(".")? + .map(|e| e.unwrap().file_name()) + .collect::>(); + println!("dirents = {:?}", dirents); + assert!(dirents.contains(&"new-file.txt".into())); + assert_eq!(fs::read_to_string(fname)?, contents); + assert_err!(File::create_new(fname), AlreadyExists); + + // create a directory and test existence + let dirname = "///././/very//.//long/./new-dir"; + println!("test create dir {:?}:", dirname); + assert_err!(fs::metadata(dirname), NotFound); + fs::create_dir(dirname)?; + + let dirents = fs::read_dir("./very/long")? + .map(|e| e.unwrap().file_name()) + .collect::>(); + println!("dirents = {:?}", dirents); + assert!(dirents.contains(&"new-dir".into())); + assert!(fs::metadata(dirname)?.is_dir()); + assert_err!(fs::create_dir(dirname), AlreadyExists); + + println!("test_create_file_dir() OK!"); + Ok(()) +} + +fn test_remove_file_dir() -> Result<()> { + // remove a file and test existence + let fname = "//very-long-dir-name/..///new-file.txt"; + println!("test remove file {:?}:", fname); + assert_err!(fs::remove_dir(fname), NotADirectory); + assert!(fs::remove_file(fname).is_ok()); + assert_err!(fs::metadata(fname), NotFound); + assert_err!(fs::remove_file(fname), NotFound); + + // remove a directory and test existence + let dirname = "very//.//long/../long/.//./new-dir////"; + println!("test remove dir {:?}:", dirname); + assert_err!(fs::remove_file(dirname), IsADirectory); + assert!(fs::remove_dir(dirname).is_ok()); + assert_err!(fs::metadata(dirname), NotFound); + assert_err!(fs::remove_dir(fname), NotFound); + + // error cases + assert_err!(fs::remove_file(""), NotFound); + assert_err!(fs::remove_dir("/"), DirectoryNotEmpty); + assert_err!(fs::remove_dir("."), InvalidInput); + assert_err!(fs::remove_dir("../"), InvalidInput); + assert_err!(fs::remove_dir("./././/"), InvalidInput); + assert_err!(fs::remove_file("///very/./"), IsADirectory); + assert_err!(fs::remove_file("short.txt/"), NotADirectory); + assert_err!(fs::remove_dir(".///"), InvalidInput); + assert_err!(fs::remove_dir("/./very///"), DirectoryNotEmpty); + assert_err!(fs::remove_dir("very/long/.."), InvalidInput); + + println!("test_remove_file_dir() OK!"); + Ok(()) +} + +fn test_devfs_ramfs() -> Result<()> { + const N: usize = 32; + let mut buf = [1; N]; + + // list '/' and check if /dev and /tmp exist + let dirents = fs::read_dir("././//.//")? + .map(|e| e.unwrap().file_name()) + .collect::>(); + assert!(dirents.contains(&"dev".into())); + assert!(dirents.contains(&"tmp".into())); + + // read and write /dev/null + let mut file = File::options().read(true).write(true).open("/dev/./null")?; + assert_eq!(file.read_to_end(&mut Vec::new())?, 0); + assert_eq!(file.write(&buf)?, N); + assert_eq!(buf, [1; N]); + + // read and write /dev/zero + let mut file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open("////dev/zero")?; + assert_eq!(file.read(&mut buf)?, N); + assert!(file.write_all(&buf).is_ok()); + assert_eq!(buf, [0; N]); + + // list /dev + let dirents = fs::read_dir("/dev")? + .map(|e| e.unwrap().file_name()) + .collect::>(); + assert!(dirents.contains(&"null".into())); + assert!(dirents.contains(&"zero".into())); + + // stat /dev + let dname = "/dev"; + let dir = File::open(dname)?; + let md = dir.metadata()?; + println!("metadata of {:?}: {:?}", dname, md); + assert_eq!(md.file_type(), FileType::Dir); + assert!(!md.is_file()); + assert!(md.is_dir()); + + // stat /dev/foo/bar + let fname = ".//.///././/./dev///.///./foo//././bar"; + let file = File::open(fname)?; + let md = file.metadata()?; + println!("metadata of {:?}: {:?}", fname, md); + assert_eq!(md.file_type(), FileType::CharDevice); + assert!(!md.is_dir()); + + // error cases + assert_err!(fs::metadata("/dev/null/"), NotADirectory); + assert_err!(fs::create_dir("dev"), AlreadyExists); + assert_err!(File::create_new("/dev/"), AlreadyExists); + assert_err!(fs::create_dir("/dev/zero"), AlreadyExists); + assert_err!(fs::write("/dev/stdout", "test"), PermissionDenied); + assert_err!(fs::create_dir("/dev/test"), PermissionDenied); + assert_err!(fs::remove_file("/dev/null"), PermissionDenied); + assert_err!(fs::remove_dir("./dev"), PermissionDenied); + assert_err!(fs::remove_dir("./dev/."), InvalidInput); + assert_err!(fs::remove_dir("///dev//..//"), InvalidInput); + + // parent of '/dev' + assert_eq!(fs::create_dir("///dev//..//233//"), Ok(())); + assert_eq!(fs::write(".///dev//..//233//.///test.txt", "test"), Ok(())); + assert_err!(fs::remove_file("./dev//../..//233//.///test.txt"), NotFound); + assert_eq!(fs::remove_file("./dev//..//233//../233/./test.txt"), Ok(())); + assert_eq!(fs::remove_dir("dev//foo/../foo/../.././/233"), Ok(())); + assert_err!(fs::remove_dir("very/../dev//"), PermissionDenied); + + // tests in /tmp + assert_eq!(fs::metadata("tmp")?.file_type(), FileType::Dir); + assert_eq!(fs::create_dir(".///tmp///././dir"), Ok(())); + assert_eq!(fs::read_dir("tmp").unwrap().count(), 1); + assert_eq!(fs::write(".///tmp///dir//.///test.txt", "test"), Ok(())); + assert_eq!(fs::read("tmp//././/dir//.///test.txt"), Ok("test".into())); + // assert_err!(fs::remove_dir("dev/../tmp//dir"), DirectoryNotEmpty); // TODO + assert_err!(fs::remove_dir("/tmp/dir/../dir"), DirectoryNotEmpty); + assert_eq!(fs::remove_file("./tmp//dir//test.txt"), Ok(())); + assert_eq!(fs::remove_dir("tmp/dir/.././dir///"), Ok(())); + assert_eq!(fs::read_dir("tmp").unwrap().count(), 0); + + println!("test_devfs_ramfs() OK!"); + Ok(()) +} + +pub fn test_all() { + test_read_write_file().expect("test_read_write_file() failed"); + test_read_dir().expect("test_read_dir() failed"); + test_file_permission().expect("test_file_permission() failed"); + test_create_file_dir().expect("test_create_file_dir() failed"); + test_remove_file_dir().expect("test_remove_file_dir() failed"); + test_devfs_ramfs().expect("test_devfs_ramfs() failed"); +} diff --git a/modules/axfs/tests/test_fatfs.rs b/modules/axfs/tests/test_fatfs.rs new file mode 100644 index 000000000..f8d046f39 --- /dev/null +++ b/modules/axfs/tests/test_fatfs.rs @@ -0,0 +1,27 @@ +#![cfg(not(feature = "myfs"))] + +mod test_common; + +use axdriver::AxDeviceContainer; +use driver_block::ramdisk::RamDisk; + +const IMG_PATH: &str = "resources/fat16.img"; + +fn make_disk() -> std::io::Result { + let path = std::env::current_dir()?.join(IMG_PATH); + println!("Loading disk image from {:?} ...", path); + let data = std::fs::read(path)?; + println!("size = {} bytes", data.len()); + Ok(RamDisk::from(&data)) +} + +#[test] +fn test_fatfs() { + println!("Testing fatfs with ramdisk ..."); + + let disk = make_disk().expect("failed to load disk image"); + axtask::init_scheduler(); // call this to use `axsync::Mutex`. + axfs::init_filesystems(AxDeviceContainer::from_one(disk)); + + test_common::test_all(); +} diff --git a/modules/axfs/tests/test_ramfs.rs b/modules/axfs/tests/test_ramfs.rs new file mode 100644 index 000000000..09a92978a --- /dev/null +++ b/modules/axfs/tests/test_ramfs.rs @@ -0,0 +1,56 @@ +#![cfg(feature = "myfs")] + +mod test_common; + +use std::sync::Arc; + +use axdriver::AxDeviceContainer; +use axfs::api::{self as fs, File}; +use axfs::fops::{Disk, MyFileSystemIf}; +use axfs_ramfs::RamFileSystem; +use axfs_vfs::VfsOps; +use axio::{Result, Write}; +use driver_block::ramdisk::RamDisk; + +struct MyFileSystemIfImpl; + +#[crate_interface::impl_interface] +impl MyFileSystemIf for MyFileSystemIfImpl { + fn new_myfs(_disk: Disk) -> Arc { + Arc::new(RamFileSystem::new()) + } +} + +fn create_init_files() -> Result<()> { + fs::write("./short.txt", "Rust is cool!\n")?; + let mut file = File::create_new("/long.txt")?; + for _ in 0..100 { + file.write_fmt(format_args!("Rust is cool!\n"))?; + } + + fs::create_dir("very-long-dir-name")?; + fs::write( + "very-long-dir-name/very-long-file-name.txt", + "Rust is cool!\n", + )?; + + fs::create_dir("very")?; + fs::create_dir("//very/long")?; + fs::create_dir("/./very/long/path")?; + fs::write(".//very/long/path/test.txt", "Rust is cool!\n")?; + Ok(()) +} + +#[test] +fn test_ramfs() { + println!("Testing ramfs ..."); + + axtask::init_scheduler(); // call this to use `axsync::Mutex`. + axfs::init_filesystems(AxDeviceContainer::from_one(RamDisk::default())); // dummy disk, actually not used. + + if let Err(e) = create_init_files() { + log::warn!("failed to create init files: {:?}", e); + } + + test_common::test_all(); +} diff --git a/modules/axhal/.gitignore b/modules/axhal/.gitignore new file mode 100644 index 000000000..8092b09fe --- /dev/null +++ b/modules/axhal/.gitignore @@ -0,0 +1 @@ +/linker_*.lds diff --git a/modules/axhal/Cargo.toml b/modules/axhal/Cargo.toml new file mode 100644 index 000000000..f29ecde07 --- /dev/null +++ b/modules/axhal/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "axhal" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "ArceOS hardware abstraction layer, provides unified APIs for platform-specific operations" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axhal" +documentation = "https://rcore-os.github.io/arceos/axhal/index.html" + +[features] +smp = [] +alloc = [] +fp_simd = [] +paging = ["axalloc", "page_table"] +irq = [] +tls = ["alloc"] +default = [] + +[dependencies] +log = "0.4" +cfg-if = "1.0" +bitflags = "2.2" +static_assertions = "1.1.0" +axlog = { path = "../axlog" } +axconfig = { path = "../axconfig" } +axalloc = { path = "../axalloc", optional = true } +kernel_guard = { path = "../../crates/kernel_guard" } +spinlock = { path = "../../crates/spinlock" } +ratio = { path = "../../crates/ratio" } +lazy_init = { path = "../../crates/lazy_init" } +page_table = { path = "../../crates/page_table", optional = true } +page_table_entry = { path = "../../crates/page_table_entry" } +percpu = { path = "../../crates/percpu" } +memory_addr = { path = "../../crates/memory_addr" } +handler_table = { path = "../../crates/handler_table" } +crate_interface = { path = "../../crates/crate_interface" } + +[target.'cfg(target_arch = "x86_64")'.dependencies] +x86 = "0.52" +x86_64 = "0.14" +x2apic = "0.4" +raw-cpuid = "11.0" + +[target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] +riscv = "0.10" +sbi-rt = { version = "0.0.2", features = ["legacy"] } + +[target.'cfg(target_arch = "aarch64")'.dependencies] +aarch64-cpu = "9.3" +tock-registers = "0.8" +arm_gic = { path = "../../crates/arm_gic" } +arm_pl011 = { path = "../../crates/arm_pl011" } +dw_apb_uart = { path = "../../crates/dw_apb_uart" } + +[build-dependencies] +axconfig = { path = "../axconfig" } diff --git a/modules/axhal/build.rs b/modules/axhal/build.rs new file mode 100644 index 000000000..69343f425 --- /dev/null +++ b/modules/axhal/build.rs @@ -0,0 +1,33 @@ +use std::io::Result; + +fn main() { + let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let platform = axconfig::PLATFORM; + if platform != "dummy" { + gen_linker_script(&arch, platform).unwrap(); + } + + println!("cargo:rustc-cfg=platform=\"{}\"", platform); + println!("cargo:rustc-cfg=platform_family=\"{}\"", axconfig::FAMILY); +} + +fn gen_linker_script(arch: &str, platform: &str) -> Result<()> { + let fname = format!("linker_{}.lds", platform); + let output_arch = if arch == "x86_64" { + "i386:x86-64" + } else if arch.contains("riscv") { + "riscv" // OUTPUT_ARCH of both riscv32/riscv64 is "riscv" + } else { + arch + }; + let ld_content = std::fs::read_to_string("linker.lds.S")?; + let ld_content = ld_content.replace("%ARCH%", output_arch); + let ld_content = ld_content.replace( + "%KERNEL_BASE%", + &format!("{:#x}", axconfig::KERNEL_BASE_VADDR), + ); + let ld_content = ld_content.replace("%SMP%", &format!("{}", axconfig::SMP)); + + std::fs::write(fname, ld_content)?; + Ok(()) +} diff --git a/modules/axhal/linker.lds.S b/modules/axhal/linker.lds.S new file mode 100644 index 000000000..73b7a7884 --- /dev/null +++ b/modules/axhal/linker.lds.S @@ -0,0 +1,86 @@ +OUTPUT_ARCH(%ARCH%) + +BASE_ADDRESS = %KERNEL_BASE%; + +ENTRY(_start) +SECTIONS +{ + . = BASE_ADDRESS; + _skernel = .; + + .text : ALIGN(4K) { + _stext = .; + *(.text.boot) + *(.text .text.*) + . = ALIGN(4K); + _etext = .; + } + + .rodata : ALIGN(4K) { + _srodata = .; + *(.rodata .rodata.*) + *(.srodata .srodata.*) + *(.sdata2 .sdata2.*) + . = ALIGN(4K); + _erodata = .; + } + + .data : ALIGN(4K) { + _sdata = .; + *(.data.boot_page_table) + . = ALIGN(4K); + *(.data .data.*) + *(.sdata .sdata.*) + *(.got .got.*) + } + + .tdata : ALIGN(0x10) { + _stdata = .; + *(.tdata .tdata.*) + _etdata = .; + } + + .tbss : ALIGN(0x10) { + _stbss = .; + *(.tbss .tbss.*) + *(.tcommon) + _etbss = .; + } + + . = ALIGN(4K); + _percpu_start = .; + .percpu 0x0 : AT(_percpu_start) { + _percpu_load_start = .; + *(.percpu .percpu.*) + _percpu_load_end = .; + . = ALIGN(64); + _percpu_size_aligned = .; + + . = _percpu_load_start + _percpu_size_aligned * %SMP%; + } + . = _percpu_start + SIZEOF(.percpu); + _percpu_end = .; + + . = ALIGN(4K); + _edata = .; + + .bss : ALIGN(4K) { + boot_stack = .; + *(.bss.stack) + . = ALIGN(4K); + boot_stack_top = .; + + _sbss = .; + *(.bss .bss.*) + *(.sbss .sbss.*) + *(COMMON) + . = ALIGN(4K); + _ebss = .; + } + + _ekernel = .; + + /DISCARD/ : { + *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) + } +} diff --git a/modules/axhal/src/arch/aarch64/context.rs b/modules/axhal/src/arch/aarch64/context.rs new file mode 100644 index 000000000..67b923364 --- /dev/null +++ b/modules/axhal/src/arch/aarch64/context.rs @@ -0,0 +1,179 @@ +use core::arch::asm; +use memory_addr::VirtAddr; + +/// Saved registers when a trap (exception) occurs. +#[repr(C)] +#[derive(Debug, Default, Clone, Copy)] +pub struct TrapFrame { + /// General-purpose registers (R0..R30). + pub r: [u64; 31], + /// User Stack Pointer (SP_EL0). + pub usp: u64, + /// Exception Link Register (ELR_EL1). + pub elr: u64, + /// Saved Process Status Register (SPSR_EL1). + pub spsr: u64, +} + +/// FP & SIMD registers. +#[repr(C, align(16))] +#[derive(Debug, Default)] +pub struct FpState { + /// 128-bit SIMD & FP registers (V0..V31) + pub regs: [u128; 32], + /// Floating-point Control Register (FPCR) + pub fpcr: u32, + /// Floating-point Status Register (FPSR) + pub fpsr: u32, +} + +#[cfg(feature = "fp_simd")] +impl FpState { + fn switch_to(&mut self, next_fpstate: &FpState) { + unsafe { fpstate_switch(self, next_fpstate) } + } +} + +/// Saved hardware states of a task. +/// +/// The context usually includes: +/// +/// - Callee-saved registers +/// - Stack pointer register +/// - Thread pointer register (for thread-local storage, currently unsupported) +/// - FP/SIMD registers +/// +/// On context switch, current task saves its context from CPU to memory, +/// and the next task restores its context from memory to CPU. +#[allow(missing_docs)] +#[repr(C)] +#[derive(Debug)] +pub struct TaskContext { + pub sp: u64, + pub tpidr_el0: u64, + pub r19: u64, + pub r20: u64, + pub r21: u64, + pub r22: u64, + pub r23: u64, + pub r24: u64, + pub r25: u64, + pub r26: u64, + pub r27: u64, + pub r28: u64, + pub r29: u64, + pub lr: u64, // r30 + #[cfg(feature = "fp_simd")] + pub fp_state: FpState, +} + +impl TaskContext { + /// Creates a new default context for a new task. + pub const fn new() -> Self { + unsafe { core::mem::MaybeUninit::zeroed().assume_init() } + } + + /// Initializes the context for a new task, with the given entry point and + /// kernel stack. + pub fn init(&mut self, entry: usize, kstack_top: VirtAddr, tls_area: VirtAddr) { + self.sp = kstack_top.as_usize() as u64; + self.lr = entry as u64; + self.tpidr_el0 = tls_area.as_usize() as u64; + } + + /// Switches to another task. + /// + /// It first saves the current task's context from CPU to this place, and then + /// restores the next task's context from `next_ctx` to CPU. + pub fn switch_to(&mut self, next_ctx: &Self) { + #[cfg(feature = "fp_simd")] + self.fp_state.switch_to(&next_ctx.fp_state); + unsafe { context_switch(self, next_ctx) } + } +} + +#[naked] +unsafe extern "C" fn context_switch(_current_task: &mut TaskContext, _next_task: &TaskContext) { + asm!( + " + // save old context (callee-saved registers) + stp x29, x30, [x0, 12 * 8] + stp x27, x28, [x0, 10 * 8] + stp x25, x26, [x0, 8 * 8] + stp x23, x24, [x0, 6 * 8] + stp x21, x22, [x0, 4 * 8] + stp x19, x20, [x0, 2 * 8] + mov x19, sp + mrs x20, tpidr_el0 + stp x19, x20, [x0] + + // restore new context + ldp x19, x20, [x1] + mov sp, x19 + msr tpidr_el0, x20 + ldp x19, x20, [x1, 2 * 8] + ldp x21, x22, [x1, 4 * 8] + ldp x23, x24, [x1, 6 * 8] + ldp x25, x26, [x1, 8 * 8] + ldp x27, x28, [x1, 10 * 8] + ldp x29, x30, [x1, 12 * 8] + + ret", + options(noreturn), + ) +} + +#[naked] +#[cfg(feature = "fp_simd")] +unsafe extern "C" fn fpstate_switch(_current_fpstate: &mut FpState, _next_fpstate: &FpState) { + asm!( + " + // save fp/neon context + mrs x9, fpcr + mrs x10, fpsr + stp q0, q1, [x0, 0 * 16] + stp q2, q3, [x0, 2 * 16] + stp q4, q5, [x0, 4 * 16] + stp q6, q7, [x0, 6 * 16] + stp q8, q9, [x0, 8 * 16] + stp q10, q11, [x0, 10 * 16] + stp q12, q13, [x0, 12 * 16] + stp q14, q15, [x0, 14 * 16] + stp q16, q17, [x0, 16 * 16] + stp q18, q19, [x0, 18 * 16] + stp q20, q21, [x0, 20 * 16] + stp q22, q23, [x0, 22 * 16] + stp q24, q25, [x0, 24 * 16] + stp q26, q27, [x0, 26 * 16] + stp q28, q29, [x0, 28 * 16] + stp q30, q31, [x0, 30 * 16] + str x9, [x0, 64 * 8] + str x10, [x0, 65 * 8] + + // restore fp/neon context + ldp q0, q1, [x1, 0 * 16] + ldp q2, q3, [x1, 2 * 16] + ldp q4, q5, [x1, 4 * 16] + ldp q6, q7, [x1, 6 * 16] + ldp q8, q9, [x1, 8 * 16] + ldp q10, q11, [x1, 10 * 16] + ldp q12, q13, [x1, 12 * 16] + ldp q14, q15, [x1, 14 * 16] + ldp q16, q17, [x1, 16 * 16] + ldp q18, q19, [x1, 18 * 16] + ldp q20, q21, [x1, 20 * 16] + ldp q22, q23, [x1, 22 * 16] + ldp q24, q25, [x1, 24 * 16] + ldp q26, q27, [x1, 26 * 16] + ldp q28, q29, [x1, 28 * 16] + ldp q30, q31, [x1, 30 * 16] + ldr x9, [x1, 64 * 8] + ldr x10, [x1, 65 * 8] + msr fpcr, x9 + msr fpsr, x10 + + isb + ret", + options(noreturn), + ) +} diff --git a/modules/axhal/src/arch/aarch64/mod.rs b/modules/axhal/src/arch/aarch64/mod.rs new file mode 100644 index 000000000..46d3fc7ac --- /dev/null +++ b/modules/axhal/src/arch/aarch64/mod.rs @@ -0,0 +1,137 @@ +mod context; +pub(crate) mod trap; + +use core::arch::asm; + +use aarch64_cpu::registers::{DAIF, TPIDR_EL0, TTBR0_EL1, TTBR1_EL1, VBAR_EL1}; +use memory_addr::{PhysAddr, VirtAddr}; +use tock_registers::interfaces::{Readable, Writeable}; + +pub use self::context::{FpState, TaskContext, TrapFrame}; + +/// Allows the current CPU to respond to interrupts. +#[inline] +pub fn enable_irqs() { + unsafe { asm!("msr daifclr, #2") }; +} + +/// Makes the current CPU to ignore interrupts. +#[inline] +pub fn disable_irqs() { + unsafe { asm!("msr daifset, #2") }; +} + +/// Returns whether the current CPU is allowed to respond to interrupts. +#[inline] +pub fn irqs_enabled() -> bool { + !DAIF.matches_all(DAIF::I::Masked) +} + +/// Relaxes the current CPU and waits for interrupts. +/// +/// It must be called with interrupts enabled, otherwise it will never return. +#[inline] +pub fn wait_for_irqs() { + aarch64_cpu::asm::wfi(); +} + +/// Halt the current CPU. +#[inline] +pub fn halt() { + disable_irqs(); + aarch64_cpu::asm::wfi(); // should never return +} + +/// Reads the register that stores the current page table root. +/// +/// Returns the physical address of the page table root. +#[inline] +pub fn read_page_table_root() -> PhysAddr { + let root = TTBR1_EL1.get(); + PhysAddr::from(root as usize) +} + +/// Reads the `TTBR0_EL1` register. +pub fn read_page_table_root0() -> PhysAddr { + let root = TTBR0_EL1.get(); + PhysAddr::from(root as usize) +} + +/// Writes the register to update the current page table root. +/// +/// # Safety +/// +/// This function is unsafe as it changes the virtual memory address space. +pub unsafe fn write_page_table_root(root_paddr: PhysAddr) { + let old_root = read_page_table_root(); + trace!("set page table root: {:#x} => {:#x}", old_root, root_paddr); + if old_root != root_paddr { + // kernel space page table use TTBR1 (0xffff_0000_0000_0000..0xffff_ffff_ffff_ffff) + TTBR1_EL1.set(root_paddr.as_usize() as _); + flush_tlb(None); + } +} + +/// Writes the `TTBR0_EL1` register. +/// +/// # Safety +/// +/// This function is unsafe as it changes the virtual memory address space. +pub unsafe fn write_page_table_root0(root_paddr: PhysAddr) { + TTBR0_EL1.set(root_paddr.as_usize() as _); + flush_tlb(None); +} + +/// Flushes the TLB. +/// +/// If `vaddr` is [`None`], flushes the entire TLB. Otherwise, flushes the TLB +/// entry that maps the given virtual address. +#[inline] +pub fn flush_tlb(vaddr: Option) { + unsafe { + if let Some(vaddr) = vaddr { + asm!("tlbi vaae1is, {}; dsb sy; isb", in(reg) vaddr.as_usize()) + } else { + // flush the entire TLB + asm!("tlbi vmalle1; dsb sy; isb") + } + } +} + +/// Flushes the entire instruction cache. +#[inline] +pub fn flush_icache_all() { + unsafe { asm!("ic iallu; dsb sy; isb") }; +} + +/// Sets the base address of the exception vector (writes `VBAR_EL1`). +#[inline] +pub fn set_exception_vector_base(vbar_el1: usize) { + VBAR_EL1.set(vbar_el1 as _); +} + +/// Flushes the data cache line (64 bytes) at the given virtual address +#[inline] +pub fn flush_dcache_line(vaddr: VirtAddr) { + unsafe { asm!("dc ivac, {0:x}; dsb sy; isb", in(reg) vaddr.as_usize()) }; +} + +/// Reads the thread pointer of the current CPU. +/// +/// It is used to implement TLS (Thread Local Storage). +#[inline] +pub fn read_thread_pointer() -> usize { + TPIDR_EL0.get() as usize +} + +/// Writes the thread pointer of the current CPU. +/// +/// It is used to implement TLS (Thread Local Storage). +/// +/// # Safety +/// +/// This function is unsafe as it changes the CPU states. +#[inline] +pub unsafe fn write_thread_pointer(tpidr_el0: usize) { + TPIDR_EL0.set(tpidr_el0 as _) +} diff --git a/modules/axhal/src/arch/aarch64/trap.S b/modules/axhal/src/arch/aarch64/trap.S new file mode 100644 index 000000000..7167610ac --- /dev/null +++ b/modules/axhal/src/arch/aarch64/trap.S @@ -0,0 +1,107 @@ +.macro SAVE_REGS + sub sp, sp, 34 * 8 + stp x0, x1, [sp] + stp x2, x3, [sp, 2 * 8] + stp x4, x5, [sp, 4 * 8] + stp x6, x7, [sp, 6 * 8] + stp x8, x9, [sp, 8 * 8] + stp x10, x11, [sp, 10 * 8] + stp x12, x13, [sp, 12 * 8] + stp x14, x15, [sp, 14 * 8] + stp x16, x17, [sp, 16 * 8] + stp x18, x19, [sp, 18 * 8] + stp x20, x21, [sp, 20 * 8] + stp x22, x23, [sp, 22 * 8] + stp x24, x25, [sp, 24 * 8] + stp x26, x27, [sp, 26 * 8] + stp x28, x29, [sp, 28 * 8] + + mrs x9, sp_el0 + mrs x10, elr_el1 + mrs x11, spsr_el1 + stp x30, x9, [sp, 30 * 8] + stp x10, x11, [sp, 32 * 8] +.endm + +.macro RESTORE_REGS + ldp x10, x11, [sp, 32 * 8] + ldp x30, x9, [sp, 30 * 8] + msr sp_el0, x9 + msr elr_el1, x10 + msr spsr_el1, x11 + + ldp x28, x29, [sp, 28 * 8] + ldp x26, x27, [sp, 26 * 8] + ldp x24, x25, [sp, 24 * 8] + ldp x22, x23, [sp, 22 * 8] + ldp x20, x21, [sp, 20 * 8] + ldp x18, x19, [sp, 18 * 8] + ldp x16, x17, [sp, 16 * 8] + ldp x14, x15, [sp, 14 * 8] + ldp x12, x13, [sp, 12 * 8] + ldp x10, x11, [sp, 10 * 8] + ldp x8, x9, [sp, 8 * 8] + ldp x6, x7, [sp, 6 * 8] + ldp x4, x5, [sp, 4 * 8] + ldp x2, x3, [sp, 2 * 8] + ldp x0, x1, [sp] + add sp, sp, 34 * 8 +.endm + +.macro INVALID_EXCP, kind, source +.p2align 7 + SAVE_REGS + mov x0, sp + mov x1, \kind + mov x2, \source + bl invalid_exception + b .Lexception_return +.endm + +.macro HANDLE_SYNC +.p2align 7 + SAVE_REGS + mov x0, sp + bl handle_sync_exception + b .Lexception_return +.endm + +.macro HANDLE_IRQ +.p2align 7 + SAVE_REGS + mov x0, sp + bl handle_irq_exception + b .Lexception_return +.endm + +.section .text +.p2align 11 +.global exception_vector_base +exception_vector_base: + // current EL, with SP_EL0 + INVALID_EXCP 0 0 + INVALID_EXCP 1 0 + INVALID_EXCP 2 0 + INVALID_EXCP 3 0 + + // current EL, with SP_ELx + HANDLE_SYNC + HANDLE_IRQ + INVALID_EXCP 2 1 + INVALID_EXCP 3 1 + + // lower EL, aarch64 + HANDLE_SYNC + HANDLE_IRQ + INVALID_EXCP 2 2 + INVALID_EXCP 3 2 + + // lower EL, aarch32 + INVALID_EXCP 0 3 + INVALID_EXCP 1 3 + INVALID_EXCP 2 3 + INVALID_EXCP 3 3 + +.Lexception_return: + RESTORE_REGS + eret diff --git a/modules/axhal/src/arch/aarch64/trap.rs b/modules/axhal/src/arch/aarch64/trap.rs new file mode 100644 index 000000000..00afbe985 --- /dev/null +++ b/modules/axhal/src/arch/aarch64/trap.rs @@ -0,0 +1,86 @@ +use core::arch::global_asm; + +use aarch64_cpu::registers::{ESR_EL1, FAR_EL1}; +use tock_registers::interfaces::Readable; + +use super::TrapFrame; + +global_asm!(include_str!("trap.S")); + +#[repr(u8)] +#[derive(Debug)] +#[allow(dead_code)] +enum TrapKind { + Synchronous = 0, + Irq = 1, + Fiq = 2, + SError = 3, +} + +#[repr(u8)] +#[derive(Debug)] +#[allow(dead_code)] +enum TrapSource { + CurrentSpEl0 = 0, + CurrentSpElx = 1, + LowerAArch64 = 2, + LowerAArch32 = 3, +} + +#[no_mangle] +fn invalid_exception(tf: &TrapFrame, kind: TrapKind, source: TrapSource) { + panic!( + "Invalid exception {:?} from {:?}:\n{:#x?}", + kind, source, tf + ); +} + +#[no_mangle] +fn handle_sync_exception(tf: &mut TrapFrame) { + let esr = ESR_EL1.extract(); + match esr.read_as_enum(ESR_EL1::EC) { + Some(ESR_EL1::EC::Value::Brk64) => { + let iss = esr.read(ESR_EL1::ISS); + debug!("BRK #{:#x} @ {:#x} ", iss, tf.elr); + tf.elr += 4; + } + Some(ESR_EL1::EC::Value::SVC64) => { + warn!("No supervisor call is supported currently!"); + } + Some(ESR_EL1::EC::Value::DataAbortLowerEL) + | Some(ESR_EL1::EC::Value::InstrAbortLowerEL) => { + let iss = esr.read(ESR_EL1::ISS); + warn!( + "EL0 Page Fault @ {:#x}, FAR={:#x}, ISS={:#x}", + tf.elr, + FAR_EL1.get(), + iss + ); + } + Some(ESR_EL1::EC::Value::DataAbortCurrentEL) + | Some(ESR_EL1::EC::Value::InstrAbortCurrentEL) => { + let iss = esr.read(ESR_EL1::ISS); + panic!( + "EL1 Page Fault @ {:#x}, FAR={:#x}, ISS={:#x}:\n{:#x?}", + tf.elr, + FAR_EL1.get(), + iss, + tf, + ); + } + _ => { + panic!( + "Unhandled synchronous exception @ {:#x}: ESR={:#x} (EC {:#08b}, ISS {:#x})", + tf.elr, + esr.get(), + esr.read(ESR_EL1::EC), + esr.read(ESR_EL1::ISS), + ); + } + } +} + +#[no_mangle] +fn handle_irq_exception(_tf: &TrapFrame) { + crate::trap::handle_irq_extern(0) +} diff --git a/modules/axhal/src/arch/mod.rs b/modules/axhal/src/arch/mod.rs new file mode 100644 index 000000000..b8bc0af3d --- /dev/null +++ b/modules/axhal/src/arch/mod.rs @@ -0,0 +1,14 @@ +//! Architecture-specific types and operations. + +cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + mod x86_64; + pub use self::x86_64::*; + } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { + mod riscv; + pub use self::riscv::*; + } else if #[cfg(target_arch = "aarch64")]{ + mod aarch64; + pub use self::aarch64::*; + } +} diff --git a/modules/axhal/src/arch/riscv/context.rs b/modules/axhal/src/arch/riscv/context.rs new file mode 100644 index 000000000..5f9bf3e8a --- /dev/null +++ b/modules/axhal/src/arch/riscv/context.rs @@ -0,0 +1,162 @@ +use core::arch::asm; +use memory_addr::VirtAddr; + +include_asm_marcos!(); + +/// General registers of RISC-V. +#[allow(missing_docs)] +#[repr(C)] +#[derive(Debug, Default, Clone)] +pub struct GeneralRegisters { + pub ra: usize, + pub sp: usize, + pub gp: usize, // only valid for user traps + pub tp: usize, // only valid for user traps + pub t0: usize, + pub t1: usize, + pub t2: usize, + pub s0: usize, + pub s1: usize, + pub a0: usize, + pub a1: usize, + pub a2: usize, + pub a3: usize, + pub a4: usize, + pub a5: usize, + pub a6: usize, + pub a7: usize, + pub s2: usize, + pub s3: usize, + pub s4: usize, + pub s5: usize, + pub s6: usize, + pub s7: usize, + pub s8: usize, + pub s9: usize, + pub s10: usize, + pub s11: usize, + pub t3: usize, + pub t4: usize, + pub t5: usize, + pub t6: usize, +} + +/// Saved registers when a trap (interrupt or exception) occurs. +#[repr(C)] +#[derive(Debug, Default, Clone)] +pub struct TrapFrame { + /// All general registers. + pub regs: GeneralRegisters, + /// Supervisor Exception Program Counter. + pub sepc: usize, + /// Supervisor Status Register. + pub sstatus: usize, +} + +/// Saved hardware states of a task. +/// +/// The context usually includes: +/// +/// - Callee-saved registers +/// - Stack pointer register +/// - Thread pointer register (for thread-local storage, currently unsupported) +/// - FP/SIMD registers +/// +/// On context switch, current task saves its context from CPU to memory, +/// and the next task restores its context from memory to CPU. +#[allow(missing_docs)] +#[repr(C)] +#[derive(Debug, Default)] +pub struct TaskContext { + pub ra: usize, // return address (x1) + pub sp: usize, // stack pointer (x2) + + pub s0: usize, // x8-x9 + pub s1: usize, + + pub s2: usize, // x18-x27 + pub s3: usize, + pub s4: usize, + pub s5: usize, + pub s6: usize, + pub s7: usize, + pub s8: usize, + pub s9: usize, + pub s10: usize, + pub s11: usize, + + pub tp: usize, + // TODO: FP states +} + +impl TaskContext { + /// Creates a new default context for a new task. + pub const fn new() -> Self { + unsafe { core::mem::MaybeUninit::zeroed().assume_init() } + } + + /// Initializes the context for a new task, with the given entry point and + /// kernel stack. + pub fn init(&mut self, entry: usize, kstack_top: VirtAddr, tls_area: VirtAddr) { + self.sp = kstack_top.as_usize(); + self.ra = entry; + self.tp = tls_area.as_usize(); + } + + /// Switches to another task. + /// + /// It first saves the current task's context from CPU to this place, and then + /// restores the next task's context from `next_ctx` to CPU. + pub fn switch_to(&mut self, next_ctx: &Self) { + #[cfg(feature = "tls")] + { + self.tp = super::read_thread_pointer(); + unsafe { super::write_thread_pointer(next_ctx.tp) }; + } + unsafe { + // TODO: switch FP states + context_switch(self, next_ctx) + } + } +} + +#[naked] +unsafe extern "C" fn context_switch(_current_task: &mut TaskContext, _next_task: &TaskContext) { + asm!( + " + // save old context (callee-saved registers) + STR ra, a0, 0 + STR sp, a0, 1 + STR s0, a0, 2 + STR s1, a0, 3 + STR s2, a0, 4 + STR s3, a0, 5 + STR s4, a0, 6 + STR s5, a0, 7 + STR s6, a0, 8 + STR s7, a0, 9 + STR s8, a0, 10 + STR s9, a0, 11 + STR s10, a0, 12 + STR s11, a0, 13 + + // restore new context + LDR s11, a1, 13 + LDR s10, a1, 12 + LDR s9, a1, 11 + LDR s8, a1, 10 + LDR s7, a1, 9 + LDR s6, a1, 8 + LDR s5, a1, 7 + LDR s4, a1, 6 + LDR s3, a1, 5 + LDR s2, a1, 4 + LDR s1, a1, 3 + LDR s0, a1, 2 + LDR sp, a1, 1 + LDR ra, a1, 0 + + ret", + options(noreturn), + ) +} diff --git a/modules/axhal/src/arch/riscv/macros.rs b/modules/axhal/src/arch/riscv/macros.rs new file mode 100644 index 000000000..c36c4da80 --- /dev/null +++ b/modules/axhal/src/arch/riscv/macros.rs @@ -0,0 +1,81 @@ +macro_rules! include_asm_marcos { + () => { + #[cfg(target_arch = "riscv32")] + core::arch::global_asm!( + r" + .ifndef XLENB + .equ XLENB, 4 + + .macro LDR rd, rs, off + lw \rd, \off*XLENB(\rs) + .endm + .macro STR rs2, rs1, off + sw \rs2, \off*XLENB(\rs1) + .endm + + .endif" + ); + + #[cfg(target_arch = "riscv64")] + core::arch::global_asm!( + r" + .ifndef XLENB + .equ XLENB, 8 + + .macro LDR rd, rs, off + ld \rd, \off*XLENB(\rs) + .endm + .macro STR rs2, rs1, off + sd \rs2, \off*XLENB(\rs1) + .endm + + .endif", + ); + + core::arch::global_asm!( + r" + .ifndef .LPUSH_POP_GENERAL_REGS + .equ .LPUSH_POP_GENERAL_REGS, 0 + + .macro PUSH_POP_GENERAL_REGS, op + \op ra, sp, 0 + \op t0, sp, 4 + \op t1, sp, 5 + \op t2, sp, 6 + \op s0, sp, 7 + \op s1, sp, 8 + \op a0, sp, 9 + \op a1, sp, 10 + \op a2, sp, 11 + \op a3, sp, 12 + \op a4, sp, 13 + \op a5, sp, 14 + \op a6, sp, 15 + \op a7, sp, 16 + \op s2, sp, 17 + \op s3, sp, 18 + \op s4, sp, 19 + \op s5, sp, 20 + \op s6, sp, 21 + \op s7, sp, 22 + \op s8, sp, 23 + \op s9, sp, 24 + \op s10, sp, 25 + \op s11, sp, 26 + \op t3, sp, 27 + \op t4, sp, 28 + \op t5, sp, 29 + \op t6, sp, 30 + .endm + + .macro PUSH_GENERAL_REGS + PUSH_POP_GENERAL_REGS STR + .endm + .macro POP_GENERAL_REGS + PUSH_POP_GENERAL_REGS LDR + .endm + + .endif" + ); + }; +} diff --git a/modules/axhal/src/arch/riscv/mod.rs b/modules/axhal/src/arch/riscv/mod.rs new file mode 100644 index 000000000..4cd5cd8df --- /dev/null +++ b/modules/axhal/src/arch/riscv/mod.rs @@ -0,0 +1,109 @@ +#[macro_use] +mod macros; + +mod context; +mod trap; + +use memory_addr::{PhysAddr, VirtAddr}; +use riscv::asm; +use riscv::register::{satp, sstatus, stvec}; + +pub use self::context::{GeneralRegisters, TaskContext, TrapFrame}; + +/// Allows the current CPU to respond to interrupts. +#[inline] +pub fn enable_irqs() { + unsafe { sstatus::set_sie() } +} + +/// Makes the current CPU to ignore interrupts. +#[inline] +pub fn disable_irqs() { + unsafe { sstatus::clear_sie() } +} + +/// Returns whether the current CPU is allowed to respond to interrupts. +#[inline] +pub fn irqs_enabled() -> bool { + sstatus::read().sie() +} + +/// Relaxes the current CPU and waits for interrupts. +/// +/// It must be called with interrupts enabled, otherwise it will never return. +#[inline] +pub fn wait_for_irqs() { + unsafe { riscv::asm::wfi() } +} + +/// Halt the current CPU. +#[inline] +pub fn halt() { + disable_irqs(); + unsafe { riscv::asm::wfi() } // should never return +} + +/// Reads the register that stores the current page table root. +/// +/// Returns the physical address of the page table root. +#[inline] +pub fn read_page_table_root() -> PhysAddr { + PhysAddr::from(satp::read().ppn() << 12) +} + +/// Writes the register to update the current page table root. +/// +/// # Safety +/// +/// This function is unsafe as it changes the virtual memory address space. +pub unsafe fn write_page_table_root(root_paddr: PhysAddr) { + let old_root = read_page_table_root(); + trace!("set page table root: {:#x} => {:#x}", old_root, root_paddr); + if old_root != root_paddr { + satp::set(satp::Mode::Sv39, 0, root_paddr.as_usize() >> 12); + asm::sfence_vma_all(); + } +} + +/// Flushes the TLB. +/// +/// If `vaddr` is [`None`], flushes the entire TLB. Otherwise, flushes the TLB +/// entry that maps the given virtual address. +#[inline] +pub fn flush_tlb(vaddr: Option) { + unsafe { + if let Some(vaddr) = vaddr { + asm::sfence_vma(0, vaddr.as_usize()) + } else { + asm::sfence_vma_all(); + } + } +} + +/// Writes Supervisor Trap Vector Base Address Register (`stvec`). +#[inline] +pub fn set_trap_vector_base(stvec: usize) { + unsafe { stvec::write(stvec, stvec::TrapMode::Direct) } +} + +/// Reads the thread pointer of the current CPU. +/// +/// It is used to implement TLS (Thread Local Storage). +#[inline] +pub fn read_thread_pointer() -> usize { + let tp; + unsafe { core::arch::asm!("mv {}, tp", out(reg) tp) }; + tp +} + +/// Writes the thread pointer of the current CPU. +/// +/// It is used to implement TLS (Thread Local Storage). +/// +/// # Safety +/// +/// This function is unsafe as it changes the CPU states. +#[inline] +pub unsafe fn write_thread_pointer(tp: usize) { + core::arch::asm!("mv tp, {}", in(reg) tp) +} diff --git a/modules/axhal/src/arch/riscv/trap.S b/modules/axhal/src/arch/riscv/trap.S new file mode 100644 index 000000000..4e4e3d748 --- /dev/null +++ b/modules/axhal/src/arch/riscv/trap.S @@ -0,0 +1,65 @@ +.macro SAVE_REGS, from_user + addi sp, sp, -{trapframe_size} + PUSH_GENERAL_REGS + + csrr t0, sepc + csrr t1, sstatus + csrrw t2, sscratch, zero // save sscratch (sp) and zero it + STR t0, sp, 31 // tf.sepc + STR t1, sp, 32 // tf.sstatus + STR t2, sp, 1 // tf.regs.sp + +.if \from_user == 1 + LDR t0, sp, 3 // load supervisor tp + STR gp, sp, 2 // save user gp and tp + STR tp, sp, 3 + mv tp, t0 +.endif +.endm + +.macro RESTORE_REGS, from_user +.if \from_user == 1 + LDR gp, sp, 2 // load user gp and tp + LDR t0, sp, 3 + STR tp, sp, 3 // save supervisor tp + mv tp, t0 + addi t0, sp, {trapframe_size} // put supervisor sp to scratch + csrw sscratch, t0 +.endif + + LDR t0, sp, 31 + LDR t1, sp, 32 + csrw sepc, t0 + csrw sstatus, t1 + + POP_GENERAL_REGS + LDR sp, sp, 1 // load sp from tf.regs.sp +.endm + +.section .text +.balign 4 +.global trap_vector_base +trap_vector_base: + // sscratch == 0: trap from S mode + // sscratch != 0: trap from U mode + csrrw sp, sscratch, sp // switch sscratch and sp + bnez sp, .Ltrap_entry_u + + csrr sp, sscratch // put supervisor sp back + j .Ltrap_entry_s + +.Ltrap_entry_s: + SAVE_REGS 0 + mv a0, sp + li a1, 0 + call riscv_trap_handler + RESTORE_REGS 0 + sret + +.Ltrap_entry_u: + SAVE_REGS 1 + mv a0, sp + li a1, 1 + call riscv_trap_handler + RESTORE_REGS 1 + sret diff --git a/modules/axhal/src/arch/riscv/trap.rs b/modules/axhal/src/arch/riscv/trap.rs new file mode 100644 index 000000000..7624f2558 --- /dev/null +++ b/modules/axhal/src/arch/riscv/trap.rs @@ -0,0 +1,32 @@ +use riscv::register::scause::{self, Exception as E, Trap}; + +use super::TrapFrame; + +include_asm_marcos!(); + +core::arch::global_asm!( + include_str!("trap.S"), + trapframe_size = const core::mem::size_of::(), +); + +fn handle_breakpoint(sepc: &mut usize) { + debug!("Exception(Breakpoint) @ {:#x} ", sepc); + *sepc += 2 +} + +#[no_mangle] +fn riscv_trap_handler(tf: &mut TrapFrame, _from_user: bool) { + let scause = scause::read(); + match scause.cause() { + Trap::Exception(E::Breakpoint) => handle_breakpoint(&mut tf.sepc), + Trap::Interrupt(_) => crate::trap::handle_irq_extern(scause.bits()), + _ => { + panic!( + "Unhandled trap {:?} @ {:#x}:\n{:#x?}", + scause.cause(), + tf.sepc, + tf + ); + } + } +} diff --git a/modules/axhal/src/arch/x86_64/context.rs b/modules/axhal/src/arch/x86_64/context.rs new file mode 100644 index 000000000..2f1f27e53 --- /dev/null +++ b/modules/axhal/src/arch/x86_64/context.rs @@ -0,0 +1,221 @@ +use core::{arch::asm, fmt}; +use memory_addr::VirtAddr; + +/// Saved registers when a trap (interrupt or exception) occurs. +#[allow(missing_docs)] +#[repr(C)] +#[derive(Debug, Default, Clone)] +pub struct TrapFrame { + pub rax: u64, + pub rcx: u64, + pub rdx: u64, + pub rbx: u64, + pub rbp: u64, + pub rsi: u64, + pub rdi: u64, + pub r8: u64, + pub r9: u64, + pub r10: u64, + pub r11: u64, + pub r12: u64, + pub r13: u64, + pub r14: u64, + pub r15: u64, + + // Pushed by `trap.S` + pub vector: u64, + pub error_code: u64, + + // Pushed by CPU + pub rip: u64, + pub cs: u64, + pub rflags: u64, + pub rsp: u64, + pub ss: u64, +} + +impl TrapFrame { + /// Whether the trap is from userspace. + pub const fn is_user(&self) -> bool { + self.cs & 0b11 == 3 + } +} + +#[repr(C)] +#[derive(Debug, Default)] +struct ContextSwitchFrame { + r15: u64, + r14: u64, + r13: u64, + r12: u64, + rbx: u64, + rbp: u64, + rip: u64, +} + +/// A 512-byte memory region for the FXSAVE/FXRSTOR instruction to save and +/// restore the x87 FPU, MMX, XMM, and MXCSR registers. +/// +/// See for more details. +#[allow(missing_docs)] +#[repr(C, align(16))] +#[derive(Debug)] +pub struct FxsaveArea { + pub fcw: u16, + pub fsw: u16, + pub ftw: u16, + pub fop: u16, + pub fip: u64, + pub fdp: u64, + pub mxcsr: u32, + pub mxcsr_mask: u32, + pub st: [u64; 16], + pub xmm: [u64; 32], + _padding: [u64; 12], +} + +static_assertions::const_assert_eq!(core::mem::size_of::(), 512); + +/// Extended state of a task, such as FP/SIMD states. +pub struct ExtendedState { + /// Memory region for the FXSAVE/FXRSTOR instruction. + pub fxsave_area: FxsaveArea, +} + +#[cfg(feature = "fp_simd")] +impl ExtendedState { + #[inline] + fn save(&mut self) { + unsafe { core::arch::x86_64::_fxsave64(&mut self.fxsave_area as *mut _ as *mut u8) } + } + + #[inline] + fn restore(&self) { + unsafe { core::arch::x86_64::_fxrstor64(&self.fxsave_area as *const _ as *const u8) } + } + + const fn default() -> Self { + let mut area: FxsaveArea = unsafe { core::mem::MaybeUninit::zeroed().assume_init() }; + area.fcw = 0x37f; + area.ftw = 0xffff; + area.mxcsr = 0x1f80; + Self { fxsave_area: area } + } +} + +impl fmt::Debug for ExtendedState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ExtendedState") + .field("fxsave_area", &self.fxsave_area) + .finish() + } +} + +/// Saved hardware states of a task. +/// +/// The context usually includes: +/// +/// - Callee-saved registers +/// - Stack pointer register +/// - Thread pointer register (for thread-local storage, currently unsupported) +/// - FP/SIMD registers +/// +/// On context switch, current task saves its context from CPU to memory, +/// and the next task restores its context from memory to CPU. +/// +/// On x86_64, callee-saved registers are saved to the kernel stack by the +/// `PUSH` instruction. So that [`rsp`] is the `RSP` after callee-saved +/// registers are pushed, and [`kstack_top`] is the top of the kernel stack +/// (`RSP` before any push). +/// +/// [`rsp`]: TaskContext::rsp +/// [`kstack_top`]: TaskContext::kstack_top +#[derive(Debug)] +pub struct TaskContext { + /// The kernel stack top of the task. + pub kstack_top: VirtAddr, + /// `RSP` after all callee-saved registers are pushed. + pub rsp: u64, + /// Thread Local Storage (TLS). + pub fs_base: usize, + /// Extended states, i.e., FP/SIMD states. + #[cfg(feature = "fp_simd")] + pub ext_state: ExtendedState, +} + +impl TaskContext { + /// Creates a new default context for a new task. + pub const fn new() -> Self { + Self { + kstack_top: VirtAddr::from(0), + rsp: 0, + fs_base: 0, + #[cfg(feature = "fp_simd")] + ext_state: ExtendedState::default(), + } + } + + /// Initializes the context for a new task, with the given entry point and + /// kernel stack. + pub fn init(&mut self, entry: usize, kstack_top: VirtAddr, tls_area: VirtAddr) { + unsafe { + // x86_64 calling convention: the stack must be 16-byte aligned before + // calling a function. That means when entering a new task (`ret` in `context_switch` + // is executed), (stack pointer + 8) should be 16-byte aligned. + let frame_ptr = (kstack_top.as_mut_ptr() as *mut u64).sub(1); + let frame_ptr = (frame_ptr as *mut ContextSwitchFrame).sub(1); + core::ptr::write( + frame_ptr, + ContextSwitchFrame { + rip: entry as _, + ..Default::default() + }, + ); + self.rsp = frame_ptr as u64; + } + self.kstack_top = kstack_top; + self.fs_base = tls_area.as_usize(); + } + + /// Switches to another task. + /// + /// It first saves the current task's context from CPU to this place, and then + /// restores the next task's context from `next_ctx` to CPU. + pub fn switch_to(&mut self, next_ctx: &Self) { + #[cfg(feature = "fp_simd")] + { + self.ext_state.save(); + next_ctx.ext_state.restore(); + } + #[cfg(feature = "tls")] + { + self.fs_base = super::read_thread_pointer(); + unsafe { super::write_thread_pointer(next_ctx.fs_base) }; + } + unsafe { context_switch(&mut self.rsp, &next_ctx.rsp) } + } +} + +#[naked] +unsafe extern "C" fn context_switch(_current_stack: &mut u64, _next_stack: &u64) { + asm!( + " + push rbp + push rbx + push r12 + push r13 + push r14 + push r15 + mov [rdi], rsp + + mov rsp, [rsi] + pop r15 + pop r14 + pop r13 + pop r12 + pop rbx + pop rbp + ret", + options(noreturn), + ) +} diff --git a/modules/axhal/src/arch/x86_64/gdt.rs b/modules/axhal/src/arch/x86_64/gdt.rs new file mode 100644 index 000000000..e0ab99ac6 --- /dev/null +++ b/modules/axhal/src/arch/x86_64/gdt.rs @@ -0,0 +1,88 @@ +use core::fmt; + +use x86_64::instructions::tables::{lgdt, load_tss}; +use x86_64::registers::segmentation::{Segment, SegmentSelector, CS}; +use x86_64::structures::gdt::{Descriptor, DescriptorFlags}; +use x86_64::structures::{tss::TaskStateSegment, DescriptorTablePointer}; +use x86_64::{addr::VirtAddr, PrivilegeLevel}; + +/// A wrapper of the Global Descriptor Table (GDT) with maximum 16 entries. +#[repr(align(16))] +pub struct GdtStruct { + table: [u64; 16], +} + +impl GdtStruct { + /// Kernel code segment for 32-bit mode. + pub const KCODE32_SELECTOR: SegmentSelector = SegmentSelector::new(1, PrivilegeLevel::Ring0); + /// Kernel code segment for 64-bit mode. + pub const KCODE64_SELECTOR: SegmentSelector = SegmentSelector::new(2, PrivilegeLevel::Ring0); + /// Kernel data segment. + pub const KDATA_SELECTOR: SegmentSelector = SegmentSelector::new(3, PrivilegeLevel::Ring0); + /// User code segment for 32-bit mode. + pub const UCODE32_SELECTOR: SegmentSelector = SegmentSelector::new(4, PrivilegeLevel::Ring3); + /// User data segment. + pub const UDATA_SELECTOR: SegmentSelector = SegmentSelector::new(5, PrivilegeLevel::Ring3); + /// User code segment for 64-bit mode. + pub const UCODE64_SELECTOR: SegmentSelector = SegmentSelector::new(6, PrivilegeLevel::Ring3); + /// TSS segment. + pub const TSS_SELECTOR: SegmentSelector = SegmentSelector::new(7, PrivilegeLevel::Ring0); + + /// Constructs a new GDT struct that filled with the default segment + /// descriptors, including the given TSS segment. + pub fn new(tss: &'static TaskStateSegment) -> Self { + let mut table = [0; 16]; + // first 3 entries are the same as in multiboot.S + table[1] = DescriptorFlags::KERNEL_CODE32.bits(); // 0x00cf9b000000ffff + table[2] = DescriptorFlags::KERNEL_CODE64.bits(); // 0x00af9b000000ffff + table[3] = DescriptorFlags::KERNEL_DATA.bits(); // 0x00cf93000000ffff + table[4] = DescriptorFlags::USER_CODE32.bits(); // 0x00cffb000000ffff + table[5] = DescriptorFlags::USER_DATA.bits(); // 0x00cff3000000ffff + table[6] = DescriptorFlags::USER_CODE64.bits(); // 0x00affb000000ffff + if let Descriptor::SystemSegment(low, high) = Descriptor::tss_segment(tss) { + table[7] = low; + table[8] = high; + } + Self { table } + } + + /// Returns the GDT pointer (base and limit) that can be used in `lgdt` + /// instruction. + pub fn pointer(&self) -> DescriptorTablePointer { + DescriptorTablePointer { + base: VirtAddr::new(self.table.as_ptr() as u64), + limit: (core::mem::size_of_val(&self.table) - 1) as u16, + } + } + + /// Loads the GDT into the CPU (executes the `lgdt` instruction), and + /// updates the code segment register (`CS`). + /// + /// # Safety + /// + /// This function is unsafe because it manipulates the CPU's privileged + /// states. + pub unsafe fn load(&'static self) { + lgdt(&self.pointer()); + CS::set_reg(Self::KCODE64_SELECTOR); + } + + /// Loads the TSS into the CPU (executes the `ltr` instruction). + /// + /// # Safety + /// + /// This function is unsafe because it manipulates the CPU's privileged + /// states. + pub unsafe fn load_tss(&'static self) { + load_tss(Self::TSS_SELECTOR); + } +} + +impl fmt::Debug for GdtStruct { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("GdtStruct") + .field("pointer", &self.pointer()) + .field("table", &self.table) + .finish() + } +} diff --git a/modules/axhal/src/arch/x86_64/idt.rs b/modules/axhal/src/arch/x86_64/idt.rs new file mode 100644 index 000000000..ffccff7d4 --- /dev/null +++ b/modules/axhal/src/arch/x86_64/idt.rs @@ -0,0 +1,67 @@ +use core::fmt; + +use x86_64::addr::VirtAddr; +use x86_64::structures::idt::{Entry, HandlerFunc, InterruptDescriptorTable}; +use x86_64::structures::DescriptorTablePointer; + +const NUM_INT: usize = 256; + +/// A wrapper of the Interrupt Descriptor Table (IDT). +#[repr(transparent)] +pub struct IdtStruct { + table: InterruptDescriptorTable, +} + +impl IdtStruct { + /// Constructs a new IDT struct that filled with entries from + /// `trap_handler_table`. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + extern "C" { + #[link_name = "trap_handler_table"] + static ENTRIES: [extern "C" fn(); NUM_INT]; + } + let mut idt = Self { + table: InterruptDescriptorTable::new(), + }; + + let entries = unsafe { + core::slice::from_raw_parts_mut( + &mut idt.table as *mut _ as *mut Entry, + NUM_INT, + ) + }; + for i in 0..NUM_INT { + entries[i].set_handler_fn(unsafe { core::mem::transmute(ENTRIES[i]) }); + } + idt + } + + /// Returns the IDT pointer (base and limit) that can be used in the `lidt` + /// instruction. + pub fn pointer(&self) -> DescriptorTablePointer { + DescriptorTablePointer { + base: VirtAddr::new(&self.table as *const _ as u64), + limit: (core::mem::size_of::() - 1) as u16, + } + } + + /// Loads the IDT into the CPU (executes the `lidt` instruction). + /// + /// # Safety + /// + /// This function is unsafe because it manipulates the CPU's privileged + /// states. + pub unsafe fn load(&'static self) { + self.table.load(); + } +} + +impl fmt::Debug for IdtStruct { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("IdtStruct") + .field("pointer", &self.pointer()) + .field("table", &self.table) + .finish() + } +} diff --git a/modules/axhal/src/arch/x86_64/mod.rs b/modules/axhal/src/arch/x86_64/mod.rs new file mode 100644 index 000000000..f4ef11311 --- /dev/null +++ b/modules/axhal/src/arch/x86_64/mod.rs @@ -0,0 +1,110 @@ +mod context; +mod gdt; +mod idt; + +#[cfg(target_os = "none")] +mod trap; + +use core::arch::asm; + +use memory_addr::{PhysAddr, VirtAddr}; +use x86::{controlregs, msr, tlb}; +use x86_64::instructions::interrupts; + +pub use self::context::{ExtendedState, FxsaveArea, TaskContext, TrapFrame}; +pub use self::gdt::GdtStruct; +pub use self::idt::IdtStruct; +pub use x86_64::structures::tss::TaskStateSegment; + +/// Allows the current CPU to respond to interrupts. +#[inline] +pub fn enable_irqs() { + #[cfg(target_os = "none")] + interrupts::enable() +} + +/// Makes the current CPU to ignore interrupts. +#[inline] +pub fn disable_irqs() { + #[cfg(target_os = "none")] + interrupts::disable() +} + +/// Returns whether the current CPU is allowed to respond to interrupts. +#[inline] +pub fn irqs_enabled() -> bool { + interrupts::are_enabled() +} + +/// Relaxes the current CPU and waits for interrupts. +/// +/// It must be called with interrupts enabled, otherwise it will never return. +#[inline] +pub fn wait_for_irqs() { + if cfg!(target_os = "none") { + unsafe { asm!("hlt") } + } else { + core::hint::spin_loop() + } +} + +/// Halt the current CPU. +#[inline] +pub fn halt() { + disable_irqs(); + wait_for_irqs(); // should never return +} + +/// Reads the register that stores the current page table root. +/// +/// Returns the physical address of the page table root. +#[inline] +pub fn read_page_table_root() -> PhysAddr { + PhysAddr::from(unsafe { controlregs::cr3() } as usize).align_down_4k() +} + +/// Writes the register to update the current page table root. +/// +/// # Safety +/// +/// This function is unsafe as it changes the virtual memory address space. +pub unsafe fn write_page_table_root(root_paddr: PhysAddr) { + let old_root = read_page_table_root(); + trace!("set page table root: {:#x} => {:#x}", old_root, root_paddr); + if old_root != root_paddr { + controlregs::cr3_write(root_paddr.as_usize() as _) + } +} + +/// Flushes the TLB. +/// +/// If `vaddr` is [`None`], flushes the entire TLB. Otherwise, flushes the TLB +/// entry that maps the given virtual address. +#[inline] +pub fn flush_tlb(vaddr: Option) { + if let Some(vaddr) = vaddr { + unsafe { tlb::flush(vaddr.into()) } + } else { + unsafe { tlb::flush_all() } + } +} + +/// Reads the thread pointer of the current CPU. +/// +/// It is used to implement TLS (Thread Local Storage). +#[inline] +pub fn read_thread_pointer() -> usize { + unsafe { msr::rdmsr(msr::IA32_FS_BASE) as usize } +} + +/// Writes the thread pointer of the current CPU. +/// +/// It is used to implement TLS (Thread Local Storage). +/// +/// # Safety +/// +/// This function is unsafe as it changes the CPU states. +#[inline] +pub unsafe fn write_thread_pointer(fs_base: usize) { + unsafe { msr::wrmsr(msr::IA32_FS_BASE, fs_base as u64) } +} diff --git a/modules/axhal/src/arch/x86_64/trap.S b/modules/axhal/src/arch/x86_64/trap.S new file mode 100644 index 000000000..4bd0d941e --- /dev/null +++ b/modules/axhal/src/arch/x86_64/trap.S @@ -0,0 +1,84 @@ +.equ NUM_INT, 256 + +.altmacro +.macro DEF_HANDLER, i +.Ltrap_handler_\i: +.if \i == 8 || (\i >= 10 && \i <= 14) || \i == 17 + # error code pushed by CPU + push \i # interrupt vector + jmp .Ltrap_common +.else + push 0 # fill in error code in TrapFrame + push \i # interrupt vector + jmp .Ltrap_common +.endif +.endm + +.macro DEF_TABLE_ENTRY, i + .quad .Ltrap_handler_\i +.endm + +.section .text +.code64 +_trap_handlers: +.set i, 0 +.rept NUM_INT + DEF_HANDLER %i + .set i, i + 1 +.endr + +.Ltrap_common: + test byte ptr [rsp + 3 * 8], 3 # swap GS if it comes from user space + jz 1f + swapgs +1: + push r15 + push r14 + push r13 + push r12 + push r11 + push r10 + push r9 + push r8 + push rdi + push rsi + push rbp + push rbx + push rdx + push rcx + push rax + + mov rdi, rsp + call x86_trap_handler + + pop rax + pop rcx + pop rdx + pop rbx + pop rbp + pop rsi + pop rdi + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + pop r15 + + test byte ptr [rsp + 3 * 8], 3 # swap GS back if return to user space + jz 2f + swapgs +2: + add rsp, 16 # pop vector, error_code + iretq + +.section .rodata +.global trap_handler_table +trap_handler_table: +.set i, 0 +.rept NUM_INT + DEF_TABLE_ENTRY %i + .set i, i + 1 +.endr diff --git a/modules/axhal/src/arch/x86_64/trap.rs b/modules/axhal/src/arch/x86_64/trap.rs new file mode 100644 index 000000000..0f379f14b --- /dev/null +++ b/modules/axhal/src/arch/x86_64/trap.rs @@ -0,0 +1,46 @@ +use x86::{controlregs::cr2, irq::*}; + +use super::context::TrapFrame; + +core::arch::global_asm!(include_str!("trap.S")); + +const IRQ_VECTOR_START: u8 = 0x20; +const IRQ_VECTOR_END: u8 = 0xff; + +#[no_mangle] +fn x86_trap_handler(tf: &TrapFrame) { + match tf.vector as u8 { + PAGE_FAULT_VECTOR => { + if tf.is_user() { + warn!( + "User #PF @ {:#x}, fault_vaddr={:#x}, error_code={:#x}", + tf.rip, + unsafe { cr2() }, + tf.error_code, + ); + } else { + panic!( + "Kernel #PF @ {:#x}, fault_vaddr={:#x}, error_code={:#x}:\n{:#x?}", + tf.rip, + unsafe { cr2() }, + tf.error_code, + tf, + ); + } + } + BREAKPOINT_VECTOR => debug!("#BP @ {:#x} ", tf.rip), + GENERAL_PROTECTION_FAULT_VECTOR => { + panic!( + "#GP @ {:#x}, error_code={:#x}:\n{:#x?}", + tf.rip, tf.error_code, tf + ); + } + IRQ_VECTOR_START..=IRQ_VECTOR_END => crate::trap::handle_irq_extern(tf.vector as _), + _ => { + panic!( + "Unhandled exception {} (error_code = {:#x}) @ {:#x}:\n{:#x?}", + tf.vector, tf.error_code, tf.rip, tf + ); + } + } +} diff --git a/modules/axhal/src/cpu.rs b/modules/axhal/src/cpu.rs new file mode 100644 index 000000000..d0ee27a34 --- /dev/null +++ b/modules/axhal/src/cpu.rs @@ -0,0 +1,93 @@ +//! CPU-related operations. + +#[percpu::def_percpu] +static CPU_ID: usize = 0; + +#[percpu::def_percpu] +static IS_BSP: bool = false; + +#[percpu::def_percpu] +static CURRENT_TASK_PTR: usize = 0; + +/// Returns the ID of the current CPU. +#[inline] +pub fn this_cpu_id() -> usize { + CPU_ID.read_current() +} + +/// Returns whether the current CPU is the primary CPU (aka the bootstrap +/// processor or BSP) +#[inline] +pub fn this_cpu_is_bsp() -> bool { + IS_BSP.read_current() +} + +/// Gets the pointer to the current task with preemption-safety. +/// +/// Preemption may be enabled when calling this function. This function will +/// guarantee the correctness even the current task is preempted. +#[inline] +pub fn current_task_ptr() -> *const T { + #[cfg(target_arch = "x86_64")] + unsafe { + // on x86, only one instruction is needed to read the per-CPU task pointer from `gs:[off]`. + CURRENT_TASK_PTR.read_current_raw() as _ + } + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + unsafe { + // on RISC-V, reading `CURRENT_TASK_PTR` requires multiple instruction, so we disable local IRQs. + let _guard = kernel_guard::IrqSave::new(); + CURRENT_TASK_PTR.read_current_raw() as _ + } + #[cfg(target_arch = "aarch64")] + { + // on ARM64, we use `SP_EL0` to store the task pointer. + use tock_registers::interfaces::Readable; + aarch64_cpu::registers::SP_EL0.get() as _ + } +} + +/// Sets the pointer to the current task with preemption-safety. +/// +/// Preemption may be enabled when calling this function. This function will +/// guarantee the correctness even the current task is preempted. +/// +/// # Safety +/// +/// The given `ptr` must be pointed to a valid task structure. +#[inline] +pub unsafe fn set_current_task_ptr(ptr: *const T) { + #[cfg(target_arch = "x86_64")] + { + CURRENT_TASK_PTR.write_current_raw(ptr as usize) + } + #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] + { + let _guard = kernel_guard::IrqSave::new(); + CURRENT_TASK_PTR.write_current_raw(ptr as usize) + } + #[cfg(target_arch = "aarch64")] + { + use tock_registers::interfaces::Writeable; + aarch64_cpu::registers::SP_EL0.set(ptr as u64) + } +} + +#[allow(dead_code)] +pub(crate) fn init_primary(cpu_id: usize) { + percpu::init(axconfig::SMP); + percpu::set_local_thread_pointer(cpu_id); + unsafe { + CPU_ID.write_current_raw(cpu_id); + IS_BSP.write_current_raw(true); + } +} + +#[allow(dead_code)] +pub(crate) fn init_secondary(cpu_id: usize) { + percpu::set_local_thread_pointer(cpu_id); + unsafe { + CPU_ID.write_current_raw(cpu_id); + IS_BSP.write_current_raw(false); + } +} diff --git a/modules/axhal/src/irq.rs b/modules/axhal/src/irq.rs new file mode 100644 index 000000000..46160d40f --- /dev/null +++ b/modules/axhal/src/irq.rs @@ -0,0 +1,35 @@ +//! Interrupt management. + +use handler_table::HandlerTable; + +use crate::platform::irq::MAX_IRQ_COUNT; + +pub use crate::platform::irq::{dispatch_irq, register_handler, set_enable}; + +/// The type if an IRQ handler. +pub type IrqHandler = handler_table::Handler; + +static IRQ_HANDLER_TABLE: HandlerTable = HandlerTable::new(); + +/// Platform-independent IRQ dispatching. +#[allow(dead_code)] +pub(crate) fn dispatch_irq_common(irq_num: usize) { + trace!("IRQ {}", irq_num); + if !IRQ_HANDLER_TABLE.handle(irq_num) { + warn!("Unhandled IRQ {}", irq_num); + } +} + +/// Platform-independent IRQ handler registration. +/// +/// It also enables the IRQ if the registration succeeds. It returns `false` if +/// the registration failed. +#[allow(dead_code)] +pub(crate) fn register_handler_common(irq_num: usize, handler: IrqHandler) -> bool { + if irq_num < MAX_IRQ_COUNT && IRQ_HANDLER_TABLE.register_handler(irq_num, handler) { + set_enable(irq_num, true); + return true; + } + warn!("register handler for IRQ {} failed", irq_num); + false +} diff --git a/modules/axhal/src/lib.rs b/modules/axhal/src/lib.rs new file mode 100644 index 000000000..9fd27a1b1 --- /dev/null +++ b/modules/axhal/src/lib.rs @@ -0,0 +1,81 @@ +//! [ArceOS] hardware abstraction layer, provides unified APIs for +//! platform-specific operations. +//! +//! It does the bootstrapping and initialization process for the specified +//! platform, and provides useful operations on the hardware. +//! +//! Currently supported platforms (specify by cargo features): +//! +//! - `x86-pc`: Standard PC with x86_64 ISA. +//! - `riscv64-qemu-virt`: QEMU virt machine with RISC-V ISA. +//! - `aarch64-qemu-virt`: QEMU virt machine with AArch64 ISA. +//! - `aarch64-raspi`: Raspberry Pi with AArch64 ISA. +//! - `dummy`: If none of the above platform is selected, the dummy platform +//! will be used. In this platform, most of the operations are no-op or +//! `unimplemented!()`. This platform is mainly used for [cargo test]. +//! +//! # Cargo Features +//! +//! - `smp`: Enable SMP (symmetric multiprocessing) support. +//! - `fp_simd`: Enable floating-point and SIMD support. +//! - `paging`: Enable page table manipulation. +//! - `irq`: Enable interrupt handling support. +//! +//! [ArceOS]: https://github.com/rcore-os/arceos +//! [cargo test]: https://doc.rust-lang.org/cargo/guide/tests.html + +#![no_std] +#![feature(asm_const)] +#![feature(naked_functions)] +#![feature(const_maybe_uninit_zeroed)] +#![feature(const_option)] +#![feature(doc_auto_cfg)] + +#[allow(unused_imports)] +#[macro_use] +extern crate log; + +mod platform; + +pub mod arch; +pub mod cpu; +pub mod mem; +pub mod time; +pub mod trap; + +#[cfg(feature = "tls")] +pub mod tls; + +#[cfg(feature = "irq")] +pub mod irq; + +#[cfg(feature = "paging")] +pub mod paging; + +/// Console input and output. +pub mod console { + pub use super::platform::console::*; + + /// Write a slice of bytes to the console. + pub fn write_bytes(bytes: &[u8]) { + for c in bytes { + putchar(*c); + } + } +} + +/// Miscellaneous operation, e.g. terminate the system. +pub mod misc { + pub use super::platform::misc::*; +} + +/// Multi-core operations. +#[cfg(feature = "smp")] +pub mod mp { + pub use super::platform::mp::*; +} + +pub use self::platform::platform_init; + +#[cfg(feature = "smp")] +pub use self::platform::platform_init_secondary; diff --git a/modules/axhal/src/mem.rs b/modules/axhal/src/mem.rs new file mode 100644 index 000000000..ee96c4dc5 --- /dev/null +++ b/modules/axhal/src/mem.rs @@ -0,0 +1,163 @@ +//! Physical memory management. + +use core::fmt; + +#[doc(no_inline)] +pub use memory_addr::{PhysAddr, VirtAddr, PAGE_SIZE_4K}; + +bitflags::bitflags! { + /// The flags of a physical memory region. + pub struct MemRegionFlags: usize { + /// Readable. + const READ = 1 << 0; + /// Writable. + const WRITE = 1 << 1; + /// Executable. + const EXECUTE = 1 << 2; + /// Device memory. (e.g., MMIO regions) + const DEVICE = 1 << 4; + /// Uncachable memory. (e.g., framebuffer) + const UNCACHED = 1 << 5; + /// Reserved memory, do not use for allocation. + const RESERVED = 1 << 6; + /// Free memory for allocation. + const FREE = 1 << 7; + } +} + +impl fmt::Debug for MemRegionFlags { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + +/// A physical memory region. +#[derive(Debug)] +pub struct MemRegion { + /// The start physical address of the region. + pub paddr: PhysAddr, + /// The size in bytes of the region. + pub size: usize, + /// The region flags, see [`MemRegionFlags`]. + pub flags: MemRegionFlags, + /// The region name, used for identification. + pub name: &'static str, +} + +/// Converts a virtual address to a physical address. +/// +/// It assumes that there is a linear mapping with the offset +/// [`PHYS_VIRT_OFFSET`], that maps all the physical memory to the virtual +/// space at the address plus the offset. So we have +/// `paddr = vaddr - PHYS_VIRT_OFFSET`. +/// +/// [`PHYS_VIRT_OFFSET`]: axconfig::PHYS_VIRT_OFFSET +#[inline] +pub const fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr { + PhysAddr::from(vaddr.as_usize() - axconfig::PHYS_VIRT_OFFSET) +} + +/// Converts a physical address to a virtual address. +/// +/// It assumes that there is a linear mapping with the offset +/// [`PHYS_VIRT_OFFSET`], that maps all the physical memory to the virtual +/// space at the address plus the offset. So we have +/// `vaddr = paddr + PHYS_VIRT_OFFSET`. +/// +/// [`PHYS_VIRT_OFFSET`]: axconfig::PHYS_VIRT_OFFSET +#[inline] +pub const fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + VirtAddr::from(paddr.as_usize() + axconfig::PHYS_VIRT_OFFSET) +} + +/// Returns an iterator over all physical memory regions. +pub fn memory_regions() -> impl Iterator { + kernel_image_regions().chain(crate::platform::mem::platform_regions()) +} + +/// Returns the memory regions of the kernel image (code and data sections). +fn kernel_image_regions() -> impl Iterator { + [ + MemRegion { + paddr: virt_to_phys((_stext as usize).into()), + size: _etext as usize - _stext as usize, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::EXECUTE, + name: ".text", + }, + MemRegion { + paddr: virt_to_phys((_srodata as usize).into()), + size: _erodata as usize - _srodata as usize, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ, + name: ".rodata", + }, + MemRegion { + paddr: virt_to_phys((_sdata as usize).into()), + size: _edata as usize - _sdata as usize, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: ".data .tdata .tbss .percpu", + }, + MemRegion { + paddr: virt_to_phys((boot_stack as usize).into()), + size: boot_stack_top as usize - boot_stack as usize, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "boot stack", + }, + MemRegion { + paddr: virt_to_phys((_sbss as usize).into()), + size: _ebss as usize - _sbss as usize, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: ".bss", + }, + ] + .into_iter() +} + +/// Returns the default MMIO memory regions (from [`axconfig::MMIO_REGIONS`]). +#[allow(dead_code)] +pub(crate) fn default_mmio_regions() -> impl Iterator { + axconfig::MMIO_REGIONS.iter().map(|reg| MemRegion { + paddr: reg.0.into(), + size: reg.1, + flags: MemRegionFlags::RESERVED + | MemRegionFlags::DEVICE + | MemRegionFlags::READ + | MemRegionFlags::WRITE, + name: "mmio", + }) +} + +/// Returns the default free memory regions (kernel image end to physical memory end). +#[allow(dead_code)] +pub(crate) fn default_free_regions() -> impl Iterator { + let start = virt_to_phys((_ekernel as usize).into()).align_up_4k(); + let end = PhysAddr::from(axconfig::PHYS_MEMORY_END).align_down_4k(); + core::iter::once(MemRegion { + paddr: start, + size: end.as_usize() - start.as_usize(), + flags: MemRegionFlags::FREE | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "free memory", + }) +} + +/// Fills the `.bss` section with zeros. +#[allow(dead_code)] +pub(crate) fn clear_bss() { + unsafe { + core::slice::from_raw_parts_mut(_sbss as usize as *mut u8, _ebss as usize - _sbss as usize) + .fill(0); + } +} + +extern "C" { + fn _stext(); + fn _etext(); + fn _srodata(); + fn _erodata(); + fn _sdata(); + fn _edata(); + fn _sbss(); + fn _ebss(); + fn _ekernel(); + fn boot_stack(); + fn boot_stack_top(); +} diff --git a/modules/axhal/src/paging.rs b/modules/axhal/src/paging.rs new file mode 100644 index 000000000..2d035e5c6 --- /dev/null +++ b/modules/axhal/src/paging.rs @@ -0,0 +1,66 @@ +//! Page table manipulation. + +use axalloc::global_allocator; +use page_table::PagingIf; + +use crate::mem::{phys_to_virt, virt_to_phys, MemRegionFlags, PhysAddr, VirtAddr, PAGE_SIZE_4K}; + +#[doc(no_inline)] +pub use page_table::{MappingFlags, PageSize, PagingError, PagingResult}; + +impl From for MappingFlags { + fn from(f: MemRegionFlags) -> Self { + let mut ret = Self::empty(); + if f.contains(MemRegionFlags::READ) { + ret |= Self::READ; + } + if f.contains(MemRegionFlags::WRITE) { + ret |= Self::WRITE; + } + if f.contains(MemRegionFlags::EXECUTE) { + ret |= Self::EXECUTE; + } + if f.contains(MemRegionFlags::DEVICE) { + ret |= Self::DEVICE; + } + if f.contains(MemRegionFlags::UNCACHED) { + ret |= Self::UNCACHED; + } + ret + } +} + +/// Implementation of [`PagingIf`], to provide physical memory manipulation to +/// the [page_table] crate. +pub struct PagingIfImpl; + +impl PagingIf for PagingIfImpl { + fn alloc_frame() -> Option { + global_allocator() + .alloc_pages(1, PAGE_SIZE_4K) + .map(|vaddr| virt_to_phys(vaddr.into())) + .ok() + } + + fn dealloc_frame(paddr: PhysAddr) { + global_allocator().dealloc_pages(phys_to_virt(paddr).as_usize(), 1) + } + + #[inline] + fn phys_to_virt(paddr: PhysAddr) -> VirtAddr { + phys_to_virt(paddr) + } +} + +cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + /// The architecture-specific page table. + pub type PageTable = page_table::x86_64::X64PageTable; + } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] { + /// The architecture-specific page table. + pub type PageTable = page_table::riscv::Sv39PageTable; + } else if #[cfg(target_arch = "aarch64")]{ + /// The architecture-specific page table. + pub type PageTable = page_table::aarch64::A64PageTable; + } +} diff --git a/modules/axhal/src/platform/aarch64_bsta1000b/dw_apb_uart.rs b/modules/axhal/src/platform/aarch64_bsta1000b/dw_apb_uart.rs new file mode 100644 index 000000000..844a3cbb3 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_bsta1000b/dw_apb_uart.rs @@ -0,0 +1,44 @@ +//! snps,dw-apb-uart serial driver + +use crate::mem::phys_to_virt; +use dw_apb_uart::DW8250; +use memory_addr::PhysAddr; +use spinlock::SpinNoIrq; + +const UART_BASE: PhysAddr = PhysAddr::from(axconfig::UART_PADDR); + +static UART: SpinNoIrq = SpinNoIrq::new(DW8250::new(phys_to_virt(UART_BASE).as_usize())); + +/// Writes a byte to the console. +pub fn putchar(c: u8) { + let mut uart = UART.lock(); + match c { + b'\r' | b'\n' => { + uart.putchar(b'\r'); + uart.putchar(b'\n'); + } + c => uart.putchar(c), + } +} + +/// Reads a byte from the console, or returns [`None`] if no input is available. +pub fn getchar() -> Option { + UART.lock().getchar() +} + +/// UART simply initialize +pub fn init_early() { + UART.lock().init(); +} + +/// Set UART IRQ Enable +#[cfg(feature = "irq")] +pub fn init_irq() { + UART.lock().set_ier(true); + crate::irq::register_handler(crate::platform::irq::UART_IRQ_NUM, handle); +} + +/// UART IRQ Handler +pub fn handle() { + trace!("Uart IRQ Handler"); +} diff --git a/modules/axhal/src/platform/aarch64_bsta1000b/mem.rs b/modules/axhal/src/platform/aarch64_bsta1000b/mem.rs new file mode 100644 index 000000000..e6faa5ef4 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_bsta1000b/mem.rs @@ -0,0 +1,33 @@ +use crate::mem::{MemRegion, PhysAddr}; +use page_table_entry::{aarch64::A64PTE, GenericPTE, MappingFlags}; + +/// Returns platform-specific memory regions. +pub(crate) fn platform_regions() -> impl Iterator { + crate::mem::default_free_regions().chain(crate::mem::default_mmio_regions()) +} + +pub(crate) unsafe fn init_boot_page_table( + boot_pt_l0: &mut [A64PTE; 512], + boot_pt_l1: &mut [A64PTE; 512], +) { + // 0x0000_0000_0000 ~ 0x0080_0000_0000, table + boot_pt_l0[0] = A64PTE::new_table(PhysAddr::from(boot_pt_l1.as_ptr() as usize)); + // 0x0000_0000_0000..0x0000_4000_0000, 1G block, device memory + boot_pt_l1[0] = A64PTE::new_page( + PhysAddr::from(0), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::DEVICE, + true, + ); + // 1G block, device memory + boot_pt_l1[1] = A64PTE::new_page( + PhysAddr::from(0x40000000), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::DEVICE, + true, + ); + // 1G block, normal memory + boot_pt_l1[2] = A64PTE::new_page( + PhysAddr::from(0x80000000), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE, + true, + ); +} diff --git a/modules/axhal/src/platform/aarch64_bsta1000b/misc.rs b/modules/axhal/src/platform/aarch64_bsta1000b/misc.rs new file mode 100644 index 000000000..8954132e1 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_bsta1000b/misc.rs @@ -0,0 +1,60 @@ +pub use crate::platform::aarch64_common::psci::system_off as terminate; + +use crate::mem::phys_to_virt; +use crate::time::{busy_wait, Duration}; +use core::ptr::{read_volatile, write_volatile}; + +/// Do QSPI reset +pub fn reset_qspi() { + // qspi exit 4-byte mode + // exit_4byte_qspi(); + + let ptr = phys_to_virt((axconfig::A1000BASE_SAFETYCRM + 0x8).into()).as_mut_ptr() as *mut u32; + unsafe { + let value = read_volatile(ptr); + trace!("SAFETY CRM RESET CTRL = {:#x}", value); + write_volatile(ptr, value & !(0b11 << 15)); + busy_wait(Duration::from_millis(100)); + + write_volatile(ptr, value | (0b11 << 15)); + busy_wait(Duration::from_millis(100)); + } +} + +/// Do CPU reset +pub fn reset_cpu() { + reset_qspi(); + + //Data Width = 32 + let ptr = phys_to_virt((axconfig::A1000BASE_SAFETYCRM + 0x8).into()).as_mut_ptr() as *mut u32; + unsafe { + write_volatile(ptr, read_volatile(ptr) & !0b1); + } + + loop {} +} + +/// reboot system +#[allow(dead_code)] +pub fn do_reset() { + axlog::ax_println!("resetting ...\n"); + + // wait 50 ms + busy_wait(Duration::from_millis(50)); + + // disable_interrupts(); + + reset_cpu(); + + // NOT REACHED + warn!("NOT REACHED Resetting"); +} + +/// bootmode define bit [27:26], from strap pin +#[allow(dead_code)] +pub fn get_bootmode() -> u32 { + unsafe { + let ptr = phys_to_virt((axconfig::A1000BASE_TOPCRM).into()).as_mut_ptr() as *mut u32; + (ptr.read_volatile() >> 26) & 0x7 + } +} diff --git a/modules/axhal/src/platform/aarch64_bsta1000b/mod.rs b/modules/axhal/src/platform/aarch64_bsta1000b/mod.rs new file mode 100644 index 000000000..8c7634bcf --- /dev/null +++ b/modules/axhal/src/platform/aarch64_bsta1000b/mod.rs @@ -0,0 +1,62 @@ +mod dw_apb_uart; + +pub mod mem; +pub mod misc; + +#[cfg(feature = "smp")] +pub mod mp; + +#[cfg(feature = "irq")] +pub mod irq { + pub use crate::platform::aarch64_common::gic::*; +} + +pub mod console { + pub use super::dw_apb_uart::*; +} + +pub mod time { + pub use crate::platform::aarch64_common::generic_timer::*; +} + +extern "C" { + fn exception_vector_base(); + fn rust_main(cpu_id: usize, dtb: usize); + #[cfg(feature = "smp")] + fn rust_main_secondary(cpu_id: usize); +} + +pub(crate) unsafe extern "C" fn rust_entry(cpu_id: usize, dtb: usize) { + crate::mem::clear_bss(); + crate::arch::set_exception_vector_base(exception_vector_base as usize); + crate::cpu::init_primary(cpu_id); + dw_apb_uart::init_early(); + super::aarch64_common::generic_timer::init_early(); + rust_main(cpu_id, dtb); +} + +#[cfg(feature = "smp")] +pub(crate) unsafe extern "C" fn rust_entry_secondary(cpu_id: usize) { + crate::arch::set_exception_vector_base(exception_vector_base as usize); + crate::cpu::init_secondary(cpu_id); + rust_main_secondary(cpu_id); +} + +/// Initializes the platform devices for the primary CPU. +/// +/// For example, the interrupt controller and the timer. +pub fn platform_init() { + #[cfg(feature = "irq")] + super::aarch64_common::gic::init_primary(); + super::aarch64_common::generic_timer::init_percpu(); + #[cfg(feature = "irq")] + dw_apb_uart::init_irq(); +} + +/// Initializes the platform devices for secondary CPUs. +#[cfg(feature = "smp")] +pub fn platform_init_secondary() { + #[cfg(feature = "irq")] + super::aarch64_common::gic::init_secondary(); + super::aarch64_common::generic_timer::init_percpu(); +} diff --git a/modules/axhal/src/platform/aarch64_bsta1000b/mp.rs b/modules/axhal/src/platform/aarch64_bsta1000b/mp.rs new file mode 100644 index 000000000..489f342fa --- /dev/null +++ b/modules/axhal/src/platform/aarch64_bsta1000b/mp.rs @@ -0,0 +1,23 @@ +use crate::mem::{virt_to_phys, PhysAddr, VirtAddr}; + +/// Hart number of bsta1000b board +pub const MAX_HARTS: usize = 8; +/// CPU HWID from cpu device tree nodes with "reg" property +pub const CPU_HWID: [usize; MAX_HARTS] = [0x00, 0x100, 0x200, 0x300, 0x400, 0x500, 0x600, 0x700]; + +/// Starts the given secondary CPU with its boot stack. +pub fn start_secondary_cpu(cpu_id: usize, stack_top: PhysAddr) { + if cpu_id >= MAX_HARTS { + error!("No support for bsta1000b core {}", cpu_id); + return; + } + extern "C" { + fn _start_secondary(); + } + let entry = virt_to_phys(VirtAddr::from(_start_secondary as usize)); + crate::platform::aarch64_common::psci::cpu_on( + CPU_HWID[cpu_id], + entry.as_usize(), + stack_top.as_usize(), + ); +} diff --git a/modules/axhal/src/platform/aarch64_common/boot.rs b/modules/axhal/src/platform/aarch64_common/boot.rs new file mode 100644 index 000000000..a944e1811 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/boot.rs @@ -0,0 +1,173 @@ +use aarch64_cpu::{asm, asm::barrier, registers::*}; +use memory_addr::PhysAddr; +use page_table_entry::aarch64::{MemAttr, A64PTE}; +use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; + +use axconfig::TASK_STACK_SIZE; + +#[link_section = ".bss.stack"] +static mut BOOT_STACK: [u8; TASK_STACK_SIZE] = [0; TASK_STACK_SIZE]; + +#[link_section = ".data.boot_page_table"] +static mut BOOT_PT_L0: [A64PTE; 512] = [A64PTE::empty(); 512]; + +#[link_section = ".data.boot_page_table"] +static mut BOOT_PT_L1: [A64PTE; 512] = [A64PTE::empty(); 512]; + +unsafe fn switch_to_el1() { + SPSel.write(SPSel::SP::ELx); + SP_EL0.set(0); + let current_el = CurrentEL.read(CurrentEL::EL); + if current_el >= 2 { + if current_el == 3 { + // Set EL2 to 64bit and enable the HVC instruction. + SCR_EL3.write( + SCR_EL3::NS::NonSecure + SCR_EL3::HCE::HvcEnabled + SCR_EL3::RW::NextELIsAarch64, + ); + // Set the return address and exception level. + SPSR_EL3.write( + SPSR_EL3::M::EL1h + + SPSR_EL3::D::Masked + + SPSR_EL3::A::Masked + + SPSR_EL3::I::Masked + + SPSR_EL3::F::Masked, + ); + ELR_EL3.set(LR.get()); + } + // Disable EL1 timer traps and the timer offset. + CNTHCTL_EL2.modify(CNTHCTL_EL2::EL1PCEN::SET + CNTHCTL_EL2::EL1PCTEN::SET); + CNTVOFF_EL2.set(0); + // Set EL1 to 64bit. + HCR_EL2.write(HCR_EL2::RW::EL1IsAarch64); + // Set the return address and exception level. + SPSR_EL2.write( + SPSR_EL2::M::EL1h + + SPSR_EL2::D::Masked + + SPSR_EL2::A::Masked + + SPSR_EL2::I::Masked + + SPSR_EL2::F::Masked, + ); + core::arch::asm!( + " + mov x8, sp + msr sp_el1, x8" + ); + ELR_EL2.set(LR.get()); + asm::eret(); + } +} + +unsafe fn init_mmu() { + MAIR_EL1.set(MemAttr::MAIR_VALUE); + + // Enable TTBR0 and TTBR1 walks, page size = 4K, vaddr size = 48 bits, paddr size = 40 bits. + let tcr_flags0 = TCR_EL1::EPD0::EnableTTBR0Walks + + TCR_EL1::TG0::KiB_4 + + TCR_EL1::SH0::Inner + + TCR_EL1::ORGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::IRGN0::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::T0SZ.val(16); + let tcr_flags1 = TCR_EL1::EPD1::EnableTTBR1Walks + + TCR_EL1::TG1::KiB_4 + + TCR_EL1::SH1::Inner + + TCR_EL1::ORGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::IRGN1::WriteBack_ReadAlloc_WriteAlloc_Cacheable + + TCR_EL1::T1SZ.val(16); + TCR_EL1.write(TCR_EL1::IPS::Bits_48 + tcr_flags0 + tcr_flags1); + barrier::isb(barrier::SY); + + // Set both TTBR0 and TTBR1 + let root_paddr = PhysAddr::from(BOOT_PT_L0.as_ptr() as usize).as_usize() as _; + TTBR0_EL1.set(root_paddr); + TTBR1_EL1.set(root_paddr); + + // Flush the entire TLB + crate::arch::flush_tlb(None); + + // Enable the MMU and turn on I-cache and D-cache + SCTLR_EL1.modify(SCTLR_EL1::M::Enable + SCTLR_EL1::C::Cacheable + SCTLR_EL1::I::Cacheable); + barrier::isb(barrier::SY); +} + +unsafe fn enable_fp() { + if cfg!(feature = "fp_simd") { + CPACR_EL1.write(CPACR_EL1::FPEN::TrapNothing); + barrier::isb(barrier::SY); + } +} + +unsafe fn init_boot_page_table() { + crate::platform::mem::init_boot_page_table(&mut BOOT_PT_L0, &mut BOOT_PT_L1); +} + +/// The earliest entry point for the primary CPU. +#[naked] +#[no_mangle] +#[link_section = ".text.boot"] +unsafe extern "C" fn _start() -> ! { + // PC = 0x8_0000 + // X0 = dtb + core::arch::asm!(" + mrs x19, mpidr_el1 + and x19, x19, #0xffffff // get current CPU id + mov x20, x0 // save DTB pointer + + adrp x8, {boot_stack} // setup boot stack + add x8, x8, {boot_stack_size} + mov sp, x8 + + bl {switch_to_el1} // switch to EL1 + bl {init_boot_page_table} + bl {init_mmu} // setup MMU + bl {enable_fp} // enable fp/neon + + mov x8, {phys_virt_offset} // set SP to the high address + add sp, sp, x8 + + mov x0, x19 // call rust_entry(cpu_id, dtb) + mov x1, x20 + ldr x8, ={entry} + blr x8 + b .", + switch_to_el1 = sym switch_to_el1, + init_boot_page_table = sym init_boot_page_table, + init_mmu = sym init_mmu, + enable_fp = sym enable_fp, + boot_stack = sym BOOT_STACK, + boot_stack_size = const TASK_STACK_SIZE, + phys_virt_offset = const axconfig::PHYS_VIRT_OFFSET, + entry = sym crate::platform::rust_entry, + options(noreturn), + ) +} + +/// The earliest entry point for the secondary CPUs. +#[cfg(feature = "smp")] +#[naked] +#[no_mangle] +#[link_section = ".text.boot"] +unsafe extern "C" fn _start_secondary() -> ! { + core::arch::asm!(" + mrs x19, mpidr_el1 + and x19, x19, #0xffffff // get current CPU id + + mov sp, x0 + bl {switch_to_el1} + bl {init_mmu} + bl {enable_fp} + + mov x8, {phys_virt_offset} // set SP to the high address + add sp, sp, x8 + + mov x0, x19 // call rust_entry_secondary(cpu_id) + ldr x8, ={entry} + blr x8 + b .", + switch_to_el1 = sym switch_to_el1, + init_mmu = sym init_mmu, + enable_fp = sym enable_fp, + phys_virt_offset = const axconfig::PHYS_VIRT_OFFSET, + entry = sym crate::platform::rust_entry_secondary, + options(noreturn), + ) +} diff --git a/modules/axhal/src/platform/aarch64_common/generic_timer.rs b/modules/axhal/src/platform/aarch64_common/generic_timer.rs new file mode 100644 index 000000000..59b76d165 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/generic_timer.rs @@ -0,0 +1,60 @@ +#![allow(unused_imports)] + +use aarch64_cpu::registers::{CNTFRQ_EL0, CNTPCT_EL0, CNTP_CTL_EL0, CNTP_TVAL_EL0}; +use ratio::Ratio; +use tock_registers::interfaces::{Readable, Writeable}; + +static mut CNTPCT_TO_NANOS_RATIO: Ratio = Ratio::zero(); +static mut NANOS_TO_CNTPCT_RATIO: Ratio = Ratio::zero(); + +/// Returns the current clock time in hardware ticks. +#[inline] +pub fn current_ticks() -> u64 { + CNTPCT_EL0.get() +} + +/// Converts hardware ticks to nanoseconds. +#[inline] +pub fn ticks_to_nanos(ticks: u64) -> u64 { + unsafe { CNTPCT_TO_NANOS_RATIO.mul_trunc(ticks) } +} + +/// Converts nanoseconds to hardware ticks. +#[inline] +pub fn nanos_to_ticks(nanos: u64) -> u64 { + unsafe { NANOS_TO_CNTPCT_RATIO.mul_trunc(nanos) } +} + +/// Set a one-shot timer. +/// +/// A timer interrupt will be triggered at the given deadline (in nanoseconds). +#[cfg(feature = "irq")] +pub fn set_oneshot_timer(deadline_ns: u64) { + let cnptct = CNTPCT_EL0.get(); + let cnptct_deadline = nanos_to_ticks(deadline_ns); + if cnptct < cnptct_deadline { + let interval = cnptct_deadline - cnptct; + debug_assert!(interval <= u32::MAX as u64); + CNTP_TVAL_EL0.set(interval); + } else { + CNTP_TVAL_EL0.set(0); + } +} + +/// Early stage initialization: stores the timer frequency. +pub(crate) fn init_early() { + let freq = CNTFRQ_EL0.get(); + unsafe { + CNTPCT_TO_NANOS_RATIO = Ratio::new(crate::time::NANOS_PER_SEC as u32, freq as u32); + NANOS_TO_CNTPCT_RATIO = CNTPCT_TO_NANOS_RATIO.inverse(); + } +} + +pub(crate) fn init_percpu() { + #[cfg(feature = "irq")] + { + CNTP_CTL_EL0.write(CNTP_CTL_EL0::ENABLE::SET); + CNTP_TVAL_EL0.set(0); + crate::platform::irq::set_enable(crate::platform::irq::TIMER_IRQ_NUM, true); + } +} diff --git a/modules/axhal/src/platform/aarch64_common/gic.rs b/modules/axhal/src/platform/aarch64_common/gic.rs new file mode 100644 index 000000000..362a73ec7 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/gic.rs @@ -0,0 +1,60 @@ +use crate::{irq::IrqHandler, mem::phys_to_virt}; +use arm_gic::gic_v2::{GicCpuInterface, GicDistributor}; +use arm_gic::{translate_irq, InterruptType}; +use memory_addr::PhysAddr; +use spinlock::SpinNoIrq; + +/// The maximum number of IRQs. +pub const MAX_IRQ_COUNT: usize = 1024; + +/// The timer IRQ number. +pub const TIMER_IRQ_NUM: usize = translate_irq(14, InterruptType::PPI).unwrap(); + +/// The UART IRQ number. +pub const UART_IRQ_NUM: usize = translate_irq(axconfig::UART_IRQ, InterruptType::SPI).unwrap(); + +const GICD_BASE: PhysAddr = PhysAddr::from(axconfig::GICD_PADDR); +const GICC_BASE: PhysAddr = PhysAddr::from(axconfig::GICC_PADDR); + +static GICD: SpinNoIrq = + SpinNoIrq::new(GicDistributor::new(phys_to_virt(GICD_BASE).as_mut_ptr())); + +// per-CPU, no lock +static GICC: GicCpuInterface = GicCpuInterface::new(phys_to_virt(GICC_BASE).as_mut_ptr()); + +/// Enables or disables the given IRQ. +pub fn set_enable(irq_num: usize, enabled: bool) { + trace!("GICD set enable: {} {}", irq_num, enabled); + GICD.lock().set_enable(irq_num as _, enabled); +} + +/// Registers an IRQ handler for the given IRQ. +/// +/// It also enables the IRQ if the registration succeeds. It returns `false` if +/// the registration failed. +pub fn register_handler(irq_num: usize, handler: IrqHandler) -> bool { + trace!("register handler irq {}", irq_num); + crate::irq::register_handler_common(irq_num, handler) +} + +/// Dispatches the IRQ. +/// +/// This function is called by the common interrupt handler. It looks +/// up in the IRQ handler table and calls the corresponding handler. If +/// necessary, it also acknowledges the interrupt controller after handling. +pub fn dispatch_irq(_unused: usize) { + GICC.handle_irq(|irq_num| crate::irq::dispatch_irq_common(irq_num as _)); +} + +/// Initializes GICD, GICC on the primary CPU. +pub(crate) fn init_primary() { + info!("Initialize GICv2..."); + GICD.lock().init(); + GICC.init(); +} + +/// Initializes GICC on secondary CPUs. +#[cfg(feature = "smp")] +pub(crate) fn init_secondary() { + GICC.init(); +} diff --git a/modules/axhal/src/platform/aarch64_common/mod.rs b/modules/axhal/src/platform/aarch64_common/mod.rs new file mode 100644 index 000000000..c585541fe --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/mod.rs @@ -0,0 +1,11 @@ +mod boot; + +pub mod generic_timer; +#[cfg(not(platform_family = "aarch64-raspi"))] +pub mod psci; + +#[cfg(feature = "irq")] +pub mod gic; + +#[cfg(not(platform_family = "aarch64-bsta1000b"))] +pub mod pl011; diff --git a/modules/axhal/src/platform/aarch64_common/pl011.rs b/modules/axhal/src/platform/aarch64_common/pl011.rs new file mode 100644 index 000000000..e1c9083e7 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/pl011.rs @@ -0,0 +1,51 @@ +//! PL011 UART. + +use arm_pl011::pl011::Pl011Uart; +use memory_addr::PhysAddr; +use spinlock::SpinNoIrq; + +use crate::mem::phys_to_virt; + +const UART_BASE: PhysAddr = PhysAddr::from(axconfig::UART_PADDR); + +static UART: SpinNoIrq = + SpinNoIrq::new(Pl011Uart::new(phys_to_virt(UART_BASE).as_mut_ptr())); + +/// Writes a byte to the console. +pub fn putchar(c: u8) { + let mut uart = UART.lock(); + match c { + b'\n' => { + uart.putchar(b'\r'); + uart.putchar(b'\n'); + } + c => uart.putchar(c), + } +} + +/// Reads a byte from the console, or returns [`None`] if no input is available. +pub fn getchar() -> Option { + UART.lock().getchar() +} + +/// Initialize the UART +pub fn init_early() { + UART.lock().init(); +} + +/// Set UART IRQ Enable +pub fn init() { + #[cfg(feature = "irq")] + crate::irq::set_enable(crate::platform::irq::UART_IRQ_NUM, true); +} + +/// UART IRQ Handler +pub fn handle() { + let is_receive_interrupt = UART.lock().is_receive_interrupt(); + UART.lock().ack_interrupts(); + if is_receive_interrupt { + while let Some(c) = getchar() { + putchar(c); + } + } +} diff --git a/modules/axhal/src/platform/aarch64_common/psci.rs b/modules/axhal/src/platform/aarch64_common/psci.rs new file mode 100644 index 000000000..feca7ba6d --- /dev/null +++ b/modules/axhal/src/platform/aarch64_common/psci.rs @@ -0,0 +1,129 @@ +//! ARM Power State Coordination Interface. + +#![allow(dead_code)] + +pub const PSCI_0_2_FN_BASE: u32 = 0x84000000; +pub const PSCI_0_2_64BIT: u32 = 0x40000000; +pub const PSCI_0_2_FN_CPU_SUSPEND: u32 = PSCI_0_2_FN_BASE + 1; +pub const PSCI_0_2_FN_CPU_OFF: u32 = PSCI_0_2_FN_BASE + 2; +pub const PSCI_0_2_FN_CPU_ON: u32 = PSCI_0_2_FN_BASE + 3; +pub const PSCI_0_2_FN_MIGRATE: u32 = PSCI_0_2_FN_BASE + 5; +pub const PSCI_0_2_FN_SYSTEM_OFF: u32 = PSCI_0_2_FN_BASE + 8; +pub const PSCI_0_2_FN_SYSTEM_RESET: u32 = PSCI_0_2_FN_BASE + 9; +pub const PSCI_0_2_FN64_CPU_SUSPEND: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 1; +pub const PSCI_0_2_FN64_CPU_ON: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 3; +pub const PSCI_0_2_FN64_MIGRATE: u32 = PSCI_0_2_FN_BASE + PSCI_0_2_64BIT + 5; + +/// PSCI return values, inclusive of all PSCI versions. +#[derive(PartialEq, Debug)] +#[repr(i32)] +pub enum PsciError { + NotSupported = -1, + InvalidParams = -2, + Denied = -3, + AlreadyOn = -4, + OnPending = -5, + InternalFailure = -6, + NotPresent = -7, + Disabled = -8, + InvalidAddress = -9, +} + +impl From for PsciError { + fn from(code: i32) -> PsciError { + use PsciError::*; + match code { + -1 => NotSupported, + -2 => InvalidParams, + -3 => Denied, + -4 => AlreadyOn, + -5 => OnPending, + -6 => InternalFailure, + -7 => NotPresent, + -8 => Disabled, + -9 => InvalidAddress, + _ => panic!("Unknown PSCI error code: {}", code), + } + } +} + +/// arm,psci method: smc +/// when SMCCC_CONDUIT_SMC = 1 +fn arm_smccc_smc(func: u32, arg0: usize, arg1: usize, arg2: usize) -> usize { + let mut ret; + unsafe { + core::arch::asm!( + "smc #0", + inlateout("x0") func as usize => ret, + in("x1") arg0, + in("x2") arg1, + in("x3") arg2, + ) + } + ret +} + +/// psci "hvc" method call +fn psci_hvc_call(func: u32, arg0: usize, arg1: usize, arg2: usize) -> usize { + let ret; + unsafe { + core::arch::asm!( + "hvc #0", + inlateout("x0") func as usize => ret, + in("x1") arg0, + in("x2") arg1, + in("x3") arg2, + ) + } + ret +} + +fn psci_call(func: u32, arg0: usize, arg1: usize, arg2: usize) -> Result<(), PsciError> { + let ret = match axconfig::PSCI_METHOD { + "smc" => arm_smccc_smc(func, arg0, arg1, arg2), + "hvc" => psci_hvc_call(func, arg0, arg1, arg2), + _ => panic!("Unknown PSCI method: {}", axconfig::PSCI_METHOD), + }; + if ret == 0 { + Ok(()) + } else { + Err(PsciError::from(ret as i32)) + } +} + +/// Shutdown the whole system, including all CPUs. +pub fn system_off() -> ! { + info!("Shutting down..."); + psci_call(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0).ok(); + warn!("It should shutdown!"); + loop { + crate::arch::halt(); + } +} + +/// Power up a core. This call is used to power up cores that either: +/// +/// * Have not yet been booted into the calling supervisory software. +/// * Have been previously powered down with a `cpu_off` call. +/// +/// `target_cpu` contains a copy of the affinity fields of the MPIDR register. +/// `entry_point` is the physical address of the secondary CPU's entry point. +/// `arg` will be passed to the `X0` register of the secondary CPU. +pub fn cpu_on(target_cpu: usize, entry_point: usize, arg: usize) { + info!("Starting CPU {:x} ON ...", target_cpu); + let res = psci_call(PSCI_0_2_FN64_CPU_ON, target_cpu, entry_point, arg); + if let Err(e) = res { + error!("failed to boot CPU {:x} ({:?})", target_cpu, e); + } +} + +/// Power down the calling core. This call is intended for use in hotplug. A +/// core that is powered down by `cpu_off` can only be powered up again in +/// response to a `cpu_on`. +pub fn cpu_off() { + const PSCI_POWER_STATE_TYPE_STANDBY: u32 = 0; + const PSCI_POWER_STATE_TYPE_POWER_DOWN: u32 = 1; + const PSCI_0_2_POWER_STATE_TYPE_SHIFT: u32 = 16; + let state: u32 = PSCI_POWER_STATE_TYPE_POWER_DOWN << PSCI_0_2_POWER_STATE_TYPE_SHIFT; + psci_call(PSCI_0_2_FN_CPU_OFF, state as usize, 0, 0).ok(); +} diff --git a/modules/axhal/src/platform/aarch64_qemu_virt/mem.rs b/modules/axhal/src/platform/aarch64_qemu_virt/mem.rs new file mode 100644 index 000000000..8218bda67 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_qemu_virt/mem.rs @@ -0,0 +1,27 @@ +use crate::mem::{MemRegion, PhysAddr}; +use page_table_entry::{aarch64::A64PTE, GenericPTE, MappingFlags}; + +/// Returns platform-specific memory regions. +pub(crate) fn platform_regions() -> impl Iterator { + crate::mem::default_free_regions().chain(crate::mem::default_mmio_regions()) +} + +pub(crate) unsafe fn init_boot_page_table( + boot_pt_l0: &mut [A64PTE; 512], + boot_pt_l1: &mut [A64PTE; 512], +) { + // 0x0000_0000_0000 ~ 0x0080_0000_0000, table + boot_pt_l0[0] = A64PTE::new_table(PhysAddr::from(boot_pt_l1.as_ptr() as usize)); + // 0x0000_0000_0000..0x0000_4000_0000, 1G block, device memory + boot_pt_l1[0] = A64PTE::new_page( + PhysAddr::from(0), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::DEVICE, + true, + ); + // 0x0000_4000_0000..0x0000_8000_0000, 1G block, normal memory + boot_pt_l1[1] = A64PTE::new_page( + PhysAddr::from(0x4000_0000), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE, + true, + ); +} diff --git a/modules/axhal/src/platform/aarch64_qemu_virt/mod.rs b/modules/axhal/src/platform/aarch64_qemu_virt/mod.rs new file mode 100644 index 000000000..2a452fa08 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_qemu_virt/mod.rs @@ -0,0 +1,64 @@ +pub mod mem; + +#[cfg(feature = "smp")] +pub mod mp; + +#[cfg(feature = "irq")] +pub mod irq { + pub use crate::platform::aarch64_common::gic::*; +} + +pub mod console { + pub use crate::platform::aarch64_common::pl011::*; +} + +pub mod time { + pub use crate::platform::aarch64_common::generic_timer::*; +} + +pub mod misc { + pub use crate::platform::aarch64_common::psci::system_off as terminate; +} + +extern "C" { + fn exception_vector_base(); + fn rust_main(cpu_id: usize, dtb: usize); + #[cfg(feature = "smp")] + fn rust_main_secondary(cpu_id: usize); +} + +pub(crate) unsafe extern "C" fn rust_entry(cpu_id: usize, dtb: usize) { + crate::mem::clear_bss(); + crate::arch::set_exception_vector_base(exception_vector_base as usize); + crate::arch::write_page_table_root0(0.into()); // disable low address access + crate::cpu::init_primary(cpu_id); + super::aarch64_common::pl011::init_early(); + super::aarch64_common::generic_timer::init_early(); + rust_main(cpu_id, dtb); +} + +#[cfg(feature = "smp")] +pub(crate) unsafe extern "C" fn rust_entry_secondary(cpu_id: usize) { + crate::arch::set_exception_vector_base(exception_vector_base as usize); + crate::arch::write_page_table_root0(0.into()); // disable low address access + crate::cpu::init_secondary(cpu_id); + rust_main_secondary(cpu_id); +} + +/// Initializes the platform devices for the primary CPU. +/// +/// For example, the interrupt controller and the timer. +pub fn platform_init() { + #[cfg(feature = "irq")] + super::aarch64_common::gic::init_primary(); + super::aarch64_common::generic_timer::init_percpu(); + super::aarch64_common::pl011::init(); +} + +/// Initializes the platform devices for secondary CPUs. +#[cfg(feature = "smp")] +pub fn platform_init_secondary() { + #[cfg(feature = "irq")] + super::aarch64_common::gic::init_secondary(); + super::aarch64_common::generic_timer::init_percpu(); +} diff --git a/modules/axhal/src/platform/aarch64_qemu_virt/mp.rs b/modules/axhal/src/platform/aarch64_qemu_virt/mp.rs new file mode 100644 index 000000000..9a619bfcb --- /dev/null +++ b/modules/axhal/src/platform/aarch64_qemu_virt/mp.rs @@ -0,0 +1,10 @@ +use crate::mem::{virt_to_phys, PhysAddr, VirtAddr}; + +/// Starts the given secondary CPU with its boot stack. +pub fn start_secondary_cpu(cpu_id: usize, stack_top: PhysAddr) { + extern "C" { + fn _start_secondary(); + } + let entry = virt_to_phys(VirtAddr::from(_start_secondary as usize)); + crate::platform::aarch64_common::psci::cpu_on(cpu_id, entry.as_usize(), stack_top.as_usize()); +} diff --git a/modules/axhal/src/platform/aarch64_raspi/mem.rs b/modules/axhal/src/platform/aarch64_raspi/mem.rs new file mode 100644 index 000000000..7c426e08f --- /dev/null +++ b/modules/axhal/src/platform/aarch64_raspi/mem.rs @@ -0,0 +1,46 @@ +use crate::mem::*; +use page_table_entry::{aarch64::A64PTE, GenericPTE, MappingFlags}; + +/// Returns platform-specific memory regions. +pub(crate) fn platform_regions() -> impl Iterator { + core::iter::once(MemRegion { + paddr: 0x0.into(), + size: 0x1000, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "spintable", + }) + .chain(crate::mem::default_free_regions()) + .chain(crate::mem::default_mmio_regions()) +} + +pub(crate) unsafe fn init_boot_page_table( + boot_pt_l0: &mut [A64PTE; 512], + boot_pt_l1: &mut [A64PTE; 512], +) { + // 0x0000_0000_0000 ~ 0x0080_0000_0000, table + boot_pt_l0[0] = A64PTE::new_table(PhysAddr::from(boot_pt_l1.as_ptr() as usize)); + // 0x0000_0000_0000..0x0000_4000_0000, 1G block, device memory + boot_pt_l1[0] = A64PTE::new_page( + PhysAddr::from(0), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE, + true, + ); + // 0x0000_4000_0000..0x0000_8000_0000, 1G block, normal memory + boot_pt_l1[1] = A64PTE::new_page( + PhysAddr::from(0x4000_0000), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE, + true, + ); + // 0x0000_8000_0000..0x0000_C000_0000, 1G block, normal memory + boot_pt_l1[2] = A64PTE::new_page( + PhysAddr::from(0x8000_0000), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::EXECUTE, + true, + ); + // 0x0000_C000_0000..0x0001_0000_0000, 1G block, DEVICE memory + boot_pt_l1[3] = A64PTE::new_page( + PhysAddr::from(0xc000_0000), + MappingFlags::READ | MappingFlags::WRITE | MappingFlags::DEVICE, + true, + ); +} diff --git a/modules/axhal/src/platform/aarch64_raspi/mod.rs b/modules/axhal/src/platform/aarch64_raspi/mod.rs new file mode 100644 index 000000000..c7c7264d4 --- /dev/null +++ b/modules/axhal/src/platform/aarch64_raspi/mod.rs @@ -0,0 +1,69 @@ +pub mod mem; + +#[cfg(feature = "smp")] +pub mod mp; + +#[cfg(feature = "irq")] +pub mod irq { + pub use crate::platform::aarch64_common::gic::*; +} + +pub mod console { + pub use crate::platform::aarch64_common::pl011::*; +} + +pub mod time { + pub use crate::platform::aarch64_common::generic_timer::*; +} + +pub mod misc { + pub fn terminate() -> ! { + info!("Shutting down..."); + loop { + crate::arch::halt(); + } + } +} + +extern "C" { + fn exception_vector_base(); + fn rust_main(cpu_id: usize, dtb: usize); + #[cfg(feature = "smp")] + fn rust_main_secondary(cpu_id: usize); +} + +pub(crate) unsafe extern "C" fn rust_entry(cpu_id: usize, dtb: usize) { + crate::mem::clear_bss(); + crate::arch::set_exception_vector_base(exception_vector_base as usize); + crate::arch::write_page_table_root0(0.into()); // disable low address access + crate::cpu::init_primary(cpu_id); + super::aarch64_common::pl011::init_early(); + super::aarch64_common::generic_timer::init_early(); + rust_main(cpu_id, dtb); +} + +#[cfg(feature = "smp")] +pub(crate) unsafe extern "C" fn rust_entry_secondary(cpu_id: usize) { + crate::arch::set_exception_vector_base(exception_vector_base as usize); + crate::arch::write_page_table_root0(0.into()); // disable low address access + crate::cpu::init_secondary(cpu_id); + rust_main_secondary(cpu_id); +} + +/// Initializes the platform devices for the primary CPU. +/// +/// For example, the interrupt controller and the timer. +pub fn platform_init() { + #[cfg(feature = "irq")] + super::aarch64_common::gic::init_primary(); + super::aarch64_common::generic_timer::init_percpu(); + super::aarch64_common::pl011::init(); +} + +/// Initializes the platform devices for secondary CPUs. +#[cfg(feature = "smp")] +pub fn platform_init_secondary() { + #[cfg(feature = "irq")] + super::aarch64_common::gic::init_secondary(); + super::aarch64_common::generic_timer::init_percpu(); +} diff --git a/modules/axhal/src/platform/aarch64_raspi/mp.rs b/modules/axhal/src/platform/aarch64_raspi/mp.rs new file mode 100644 index 000000000..23549c01f --- /dev/null +++ b/modules/axhal/src/platform/aarch64_raspi/mp.rs @@ -0,0 +1,49 @@ +use crate::mem::{phys_to_virt, virt_to_phys, PhysAddr, VirtAddr}; + +static mut SECONDARY_STACK_TOP: usize = 0; + +extern "C" { + fn _start_secondary(); +} + +#[naked] +#[link_section = ".text.boot"] +unsafe extern "C" fn modify_stack_and_start() { + core::arch::asm!(" + ldr x21, ={secondary_boot_stack} // the secondary CPU hasn't set the TTBR1 + mov x8, {phys_virt_offset} // minus the offset to get the phys addr of the boot stack + sub x21, x21, x8 + ldr x21, [x21] + mov x0, x21 // x0 will be set to SP in the beginning of _start_secondary + b _start_secondary", + secondary_boot_stack = sym SECONDARY_STACK_TOP, + phys_virt_offset = const axconfig::PHYS_VIRT_OFFSET, + options(noreturn) + ); +} + +pub static CPU_SPIN_TABLE: [PhysAddr; 4] = [ + PhysAddr::from(0xd8), + PhysAddr::from(0xe0), + PhysAddr::from(0xe8), + PhysAddr::from(0xf0), +]; + +/// Starts the given secondary CPU with its boot stack. +pub fn start_secondary_cpu(cpu_id: usize, stack_top: PhysAddr) { + let entry_paddr = virt_to_phys(VirtAddr::from(modify_stack_and_start as usize)).as_usize(); + unsafe { + // set the boot code address of the given secondary CPU + let spintable_vaddr = phys_to_virt(CPU_SPIN_TABLE[cpu_id]); + let release_ptr = spintable_vaddr.as_mut_ptr() as *mut usize; + release_ptr.write_volatile(entry_paddr); + crate::arch::flush_dcache_line(spintable_vaddr); + + // set the boot stack of the given secondary CPU + SECONDARY_STACK_TOP = stack_top.as_usize(); + crate::arch::flush_dcache_line(VirtAddr::from( + (&SECONDARY_STACK_TOP as *const usize) as usize, + )); + } + aarch64_cpu::asm::sev(); +} diff --git a/modules/axhal/src/platform/dummy/mod.rs b/modules/axhal/src/platform/dummy/mod.rs new file mode 100644 index 000000000..be0ae11d0 --- /dev/null +++ b/modules/axhal/src/platform/dummy/mod.rs @@ -0,0 +1,87 @@ +#![allow(unused_variables)] +#![allow(dead_code)] + +pub mod console { + /// Writes a byte to the console. + pub fn putchar(c: u8) { + unimplemented!() + } + + /// Reads a byte from the console, or returns [`None`] if no input is available. + pub fn getchar() -> Option { + unimplemented!() + } +} + +pub mod misc { + /// Shutdown the whole system, including all CPUs. + pub fn terminate() -> ! { + unimplemented!() + } +} + +#[cfg(feature = "smp")] +pub mod mp { + /// Starts the given secondary CPU with its boot stack. + pub fn start_secondary_cpu(cpu_id: usize, stack_top: crate::mem::PhysAddr) {} +} + +pub mod mem { + /// Returns platform-specific memory regions. + pub(crate) fn platform_regions() -> impl Iterator { + core::iter::empty() + } +} + +pub mod time { + /// Returns the current clock time in hardware ticks. + pub fn current_ticks() -> u64 { + 0 + } + + /// Converts hardware ticks to nanoseconds. + pub fn ticks_to_nanos(ticks: u64) -> u64 { + ticks + } + + /// Converts nanoseconds to hardware ticks. + pub fn nanos_to_ticks(nanos: u64) -> u64 { + nanos + } + + /// Set a one-shot timer. + /// + /// A timer interrupt will be triggered at the given deadline (in nanoseconds). + pub fn set_oneshot_timer(deadline_ns: u64) {} +} + +#[cfg(feature = "irq")] +pub mod irq { + /// The maximum number of IRQs. + pub const MAX_IRQ_COUNT: usize = 256; + + /// The timer IRQ number. + pub const TIMER_IRQ_NUM: usize = 0; + + /// Enables or disables the given IRQ. + pub fn set_enable(irq_num: usize, enabled: bool) {} + + /// Registers an IRQ handler for the given IRQ. + pub fn register_handler(irq_num: usize, handler: crate::irq::IrqHandler) -> bool { + false + } + + /// Dispatches the IRQ. + /// + /// This function is called by the common interrupt handler. It looks + /// up in the IRQ handler table and calls the corresponding handler. If + /// necessary, it also acknowledges the interrupt controller after handling. + pub fn dispatch_irq(irq_num: usize) {} +} + +/// Initializes the platform devices for the primary CPU. +pub fn platform_init() {} + +/// Initializes the platform devices for secondary CPUs. +#[cfg(feature = "smp")] +pub fn platform_init_secondary() {} diff --git a/modules/axhal/src/platform/mod.rs b/modules/axhal/src/platform/mod.rs new file mode 100644 index 000000000..a39151c47 --- /dev/null +++ b/modules/axhal/src/platform/mod.rs @@ -0,0 +1,29 @@ +//! Platform-specific operations. + +cfg_if::cfg_if! { + if #[cfg(target_arch = "aarch64")]{ + mod aarch64_common; + } +} + +cfg_if::cfg_if! { + if #[cfg(all(target_arch = "x86_64", platform_family = "x86-pc"))] { + mod x86_pc; + pub use self::x86_pc::*; + } else if #[cfg(all(target_arch = "riscv64", platform_family = "riscv64-qemu-virt"))] { + mod riscv64_qemu_virt; + pub use self::riscv64_qemu_virt::*; + } else if #[cfg(all(target_arch = "aarch64", platform_family = "aarch64-qemu-virt"))] { + mod aarch64_qemu_virt; + pub use self::aarch64_qemu_virt::*; + } else if #[cfg(all(target_arch = "aarch64", platform_family = "aarch64-raspi"))] { + mod aarch64_raspi; + pub use self::aarch64_raspi::*; + } else if #[cfg(all(target_arch = "aarch64", platform_family = "aarch64-bsta1000b"))] { + mod aarch64_bsta1000b; + pub use self::aarch64_bsta1000b::*; + } else { + mod dummy; + pub use self::dummy::*; + } +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/boot.rs b/modules/axhal/src/platform/riscv64_qemu_virt/boot.rs new file mode 100644 index 000000000..eb293133d --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/boot.rs @@ -0,0 +1,89 @@ +use riscv::register::satp; + +use axconfig::{PHYS_VIRT_OFFSET, TASK_STACK_SIZE}; + +#[link_section = ".bss.stack"] +static mut BOOT_STACK: [u8; TASK_STACK_SIZE] = [0; TASK_STACK_SIZE]; + +#[link_section = ".data.boot_page_table"] +static mut BOOT_PT_SV39: [u64; 512] = [0; 512]; + +unsafe fn init_boot_page_table() { + // 0x8000_0000..0xc000_0000, VRWX_GAD, 1G block + BOOT_PT_SV39[2] = (0x80000 << 10) | 0xef; + // 0xffff_ffc0_8000_0000..0xffff_ffc0_c000_0000, VRWX_GAD, 1G block + BOOT_PT_SV39[0x102] = (0x80000 << 10) | 0xef; +} + +unsafe fn init_mmu() { + let page_table_root = BOOT_PT_SV39.as_ptr() as usize; + satp::set(satp::Mode::Sv39, 0, page_table_root >> 12); + riscv::asm::sfence_vma_all(); +} + +/// The earliest entry point for the primary CPU. +#[naked] +#[no_mangle] +#[link_section = ".text.boot"] +unsafe extern "C" fn _start() -> ! { + // PC = 0x8020_0000 + // a0 = hartid + // a1 = dtb + core::arch::asm!(" + mv s0, a0 // save hartid + mv s1, a1 // save DTB pointer + la sp, {boot_stack} + li t0, {boot_stack_size} + add sp, sp, t0 // setup boot stack + + call {init_boot_page_table} + call {init_mmu} // setup boot page table and enabel MMU + + li s2, {phys_virt_offset} // fix up virtual high address + add sp, sp, s2 + + mv a0, s0 + mv a1, s1 + la a2, {entry} + add a2, a2, s2 + jalr a2 // call rust_entry(hartid, dtb) + j .", + phys_virt_offset = const PHYS_VIRT_OFFSET, + boot_stack_size = const TASK_STACK_SIZE, + boot_stack = sym BOOT_STACK, + init_boot_page_table = sym init_boot_page_table, + init_mmu = sym init_mmu, + entry = sym super::rust_entry, + options(noreturn), + ) +} + +/// The earliest entry point for secondary CPUs. +#[cfg(feature = "smp")] +#[naked] +#[no_mangle] +#[link_section = ".text.boot"] +unsafe extern "C" fn _start_secondary() -> ! { + // a0 = hartid + // a1 = SP + core::arch::asm!(" + mv s0, a0 // save hartid + mv sp, a1 // set SP + + call {init_mmu} // setup boot page table and enabel MMU + + li s1, {phys_virt_offset} // fix up virtual high address + add a1, a1, s1 + add sp, sp, s1 + + mv a0, s0 + la a1, {entry} + add a1, a1, s1 + jalr a1 // call rust_entry_secondary(hartid) + j .", + phys_virt_offset = const PHYS_VIRT_OFFSET, + init_mmu = sym init_mmu, + entry = sym super::rust_entry_secondary, + options(noreturn), + ) +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/console.rs b/modules/axhal/src/platform/riscv64_qemu_virt/console.rs new file mode 100644 index 000000000..a7ec3e646 --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/console.rs @@ -0,0 +1,14 @@ +/// Writes a byte to the console. +pub fn putchar(c: u8) { + #[allow(deprecated)] + sbi_rt::legacy::console_putchar(c as usize); +} + +/// Reads a byte from the console, or returns [`None`] if no input is available. +pub fn getchar() -> Option { + #[allow(deprecated)] + match sbi_rt::legacy::console_getchar() as isize { + -1 => None, + c => Some(c as u8), + } +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/irq.rs b/modules/axhal/src/platform/riscv64_qemu_virt/irq.rs new file mode 100644 index 000000000..f8effde7d --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/irq.rs @@ -0,0 +1,85 @@ +//! TODO: PLIC + +use crate::irq::IrqHandler; +use lazy_init::LazyInit; +use riscv::register::sie; + +/// `Interrupt` bit in `scause` +pub(super) const INTC_IRQ_BASE: usize = 1 << (usize::BITS - 1); + +/// Supervisor software interrupt in `scause` +#[allow(unused)] +pub(super) const S_SOFT: usize = INTC_IRQ_BASE + 1; + +/// Supervisor timer interrupt in `scause` +pub(super) const S_TIMER: usize = INTC_IRQ_BASE + 5; + +/// Supervisor external interrupt in `scause` +pub(super) const S_EXT: usize = INTC_IRQ_BASE + 9; + +static TIMER_HANDLER: LazyInit = LazyInit::new(); + +/// The maximum number of IRQs. +pub const MAX_IRQ_COUNT: usize = 1024; + +/// The timer IRQ number (supervisor timer interrupt in `scause`). +pub const TIMER_IRQ_NUM: usize = S_TIMER; + +macro_rules! with_cause { + ($cause: expr, @TIMER => $timer_op: expr, @EXT => $ext_op: expr $(,)?) => { + match $cause { + S_TIMER => $timer_op, + S_EXT => $ext_op, + _ => panic!("invalid trap cause: {:#x}", $cause), + } + }; +} + +/// Enables or disables the given IRQ. +pub fn set_enable(scause: usize, _enabled: bool) { + if scause == S_EXT { + // TODO: set enable in PLIC + } +} + +/// Registers an IRQ handler for the given IRQ. +/// +/// It also enables the IRQ if the registration succeeds. It returns `false` if +/// the registration failed. +pub fn register_handler(scause: usize, handler: IrqHandler) -> bool { + with_cause!( + scause, + @TIMER => if !TIMER_HANDLER.is_init() { + TIMER_HANDLER.init_by(handler); + true + } else { + false + }, + @EXT => crate::irq::register_handler_common(scause & !INTC_IRQ_BASE, handler), + ) +} + +/// Dispatches the IRQ. +/// +/// This function is called by the common interrupt handler. It looks +/// up in the IRQ handler table and calls the corresponding handler. If +/// necessary, it also acknowledges the interrupt controller after handling. +pub fn dispatch_irq(scause: usize) { + with_cause!( + scause, + @TIMER => { + trace!("IRQ: timer"); + TIMER_HANDLER(); + }, + @EXT => crate::irq::dispatch_irq_common(0), // TODO: get IRQ number from PLIC + ); +} + +pub(super) fn init_percpu() { + // enable soft interrupts, timer interrupts, and external interrupts + unsafe { + sie::set_ssoft(); + sie::set_stimer(); + sie::set_sext(); + } +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/mem.rs b/modules/axhal/src/platform/riscv64_qemu_virt/mem.rs new file mode 100644 index 000000000..bad6113c4 --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/mem.rs @@ -0,0 +1,6 @@ +use crate::mem::MemRegion; + +/// Returns platform-specific memory regions. +pub(crate) fn platform_regions() -> impl Iterator { + crate::mem::default_free_regions().chain(crate::mem::default_mmio_regions()) +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/misc.rs b/modules/axhal/src/platform/riscv64_qemu_virt/misc.rs new file mode 100644 index 000000000..6b6e02fcb --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/misc.rs @@ -0,0 +1,9 @@ +/// Shutdown the whole system, including all CPUs. +pub fn terminate() -> ! { + info!("Shutting down..."); + sbi_rt::system_reset(sbi_rt::Shutdown, sbi_rt::NoReason); + warn!("It should shutdown!"); + loop { + crate::arch::halt(); + } +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs b/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs new file mode 100644 index 000000000..3805c07de --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/mod.rs @@ -0,0 +1,50 @@ +mod boot; + +pub mod console; +pub mod mem; +pub mod misc; +pub mod time; + +#[cfg(feature = "irq")] +pub mod irq; + +#[cfg(feature = "smp")] +pub mod mp; + +extern "C" { + fn trap_vector_base(); + fn rust_main(cpu_id: usize, dtb: usize); + #[cfg(feature = "smp")] + fn rust_main_secondary(cpu_id: usize); +} + +unsafe extern "C" fn rust_entry(cpu_id: usize, dtb: usize) { + crate::mem::clear_bss(); + crate::cpu::init_primary(cpu_id); + crate::arch::set_trap_vector_base(trap_vector_base as usize); + rust_main(cpu_id, dtb); +} + +#[cfg(feature = "smp")] +unsafe extern "C" fn rust_entry_secondary(cpu_id: usize) { + crate::arch::set_trap_vector_base(trap_vector_base as usize); + crate::cpu::init_secondary(cpu_id); + rust_main_secondary(cpu_id); +} + +/// Initializes the platform devices for the primary CPU. +/// +/// For example, the interrupt controller and the timer. +pub fn platform_init() { + #[cfg(feature = "irq")] + self::irq::init_percpu(); + self::time::init_percpu(); +} + +/// Initializes the platform devices for secondary CPUs. +#[cfg(feature = "smp")] +pub fn platform_init_secondary() { + #[cfg(feature = "irq")] + self::irq::init_percpu(); + self::time::init_percpu(); +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/mp.rs b/modules/axhal/src/platform/riscv64_qemu_virt/mp.rs new file mode 100644 index 000000000..ad81481f5 --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/mp.rs @@ -0,0 +1,14 @@ +use crate::mem::{virt_to_phys, PhysAddr, VirtAddr}; + +/// Starts the given secondary CPU with its boot stack. +pub fn start_secondary_cpu(hartid: usize, stack_top: PhysAddr) { + extern "C" { + fn _start_secondary(); + } + if sbi_rt::probe_extension(sbi_rt::Hsm).is_unavailable() { + warn!("HSM SBI extension is not supported for current SEE."); + return; + } + let entry = virt_to_phys(VirtAddr::from(_start_secondary as usize)); + sbi_rt::hart_start(hartid, entry.as_usize(), stack_top.as_usize()); +} diff --git a/modules/axhal/src/platform/riscv64_qemu_virt/time.rs b/modules/axhal/src/platform/riscv64_qemu_virt/time.rs new file mode 100644 index 000000000..f206785fb --- /dev/null +++ b/modules/axhal/src/platform/riscv64_qemu_virt/time.rs @@ -0,0 +1,34 @@ +use riscv::register::time; + +const NANOS_PER_TICK: u64 = crate::time::NANOS_PER_SEC / axconfig::TIMER_FREQUENCY as u64; + +/// Returns the current clock time in hardware ticks. +#[inline] +pub fn current_ticks() -> u64 { + time::read() as u64 +} + +/// Converts hardware ticks to nanoseconds. +#[inline] +pub const fn ticks_to_nanos(ticks: u64) -> u64 { + ticks * NANOS_PER_TICK +} + +/// Converts nanoseconds to hardware ticks. +#[inline] +pub const fn nanos_to_ticks(nanos: u64) -> u64 { + nanos / NANOS_PER_TICK +} + +/// Set a one-shot timer. +/// +/// A timer interrupt will be triggered at the given deadline (in nanoseconds). +#[cfg(feature = "irq")] +pub fn set_oneshot_timer(deadline_ns: u64) { + sbi_rt::set_timer(nanos_to_ticks(deadline_ns)); +} + +pub(super) fn init_percpu() { + #[cfg(feature = "irq")] + sbi_rt::set_timer(0); +} diff --git a/modules/axhal/src/platform/x86_pc/ap_start.S b/modules/axhal/src/platform/x86_pc/ap_start.S new file mode 100644 index 000000000..9784f7672 --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/ap_start.S @@ -0,0 +1,69 @@ +# Boot application processors into the protected mode. + +# Each non-boot CPU ("AP") is started up in response to a STARTUP +# IPI from the boot CPU. Section B.4.2 of the Multi-Processor +# Specification says that the AP will start in real mode with CS:IP +# set to XY00:0000, where XY is an 8-bit value sent with the +# STARTUP. Thus this code must start at a 4096-byte boundary. +# +# Because this code sets DS to zero, it must sit +# at an address in the low 2^16 bytes. + +.equ pa_ap_start32, ap_start32 - ap_start + {start_page_paddr} +.equ pa_ap_gdt, .Lap_tmp_gdt - ap_start + {start_page_paddr} +.equ pa_ap_gdt_desc, .Lap_tmp_gdt_desc - ap_start + {start_page_paddr} + +.equ stack_ptr, {start_page_paddr} + 0xff0 +.equ entry_ptr, {start_page_paddr} + 0xff8 + +# 0x6000 +.section .text +.code16 +.p2align 12 +.global ap_start +ap_start: + cli + wbinvd + + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax + + # load the 64-bit GDT + lgdt [pa_ap_gdt_desc] + + # switch to protected-mode + mov eax, cr0 + or eax, (1 << 0) + mov cr0, eax + + # far jump to 32-bit code. 0x8 is code32 segment selector + ljmp 0x8, offset pa_ap_start32 + +.code32 +ap_start32: + mov esp, [stack_ptr] + mov eax, [entry_ptr] + jmp eax + +.balign 8 +# .type multiboot_header, STT_OBJECT +.Lap_tmp_gdt_desc: + .short .Lap_tmp_gdt_end - .Lap_tmp_gdt - 1 # limit + .long pa_ap_gdt # base + +.balign 16 +.Lap_tmp_gdt: + .quad 0x0000000000000000 # 0x00: null + .quad 0x00cf9b000000ffff # 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) + .quad 0x00af9b000000ffff # 0x10: code segment (base=0, limit=0xfffff, type=64bit code exec/read, DPL=0, 4k) + .quad 0x00cf93000000ffff # 0x18: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) +.Lap_tmp_gdt_end: + +# 0x7000 +.p2align 12 +.global ap_end +ap_end: diff --git a/modules/axhal/src/platform/x86_pc/apic.rs b/modules/axhal/src/platform/x86_pc/apic.rs new file mode 100644 index 000000000..eb4e3e593 --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/apic.rs @@ -0,0 +1,124 @@ +#![allow(dead_code)] + +use lazy_init::LazyInit; +use memory_addr::PhysAddr; +use spinlock::SpinNoIrq; +use x2apic::ioapic::IoApic; +use x2apic::lapic::{xapic_base, LocalApic, LocalApicBuilder}; +use x86_64::instructions::port::Port; + +use self::vectors::*; +use crate::mem::phys_to_virt; + +pub(super) mod vectors { + pub const APIC_TIMER_VECTOR: u8 = 0xf0; + pub const APIC_SPURIOUS_VECTOR: u8 = 0xf1; + pub const APIC_ERROR_VECTOR: u8 = 0xf2; +} + +/// The maximum number of IRQs. +pub const MAX_IRQ_COUNT: usize = 256; + +/// The timer IRQ number. +pub const TIMER_IRQ_NUM: usize = APIC_TIMER_VECTOR as usize; + +const IO_APIC_BASE: PhysAddr = PhysAddr::from(0xFEC0_0000); + +static mut LOCAL_APIC: Option = None; +static mut IS_X2APIC: bool = false; +static IO_APIC: LazyInit> = LazyInit::new(); + +/// Enables or disables the given IRQ. +#[cfg(feature = "irq")] +pub fn set_enable(vector: usize, enabled: bool) { + // should not affect LAPIC interrupts + if vector < APIC_TIMER_VECTOR as _ { + unsafe { + if enabled { + IO_APIC.lock().enable_irq(vector as u8); + } else { + IO_APIC.lock().disable_irq(vector as u8); + } + } + } +} + +/// Registers an IRQ handler for the given IRQ. +/// +/// It also enables the IRQ if the registration succeeds. It returns `false` if +/// the registration failed. +#[cfg(feature = "irq")] +pub fn register_handler(vector: usize, handler: crate::irq::IrqHandler) -> bool { + crate::irq::register_handler_common(vector, handler) +} + +/// Dispatches the IRQ. +/// +/// This function is called by the common interrupt handler. It looks +/// up in the IRQ handler table and calls the corresponding handler. If +/// necessary, it also acknowledges the interrupt controller after handling. +#[cfg(feature = "irq")] +pub fn dispatch_irq(vector: usize) { + crate::irq::dispatch_irq_common(vector); + unsafe { local_apic().end_of_interrupt() }; +} + +pub(super) fn local_apic<'a>() -> &'a mut LocalApic { + // It's safe as LAPIC is per-cpu. + unsafe { LOCAL_APIC.as_mut().unwrap() } +} + +pub(super) fn raw_apic_id(id_u8: u8) -> u32 { + if unsafe { IS_X2APIC } { + id_u8 as u32 + } else { + (id_u8 as u32) << 24 + } +} + +fn cpu_has_x2apic() -> bool { + match raw_cpuid::CpuId::new().get_feature_info() { + Some(finfo) => finfo.has_x2apic(), + None => false, + } +} + +pub(super) fn init_primary() { + info!("Initialize Local APIC..."); + + unsafe { + // Disable 8259A interrupt controllers + Port::::new(0x21).write(0xff); + Port::::new(0xA1).write(0xff); + } + + let mut builder = LocalApicBuilder::new(); + builder + .timer_vector(APIC_TIMER_VECTOR as _) + .error_vector(APIC_ERROR_VECTOR as _) + .spurious_vector(APIC_SPURIOUS_VECTOR as _); + + if cpu_has_x2apic() { + info!("Using x2APIC."); + unsafe { IS_X2APIC = true }; + } else { + info!("Using xAPIC."); + let base_vaddr = phys_to_virt(PhysAddr::from(unsafe { xapic_base() } as usize)); + builder.set_xapic_base(base_vaddr.as_usize() as u64); + } + + let mut lapic = builder.build().unwrap(); + unsafe { + lapic.enable(); + LOCAL_APIC = Some(lapic); + } + + info!("Initialize IO APIC..."); + let io_apic = unsafe { IoApic::new(phys_to_virt(IO_APIC_BASE).as_usize() as u64) }; + IO_APIC.init_by(SpinNoIrq::new(io_apic)); +} + +#[cfg(feature = "smp")] +pub(super) fn init_secondary() { + unsafe { local_apic().enable() }; +} diff --git a/modules/axhal/src/platform/x86_pc/boot.rs b/modules/axhal/src/platform/x86_pc/boot.rs new file mode 100644 index 000000000..f21f66982 --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/boot.rs @@ -0,0 +1,52 @@ +use core::arch::global_asm; + +use x86_64::registers::control::{Cr0Flags, Cr4Flags}; +use x86_64::registers::model_specific::EferFlags; + +use axconfig::{PHYS_VIRT_OFFSET, TASK_STACK_SIZE}; + +/// Flags set in the ’flags’ member of the multiboot header. +/// +/// (bits 1, 16: memory information, address fields in header) +const MULTIBOOT_HEADER_FLAGS: usize = 0x0001_0002; + +/// The magic field should contain this. +const MULTIBOOT_HEADER_MAGIC: usize = 0x1BADB002; + +/// This should be in EAX. +pub(super) const MULTIBOOT_BOOTLOADER_MAGIC: usize = 0x2BADB002; + +const CR0: u64 = Cr0Flags::PROTECTED_MODE_ENABLE.bits() + | Cr0Flags::MONITOR_COPROCESSOR.bits() + | Cr0Flags::NUMERIC_ERROR.bits() + | Cr0Flags::WRITE_PROTECT.bits() + | Cr0Flags::PAGING.bits(); +const CR4: u64 = Cr4Flags::PHYSICAL_ADDRESS_EXTENSION.bits() + | Cr4Flags::PAGE_GLOBAL.bits() + | if cfg!(feature = "fp_simd") { + Cr4Flags::OSFXSR.bits() | Cr4Flags::OSXMMEXCPT_ENABLE.bits() + } else { + 0 + }; +const EFER: u64 = EferFlags::LONG_MODE_ENABLE.bits() | EferFlags::NO_EXECUTE_ENABLE.bits(); + +#[link_section = ".bss.stack"] +static mut BOOT_STACK: [u8; TASK_STACK_SIZE] = [0; TASK_STACK_SIZE]; + +global_asm!( + include_str!("multiboot.S"), + mb_magic = const MULTIBOOT_BOOTLOADER_MAGIC, + mb_hdr_magic = const MULTIBOOT_HEADER_MAGIC, + mb_hdr_flags = const MULTIBOOT_HEADER_FLAGS, + entry = sym super::rust_entry, + entry_secondary = sym super::rust_entry_secondary, + + offset = const PHYS_VIRT_OFFSET, + boot_stack_size = const TASK_STACK_SIZE, + boot_stack = sym BOOT_STACK, + + cr0 = const CR0, + cr4 = const CR4, + efer_msr = const x86::msr::IA32_EFER, + efer = const EFER, +); diff --git a/modules/axhal/src/platform/x86_pc/dtables.rs b/modules/axhal/src/platform/x86_pc/dtables.rs new file mode 100644 index 000000000..f40edb2e3 --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/dtables.rs @@ -0,0 +1,37 @@ +//! Description tables (per-CPU GDT, per-CPU ISS, IDT) + +use crate::arch::{GdtStruct, IdtStruct, TaskStateSegment}; +use lazy_init::LazyInit; + +static IDT: LazyInit = LazyInit::new(); + +#[percpu::def_percpu] +static TSS: LazyInit = LazyInit::new(); + +#[percpu::def_percpu] +static GDT: LazyInit = LazyInit::new(); + +fn init_percpu() { + unsafe { + IDT.load(); + let tss = TSS.current_ref_mut_raw(); + let gdt = GDT.current_ref_mut_raw(); + tss.init_by(TaskStateSegment::new()); + gdt.init_by(GdtStruct::new(tss)); + gdt.load(); + gdt.load_tss(); + } +} + +/// Initializes IDT, GDT on the primary CPU. +pub(super) fn init_primary() { + axlog::ax_println!("\nInitialize IDT & GDT..."); + IDT.init_by(IdtStruct::new()); + init_percpu(); +} + +/// Initializes IDT, GDT on secondary CPUs. +#[cfg(feature = "smp")] +pub(super) fn init_secondary() { + init_percpu(); +} diff --git a/modules/axhal/src/platform/x86_pc/mem.rs b/modules/axhal/src/platform/x86_pc/mem.rs new file mode 100644 index 000000000..625e8ff29 --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/mem.rs @@ -0,0 +1,15 @@ +// TODO: get memory regions from multiboot info. + +use crate::mem::{MemRegion, MemRegionFlags, PhysAddr}; + +/// Returns platform-specific memory regions. +pub(crate) fn platform_regions() -> impl Iterator { + core::iter::once(MemRegion { + paddr: PhysAddr::from(0x1000), + size: 0x9e000, + flags: MemRegionFlags::RESERVED | MemRegionFlags::READ | MemRegionFlags::WRITE, + name: "low memory", + }) + .chain(crate::mem::default_free_regions()) + .chain(crate::mem::default_mmio_regions()) +} diff --git a/modules/axhal/src/platform/x86_pc/misc.rs b/modules/axhal/src/platform/x86_pc/misc.rs new file mode 100644 index 000000000..f01c4f339 --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/misc.rs @@ -0,0 +1,27 @@ +use x86_64::instructions::port::PortWriteOnly; + +/// Shutdown the whole system (in QEMU), including all CPUs. +/// +/// See for more information. +pub fn terminate() -> ! { + info!("Shutting down..."); + + #[cfg(platform = "x86_64-pc-oslab")] + { + axlog::ax_println!("System will reboot, press any key to continue ..."); + while super::console::getchar().is_none() {} + axlog::ax_println!("Rebooting ..."); + unsafe { PortWriteOnly::new(0x64).write(0xfeu8) }; + } + + #[cfg(platform = "x86_64-qemu-q35")] + unsafe { + PortWriteOnly::new(0x604).write(0x2000u16) + }; + + crate::arch::halt(); + warn!("It should shutdown!"); + loop { + crate::arch::halt(); + } +} diff --git a/modules/axhal/src/platform/x86_pc/mod.rs b/modules/axhal/src/platform/x86_pc/mod.rs new file mode 100644 index 000000000..ba3ce4363 --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/mod.rs @@ -0,0 +1,68 @@ +mod apic; +mod boot; +mod dtables; +mod uart16550; + +pub mod mem; +pub mod misc; +pub mod time; + +#[cfg(feature = "smp")] +pub mod mp; + +#[cfg(feature = "irq")] +pub mod irq { + pub use super::apic::*; +} + +pub mod console { + pub use super::uart16550::*; +} + +extern "C" { + fn rust_main(cpu_id: usize, dtb: usize) -> !; + #[cfg(feature = "smp")] + fn rust_main_secondary(cpu_id: usize) -> !; +} + +fn current_cpu_id() -> usize { + match raw_cpuid::CpuId::new().get_feature_info() { + Some(finfo) => finfo.initial_local_apic_id() as usize, + None => 0, + } +} + +unsafe extern "C" fn rust_entry(magic: usize, _mbi: usize) { + // TODO: handle multiboot info + if magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { + crate::mem::clear_bss(); + crate::cpu::init_primary(current_cpu_id()); + self::uart16550::init(); + self::dtables::init_primary(); + self::time::init_early(); + rust_main(current_cpu_id(), 0); + } +} + +#[allow(unused_variables)] +unsafe extern "C" fn rust_entry_secondary(magic: usize) { + #[cfg(feature = "smp")] + if magic == self::boot::MULTIBOOT_BOOTLOADER_MAGIC { + crate::cpu::init_secondary(current_cpu_id()); + self::dtables::init_secondary(); + rust_main_secondary(current_cpu_id()); + } +} + +/// Initializes the platform devices for the primary CPU. +pub fn platform_init() { + self::apic::init_primary(); + self::time::init_primary(); +} + +/// Initializes the platform devices for secondary CPUs. +#[cfg(feature = "smp")] +pub fn platform_init_secondary() { + self::apic::init_secondary(); + self::time::init_secondary(); +} diff --git a/modules/axhal/src/platform/x86_pc/mp.rs b/modules/axhal/src/platform/x86_pc/mp.rs new file mode 100644 index 000000000..dd3cdd1f5 --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/mp.rs @@ -0,0 +1,45 @@ +use crate::mem::{phys_to_virt, PhysAddr, PAGE_SIZE_4K}; +use crate::time::{busy_wait, Duration}; + +const START_PAGE_IDX: u8 = 6; +const START_PAGE_PADDR: PhysAddr = PhysAddr::from(START_PAGE_IDX as usize * PAGE_SIZE_4K); + +core::arch::global_asm!( + include_str!("ap_start.S"), + start_page_paddr = const START_PAGE_PADDR.as_usize(), +); + +unsafe fn setup_startup_page(stack_top: PhysAddr) { + extern "C" { + fn ap_entry32(); + fn ap_start(); + fn ap_end(); + } + const U64_PER_PAGE: usize = PAGE_SIZE_4K / 8; + + let start_page_ptr = phys_to_virt(START_PAGE_PADDR).as_mut_ptr() as *mut u64; + let start_page = core::slice::from_raw_parts_mut(start_page_ptr, U64_PER_PAGE); + core::ptr::copy_nonoverlapping( + ap_start as *const u64, + start_page_ptr, + (ap_end as usize - ap_start as usize) / 8, + ); + start_page[U64_PER_PAGE - 2] = stack_top.as_usize() as u64; // stack_top + start_page[U64_PER_PAGE - 1] = ap_entry32 as usize as _; // entry +} + +/// Starts the given secondary CPU with its boot stack. +pub fn start_secondary_cpu(apic_id: usize, stack_top: PhysAddr) { + unsafe { setup_startup_page(stack_top) }; + + let apic_id = super::apic::raw_apic_id(apic_id as u8); + let lapic = super::apic::local_apic(); + + // INIT-SIPI-SIPI Sequence + // Ref: Intel SDM Vol 3C, Section 8.4.4, MP Initialization Example + unsafe { lapic.send_init_ipi(apic_id) }; + busy_wait(Duration::from_millis(10)); // 10ms + unsafe { lapic.send_sipi(START_PAGE_IDX, apic_id) }; + busy_wait(Duration::from_micros(200)); // 200us + unsafe { lapic.send_sipi(START_PAGE_IDX, apic_id) }; +} diff --git a/modules/axhal/src/platform/x86_pc/multiboot.S b/modules/axhal/src/platform/x86_pc/multiboot.S new file mode 100644 index 000000000..b18103b1a --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/multiboot.S @@ -0,0 +1,143 @@ +# Bootstrapping from 32-bit with the Multiboot specification. +# See https://www.gnu.org/software/grub/manual/multiboot/multiboot.html + +.section .text.boot +.code32 +.global _start +_start: + mov edi, eax # arg1: magic: 0x2BADB002 + mov esi, ebx # arg2: multiboot info + jmp bsp_entry32 + +.balign 4 +.type multiboot_header, STT_OBJECT +multiboot_header: + .int {mb_hdr_magic} # magic: 0x1BADB002 + .int {mb_hdr_flags} # flags + .int -({mb_hdr_magic} + {mb_hdr_flags}) # checksum + .int multiboot_header - {offset} # header_addr + .int _skernel - {offset} # load_addr + .int _edata - {offset} # load_end + .int _ebss - {offset} # bss_end_addr + .int _start - {offset} # entry_addr + +# Common code in 32-bit, prepare states to enter 64-bit. +.macro ENTRY32_COMMON + # set data segment selectors + mov ax, 0x18 + mov ss, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + # set PAE, PGE bit in CR4 + mov eax, {cr4} + mov cr4, eax + + # load the temporary page table + lea eax, [.Ltmp_pml4 - {offset}] + mov cr3, eax + + # set LME, NXE bit in IA32_EFER + mov ecx, {efer_msr} + mov edx, 0 + mov eax, {efer} + wrmsr + + # set protected mode, write protect, paging bit in CR0 + mov eax, {cr0} + mov cr0, eax +.endm + +# Common code in 64-bit +.macro ENTRY64_COMMON + # clear segment selectors + xor ax, ax + mov ss, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax +.endm + +.code32 +bsp_entry32: + lgdt [.Ltmp_gdt_desc - {offset}] # load the temporary GDT + ENTRY32_COMMON + ljmp 0x10, offset bsp_entry64 - {offset} # 0x10 is code64 segment + +.code32 +.global ap_entry32 +ap_entry32: + ENTRY32_COMMON + ljmp 0x10, offset ap_entry64 - {offset} # 0x10 is code64 segment + +.code64 +bsp_entry64: + ENTRY64_COMMON + + # set RSP to boot stack + movabs rsp, offset {boot_stack} + add rsp, {boot_stack_size} + + # call rust_entry(magic, mbi) + movabs rax, offset {entry} + call rax + jmp .Lhlt + +.code64 +ap_entry64: + ENTRY64_COMMON + + # set RSP to high address (already set in ap_start.S) + mov rax, {offset} + add rsp, rax + + # call rust_entry_secondary(magic) + mov rdi, {mb_magic} + movabs rax, offset {entry_secondary} + call rax + jmp .Lhlt + +.Lhlt: + hlt + jmp .Lhlt + +.section .rodata +.balign 8 +.Ltmp_gdt_desc: + .short .Ltmp_gdt_end - .Ltmp_gdt - 1 # limit + .long .Ltmp_gdt - {offset} # base + +.section .data +.balign 16 +.Ltmp_gdt: + .quad 0x0000000000000000 # 0x00: null + .quad 0x00cf9b000000ffff # 0x08: code segment (base=0, limit=0xfffff, type=32bit code exec/read, DPL=0, 4k) + .quad 0x00af9b000000ffff # 0x10: code segment (base=0, limit=0xfffff, type=64bit code exec/read, DPL=0, 4k) + .quad 0x00cf93000000ffff # 0x18: data segment (base=0, limit=0xfffff, type=32bit data read/write, DPL=0, 4k) +.Ltmp_gdt_end: + +.balign 4096 +.Ltmp_pml4: + # 0x0000_0000 ~ 0xffff_ffff + .quad .Ltmp_pdpt_low - {offset} + 0x3 # PRESENT | WRITABLE | paddr(tmp_pdpt) + .zero 8 * 510 + # 0xffff_ff80_0000_0000 ~ 0xffff_ff80_ffff_ffff + .quad .Ltmp_pdpt_high - {offset} + 0x3 # PRESENT | WRITABLE | paddr(tmp_pdpt) + +# FIXME: may not work on macOS using hvf as the CPU does not support 1GB page (pdpe1gb) +.Ltmp_pdpt_low: + .quad 0x0000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x0) + .quad 0x40000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000) + .quad 0x80000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x8000_0000) + .quad 0xc0000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0xc000_0000) + .zero 8 * 508 + +.Ltmp_pdpt_high: + .quad 0x0000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x0) + .quad 0x40000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x4000_0000) + .quad 0x80000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0x8000_0000) + .quad 0xc0000000 | 0x83 # PRESENT | WRITABLE | HUGE_PAGE | paddr(0xc000_0000) + .zero 8 * 508 diff --git a/modules/axhal/src/platform/x86_pc/time.rs b/modules/axhal/src/platform/x86_pc/time.rs new file mode 100644 index 000000000..596db94be --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/time.rs @@ -0,0 +1,82 @@ +use raw_cpuid::CpuId; + +#[cfg(feature = "irq")] +const LAPIC_TICKS_PER_SEC: u64 = 1_000_000_000; // TODO: need to calibrate + +#[cfg(feature = "irq")] +static mut NANOS_TO_LAPIC_TICKS_RATIO: ratio::Ratio = ratio::Ratio::zero(); + +static mut INIT_TICK: u64 = 0; +static mut CPU_FREQ_MHZ: u64 = axconfig::TIMER_FREQUENCY as u64 / 1_000_000; + +/// Returns the current clock time in hardware ticks. +pub fn current_ticks() -> u64 { + unsafe { core::arch::x86_64::_rdtsc() - INIT_TICK } +} + +/// Converts hardware ticks to nanoseconds. +pub fn ticks_to_nanos(ticks: u64) -> u64 { + ticks * 1_000 / unsafe { CPU_FREQ_MHZ } +} + +/// Converts nanoseconds to hardware ticks. +pub fn nanos_to_ticks(nanos: u64) -> u64 { + nanos * unsafe { CPU_FREQ_MHZ } / 1_000 +} + +/// Set a one-shot timer. +/// +/// A timer interrupt will be triggered at the given deadline (in nanoseconds). +#[cfg(feature = "irq")] +pub fn set_oneshot_timer(deadline_ns: u64) { + let lapic = super::apic::local_apic(); + let now_ns = crate::time::current_time_nanos(); + unsafe { + if now_ns < deadline_ns { + let apic_ticks = NANOS_TO_LAPIC_TICKS_RATIO.mul_trunc(deadline_ns - now_ns); + assert!(apic_ticks <= u32::MAX as u64); + lapic.set_timer_initial(apic_ticks.max(1) as u32); + } else { + lapic.set_timer_initial(1); + } + } +} + +pub(super) fn init_early() { + if let Some(freq) = CpuId::new() + .get_processor_frequency_info() + .map(|info| info.processor_base_frequency()) + { + if freq > 0 { + axlog::ax_println!("Got TSC frequency by CPUID: {} MHz", freq); + unsafe { CPU_FREQ_MHZ = freq as u64 } + } + } + + unsafe { INIT_TICK = core::arch::x86_64::_rdtsc() }; +} + +pub(super) fn init_primary() { + #[cfg(feature = "irq")] + unsafe { + use x2apic::lapic::{TimerDivide, TimerMode}; + let lapic = super::apic::local_apic(); + lapic.set_timer_mode(TimerMode::OneShot); + lapic.set_timer_divide(TimerDivide::Div256); // indeed it is Div1, the name is confusing. + lapic.enable_timer(); + + // TODO: calibrate with HPET + NANOS_TO_LAPIC_TICKS_RATIO = ratio::Ratio::new( + LAPIC_TICKS_PER_SEC as u32, + crate::time::NANOS_PER_SEC as u32, + ); + } +} + +#[cfg(feature = "smp")] +pub(super) fn init_secondary() { + #[cfg(feature = "irq")] + unsafe { + super::apic::local_apic().enable_timer(); + } +} diff --git a/modules/axhal/src/platform/x86_pc/uart16550.rs b/modules/axhal/src/platform/x86_pc/uart16550.rs new file mode 100644 index 000000000..7627c080b --- /dev/null +++ b/modules/axhal/src/platform/x86_pc/uart16550.rs @@ -0,0 +1,105 @@ +//! Uart 16550. + +use spinlock::SpinNoIrq; +use x86_64::instructions::port::{Port, PortReadOnly, PortWriteOnly}; + +const UART_CLOCK_FACTOR: usize = 16; +const OSC_FREQ: usize = 1_843_200; + +static COM1: SpinNoIrq = SpinNoIrq::new(Uart16550::new(0x3f8)); + +bitflags::bitflags! { + /// Line status flags + struct LineStsFlags: u8 { + const INPUT_FULL = 1; + // 1 to 4 unknown + const OUTPUT_EMPTY = 1 << 5; + // 6 and 7 unknown + } +} + +struct Uart16550 { + data: Port, + int_en: PortWriteOnly, + fifo_ctrl: PortWriteOnly, + line_ctrl: PortWriteOnly, + modem_ctrl: PortWriteOnly, + line_sts: PortReadOnly, +} + +impl Uart16550 { + const fn new(port: u16) -> Self { + Self { + data: Port::new(port), + int_en: PortWriteOnly::new(port + 1), + fifo_ctrl: PortWriteOnly::new(port + 2), + line_ctrl: PortWriteOnly::new(port + 3), + modem_ctrl: PortWriteOnly::new(port + 4), + line_sts: PortReadOnly::new(port + 5), + } + } + + fn init(&mut self, baud_rate: usize) { + unsafe { + // Disable interrupts + self.int_en.write(0x00); + + // Enable DLAB + self.line_ctrl.write(0x80); + + // Set maximum speed according the input baud rate by configuring DLL and DLM + let divisor = OSC_FREQ / (baud_rate * UART_CLOCK_FACTOR); + self.data.write((divisor & 0xff) as u8); + self.int_en.write((divisor >> 8) as u8); + + // Disable DLAB and set data word length to 8 bits + self.line_ctrl.write(0x03); + + // Enable FIFO, clear TX/RX queues and + // set interrupt watermark at 14 bytes + self.fifo_ctrl.write(0xC7); + + // Mark data terminal ready, signal request to send + // and enable auxilliary output #2 (used as interrupt line for CPU) + self.modem_ctrl.write(0x0B); + } + } + + fn line_sts(&mut self) -> LineStsFlags { + unsafe { LineStsFlags::from_bits_truncate(self.line_sts.read()) } + } + + fn putchar(&mut self, c: u8) { + while !self.line_sts().contains(LineStsFlags::OUTPUT_EMPTY) {} + unsafe { self.data.write(c) }; + } + + fn getchar(&mut self) -> Option { + if self.line_sts().contains(LineStsFlags::INPUT_FULL) { + unsafe { Some(self.data.read()) } + } else { + None + } + } +} + +/// Writes a byte to the console. +pub fn putchar(c: u8) { + let mut uart = COM1.lock(); + match c { + b'\n' => { + uart.putchar(b'\r'); + uart.putchar(b'\n'); + } + c => uart.putchar(c), + } +} + +/// Reads a byte from the console, or returns [`None`] if no input is available. +pub fn getchar() -> Option { + COM1.lock().getchar() +} + +pub(super) fn init() { + COM1.lock().init(115200); +} diff --git a/modules/axhal/src/time.rs b/modules/axhal/src/time.rs new file mode 100644 index 000000000..f5c7fa867 --- /dev/null +++ b/modules/axhal/src/time.rs @@ -0,0 +1,48 @@ +//! Time-related operations. + +pub use core::time::Duration; + +/// A measurement of the system clock. +/// +/// Currently, it reuses the [`core::time::Duration`] type. But it does not +/// represent a duration, but a clock time. +pub type TimeValue = Duration; + +#[cfg(feature = "irq")] +pub use crate::platform::irq::TIMER_IRQ_NUM; +#[cfg(feature = "irq")] +pub use crate::platform::time::set_oneshot_timer; +pub use crate::platform::time::{current_ticks, nanos_to_ticks, ticks_to_nanos}; + +/// Number of milliseconds in a second. +pub const MILLIS_PER_SEC: u64 = 1_000; +/// Number of microseconds in a second. +pub const MICROS_PER_SEC: u64 = 1_000_000; +/// Number of nanoseconds in a second. +pub const NANOS_PER_SEC: u64 = 1_000_000_000; +/// Number of nanoseconds in a millisecond. +pub const NANOS_PER_MILLIS: u64 = 1_000_000; +/// Number of nanoseconds in a microsecond. +pub const NANOS_PER_MICROS: u64 = 1_000; + +/// Returns the current clock time in nanoseconds. +pub fn current_time_nanos() -> u64 { + ticks_to_nanos(current_ticks()) +} + +/// Returns the current clock time in [`TimeValue`]. +pub fn current_time() -> TimeValue { + TimeValue::from_nanos(current_time_nanos()) +} + +/// Busy waiting for the given duration. +pub fn busy_wait(dur: Duration) { + busy_wait_until(current_time() + dur); +} + +/// Busy waiting until reaching the given deadline. +pub fn busy_wait_until(deadline: TimeValue) { + while current_time() < deadline { + core::hint::spin_loop(); + } +} diff --git a/modules/axhal/src/tls.rs b/modules/axhal/src/tls.rs new file mode 100644 index 000000000..849ef0e38 --- /dev/null +++ b/modules/axhal/src/tls.rs @@ -0,0 +1,166 @@ +//! Thread Local Storage (TLS) support. +//! +//! ## TLS layout for x86_64 +//! +//! ```text +//! aligned --> +-------------------------+- static_tls_offset +//! allocation | | \ +//! | .tdata | | +//! | address | | | +//! | grow up + - - - - - - - - - - - - + > Static TLS block +//! v | | | (length: static_tls_size) +//! | .tbss | | +//! | | | +//! +-------------------------+ | +//! | / PADDING / / / / / / / | / +//! +-------------------------+ +//! tls_ptr -+-> self pointer (void *) | \ +//! (tp_offset) | | | +//! | Custom TCB format | > Thread Control Block (TCB) +//! | (might be used | | (length: TCB_SIZE) +//! | by a libC) | | +//! | | / +//! +-------------------------+- (total length: tls_area_size) +//! ``` +//! +//! ## TLS layout for AArch64 and RISC-V +//! +//! ```text +//! +-------------------------+ +//! | | \ +//! | Custom TCB format | | +//! | (might be used | > Thread Control Block (TCB) +//! | by a libC) | | (length: TCB_SIZE) +//! | | / +//! tls_ptr -+-------------------------+ +//! (tp_offset) | GAP_ABOVE_TP | +//! +-------------------------+- static_tls_offset +//! | | \ +//! | .tdata | | +//! | | | +//! + - - - - - - - - - - - - + > Static TLS block +//! | | | (length: static_tls_size) +//! | .tbss | | +//! | | / +//! +-------------------------+- (total length: tls_area_size) +//! ``` +//! +//! Reference: +//! 1. +//! 2. + +extern crate alloc; + +use memory_addr::align_up; + +use core::alloc::Layout; +use core::ptr::NonNull; + +const TLS_ALIGN: usize = 0x10; + +cfg_if::cfg_if! { + if #[cfg(target_arch = "x86_64")] { + const TCB_SIZE: usize = 8; // to store TLS self pointer + const GAP_ABOVE_TP: usize = 0; + } else if #[cfg(target_arch = "aarch64")] { + const TCB_SIZE: usize = 0; + const GAP_ABOVE_TP: usize = 16; + } else if #[cfg(target_arch = "riscv64")] { + const TCB_SIZE: usize = 0; + const GAP_ABOVE_TP: usize = 0; + } +} + +extern "C" { + fn _stdata(); + fn _etdata(); + fn _etbss(); +} + +/// The memory region for thread-local storage. +pub struct TlsArea { + base: NonNull, + layout: Layout, +} + +impl Drop for TlsArea { + fn drop(&mut self) { + unsafe { + alloc::alloc::dealloc(self.base.as_ptr(), self.layout); + } + } +} + +impl TlsArea { + /// Returns the pointer to the TLS static area. + /// + /// One should set the hardware thread pointer register to this value. + pub fn tls_ptr(&self) -> *mut u8 { + unsafe { self.base.as_ptr().add(tp_offset()) } + } + + /// Allocates the memory region for TLS, and initializes it. + pub fn alloc() -> Self { + let layout = Layout::from_size_align(tls_area_size(), TLS_ALIGN).unwrap(); + let area_base = unsafe { alloc::alloc::alloc_zeroed(layout) }; + + let tls_load_base = _stdata as *mut u8; + let tls_load_size = _etbss as usize - _stdata as usize; + unsafe { + // copy data from .tbdata section + core::ptr::copy_nonoverlapping( + tls_load_base, + area_base.add(static_tls_offset()), + tls_load_size, + ); + // initialize TCB + init_tcb(area_base); + } + + Self { + base: NonNull::new(area_base).unwrap(), + layout, + } + } +} + +fn static_tls_size() -> usize { + align_up(_etbss as usize - _stdata as usize, TLS_ALIGN) +} + +fn static_tls_offset() -> usize { + if cfg!(target_arch = "x86_64") { + 0 + } else if cfg!(any(target_arch = "aarch64", target_arch = "riscv64")) { + TCB_SIZE + GAP_ABOVE_TP + } else { + unreachable!() + } +} + +fn tp_offset() -> usize { + if cfg!(target_arch = "x86_64") { + static_tls_size() + } else if cfg!(any(target_arch = "aarch64", target_arch = "riscv64")) { + TCB_SIZE + } else { + unreachable!() + } +} + +fn tls_area_size() -> usize { + if cfg!(target_arch = "x86_64") { + static_tls_size() + TCB_SIZE + } else if cfg!(any(target_arch = "aarch64", target_arch = "riscv64")) { + TCB_SIZE + GAP_ABOVE_TP + static_tls_size() + } else { + unreachable!() + } +} + +unsafe fn init_tcb(tls_area: *mut u8) { + if cfg!(target_arch = "x86_64") { + let tp_addr = tls_area.add(tp_offset()).cast::(); + tp_addr.write(tp_addr as usize); // write self pointer + } +} diff --git a/modules/axhal/src/trap.rs b/modules/axhal/src/trap.rs new file mode 100644 index 000000000..5fc9f76dc --- /dev/null +++ b/modules/axhal/src/trap.rs @@ -0,0 +1,23 @@ +//! Trap handling. + +use crate_interface::{call_interface, def_interface}; + +/// Trap handler interface. +/// +/// This trait is defined with the [`#[def_interface]`][1] attribute. Users +/// should implement it with [`#[impl_interface]`][2] in any other crate. +/// +/// [1]: crate_interface::def_interface +/// [2]: crate_interface::impl_interface +#[def_interface] +pub trait TrapHandler { + /// Handles interrupt requests for the given IRQ number. + fn handle_irq(irq_num: usize); + // more e.g.: handle_page_fault(); +} + +/// Call the external IRQ handler. +#[allow(dead_code)] +pub(crate) fn handle_irq_extern(irq_num: usize) { + call_interface!(TrapHandler::handle_irq, irq_num); +} diff --git a/modules/axlog/Cargo.toml b/modules/axlog/Cargo.toml new file mode 100644 index 000000000..a679060a4 --- /dev/null +++ b/modules/axlog/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "axlog" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Macros for multi-level formatted logging used by ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axlog" +documentation = "https://rcore-os.github.io/arceos/axlog/index.html" + +[features] +std = ["dep:chrono"] +log-level-off = ["log/max_level_off"] +log-level-error = ["log/max_level_error"] +log-level-warn = ["log/max_level_warn"] +log-level-info = ["log/max_level_info"] +log-level-debug = ["log/max_level_debug"] +log-level-trace = ["log/max_level_trace"] +default = [] + +[dependencies] +cfg-if = "1.0" +log = "0.4" +spinlock = { path = "../../crates/spinlock" } +crate_interface = { path = "../../crates/crate_interface" } +chrono = { version = "0.4", optional = true } + +[dev-dependencies] +axlog = { path = ".", features = ["std"] } diff --git a/modules/axlog/src/lib.rs b/modules/axlog/src/lib.rs new file mode 100644 index 000000000..d3ad70b46 --- /dev/null +++ b/modules/axlog/src/lib.rs @@ -0,0 +1,262 @@ +//! Macros for multi-level formatted logging used by +//! [ArceOS](https://github.com/rcore-os/arceos). +//! +//! The log macros, in descending order of level, are: [`error!`], [`warn!`], +//! [`info!`], [`debug!`], and [`trace!`]. +//! +//! If it is used in `no_std` environment, the users need to implement the +//! [`LogIf`] to provide external functions such as console output. +//! +//! To use in the `std` environment, please enable the `std` feature: +//! +//! ```toml +//! [dependencies] +//! axlog = { version = "0.1", features = ["std"] } +//! ``` +//! +//! # Cargo features: +//! +//! - `std`: Use in the `std` environment. If it is enabled, you can use console +//! output without implementing the [`LogIf`] trait. This is disabled by default. +//! - `log-level-off`: Disable all logging. If it is enabled, all log macros +//! (e.g. [`info!`]) will be optimized out to a no-op in compilation time. +//! - `log-level-error`: Set the maximum log level to `error`. Any macro +//! with a level lower than [`error!`] (e.g, [`warn!`], [`info!`], ...) will be +//! optimized out to a no-op. +//! - `log-level-warn`, `log-level-info`, `log-level-debug`, `log-level-trace`: +//! Similar to `log-level-error`. +//! +//! # Examples +//! +//! ``` +//! use axlog::{debug, error, info, trace, warn}; +//! +//! // Initialize the logger. +//! axlog::init(); +//! // Set the maximum log level to `info`. +//! axlog::set_max_level("info"); +//! +//! // The following logs will be printed. +//! error!("error"); +//! warn!("warn"); +//! info!("info"); +//! +//! // The following logs will not be printed. +//! debug!("debug"); +//! trace!("trace"); +//! ``` + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate log; + +use core::fmt::{self, Write}; +use core::str::FromStr; + +use log::{Level, LevelFilter, Log, Metadata, Record}; + +#[cfg(not(feature = "std"))] +use crate_interface::call_interface; + +pub use log::{debug, error, info, trace, warn}; + +/// Prints to the console. +/// +/// Equivalent to the [`ax_println!`] macro except that a newline is not printed at +/// the end of the message. +#[macro_export] +macro_rules! ax_print { + ($($arg:tt)*) => { + $crate::__print_impl(format_args!($($arg)*)); + } +} + +/// Prints to the console, with a newline. +#[macro_export] +macro_rules! ax_println { + () => { $crate::ax_print!("\n") }; + ($($arg:tt)*) => { + $crate::__print_impl(format_args!("{}\n", format_args!($($arg)*))); + } +} + +macro_rules! with_color { + ($color_code:expr, $($arg:tt)*) => {{ + format_args!("\u{1B}[{}m{}\u{1B}[m", $color_code as u8, format_args!($($arg)*)) + }}; +} + +#[repr(u8)] +#[allow(dead_code)] +enum ColorCode { + Black = 30, + Red = 31, + Green = 32, + Yellow = 33, + Blue = 34, + Magenta = 35, + Cyan = 36, + White = 37, + BrightBlack = 90, + BrightRed = 91, + BrightGreen = 92, + BrightYellow = 93, + BrightBlue = 94, + BrightMagenta = 95, + BrightCyan = 96, + BrightWhite = 97, +} + +/// Extern interfaces that must be implemented in other crates. +#[crate_interface::def_interface] +pub trait LogIf { + /// Writes a string to the console. + fn console_write_str(s: &str); + + /// Gets current clock time. + fn current_time() -> core::time::Duration; + + /// Gets current CPU ID. + /// + /// Returns [`None`] if you don't want to show the CPU ID in the log. + fn current_cpu_id() -> Option; + + /// Gets current task ID. + /// + /// Returns [`None`] if you don't want to show the task ID in the log. + fn current_task_id() -> Option; +} + +struct Logger; + +impl Write for Logger { + fn write_str(&mut self, s: &str) -> fmt::Result { + cfg_if::cfg_if! { + if #[cfg(feature = "std")] { + std::print!("{}", s); + } else { + call_interface!(LogIf::console_write_str, s); + } + } + Ok(()) + } +} + +impl Log for Logger { + #[inline] + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + if !self.enabled(record.metadata()) { + return; + } + + let level = record.level(); + let line = record.line().unwrap_or(0); + let path = record.target(); + let args_color = match level { + Level::Error => ColorCode::Red, + Level::Warn => ColorCode::Yellow, + Level::Info => ColorCode::Green, + Level::Debug => ColorCode::Cyan, + Level::Trace => ColorCode::BrightBlack, + }; + + cfg_if::cfg_if! { + if #[cfg(feature = "std")] { + __print_impl(with_color!( + ColorCode::White, + "[{time} {path}:{line}] {args}\n", + time = chrono::Local::now().format("%Y-%m-%d %H:%M:%S%.6f"), + path = path, + line = line, + args = with_color!(args_color, "{}", record.args()), + )); + } else { + let cpu_id = call_interface!(LogIf::current_cpu_id); + let tid = call_interface!(LogIf::current_task_id); + let now = call_interface!(LogIf::current_time); + if let Some(cpu_id) = cpu_id { + if let Some(tid) = tid { + // show CPU ID and task ID + __print_impl(with_color!( + ColorCode::White, + "[{:>3}.{:06} {cpu_id}:{tid} {path}:{line}] {args}\n", + now.as_secs(), + now.subsec_micros(), + cpu_id = cpu_id, + tid = tid, + path = path, + line = line, + args = with_color!(args_color, "{}", record.args()), + )); + } else { + // show CPU ID only + __print_impl(with_color!( + ColorCode::White, + "[{:>3}.{:06} {cpu_id} {path}:{line}] {args}\n", + now.as_secs(), + now.subsec_micros(), + cpu_id = cpu_id, + path = path, + line = line, + args = with_color!(args_color, "{}", record.args()), + )); + } + } else { + // neither CPU ID nor task ID is shown + __print_impl(with_color!( + ColorCode::White, + "[{:>3}.{:06} {path}:{line}] {args}\n", + now.as_secs(), + now.subsec_micros(), + path = path, + line = line, + args = with_color!(args_color, "{}", record.args()), + )); + } + } + } + } + + fn flush(&self) {} +} + +/// Prints the formatted string to the console. +pub fn print_fmt(args: fmt::Arguments) -> fmt::Result { + use spinlock::SpinNoIrq; // TODO: more efficient + static LOCK: SpinNoIrq<()> = SpinNoIrq::new(()); + + let _guard = LOCK.lock(); + Logger.write_fmt(args) +} + +#[doc(hidden)] +pub fn __print_impl(args: fmt::Arguments) { + print_fmt(args).unwrap(); +} + +/// Initializes the logger. +/// +/// This function should be called before any log macros are used, otherwise +/// nothing will be printed. +pub fn init() { + log::set_logger(&Logger).unwrap(); + log::set_max_level(LevelFilter::Warn); +} + +/// Set the maximum log level. +/// +/// Unlike the features such as `log-level-error`, setting the logging level in +/// this way incurs runtime overhead. In addition, this function is no effect +/// when those features are enabled. +/// +/// `level` should be one of `off`, `error`, `warn`, `info`, `debug`, `trace`. +pub fn set_max_level(level: &str) { + let lf = LevelFilter::from_str(level) + .ok() + .unwrap_or(LevelFilter::Off); + log::set_max_level(lf); +} diff --git a/modules/axnet/Cargo.toml b/modules/axnet/Cargo.toml new file mode 100644 index 000000000..32919787a --- /dev/null +++ b/modules/axnet/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "axnet" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia ", "ChengXiang Qi "] +description = "ArceOS network module" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axnet" +documentation = "https://rcore-os.github.io/arceos/axnet/index.html" + +[features] +smoltcp = [] +default = ["smoltcp"] + +[dependencies] +log = "0.4" +cfg-if = "1.0" +spin = "0.9" +driver_net = { path = "../../crates/driver_net" } +lazy_init = { path = "../../crates/lazy_init" } +axerrno = { path = "../../crates/axerrno" } +axhal = { path = "../axhal" } +axsync = { path = "../axsync" } +axtask = { path = "../axtask" } +axdriver = { path = "../axdriver", features = ["net"] } +axio = { path = "../../crates/axio" } + +[dependencies.smoltcp] +git = "https://github.com/rcore-os/smoltcp.git" +rev = "2ade274" +default-features = false +features = [ + "alloc", "log", # no std + "medium-ethernet", + "proto-ipv4", + "socket-raw", "socket-icmp", "socket-udp", "socket-tcp", "socket-dns", + # "fragmentation-buffer-size-65536", "proto-ipv4-fragmentation", + # "reassembly-buffer-size-65536", "reassembly-buffer-count-32", + # "assembler-max-segment-count-32", +] diff --git a/modules/axnet/src/lib.rs b/modules/axnet/src/lib.rs new file mode 100644 index 000000000..301fd6091 --- /dev/null +++ b/modules/axnet/src/lib.rs @@ -0,0 +1,49 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) network module. +//! +//! It provides unified networking primitives for TCP/UDP communication +//! using various underlying network stacks. Currently, only [smoltcp] is +//! supported. +//! +//! # Organization +//! +//! - [`TcpSocket`]: A TCP socket that provides POSIX-like APIs. +//! - [`UdpSocket`]: A UDP socket that provides POSIX-like APIs. +//! - [`dns_query`]: Function for DNS query. +//! +//! # Cargo Features +//! +//! - `smoltcp`: Use [smoltcp] as the underlying network stack. This is enabled +//! by default. +//! +//! [smoltcp]: https://github.com/smoltcp-rs/smoltcp + +#![no_std] +#![feature(ip_in_core)] +#![feature(new_uninit)] + +#[macro_use] +extern crate log; +extern crate alloc; + +cfg_if::cfg_if! { + if #[cfg(feature = "smoltcp")] { + mod smoltcp_impl; + use smoltcp_impl as net_impl; + } +} + +pub use self::net_impl::TcpSocket; +pub use self::net_impl::UdpSocket; +pub use self::net_impl::{bench_receive, bench_transmit}; +pub use self::net_impl::{dns_query, poll_interfaces}; + +use axdriver::{prelude::*, AxDeviceContainer}; + +/// Initializes the network subsystem by NIC devices. +pub fn init_network(mut net_devs: AxDeviceContainer) { + info!("Initialize network subsystem..."); + + let dev = net_devs.take_one().expect("No NIC device found!"); + info!(" use NIC 0: {:?}", dev.device_name()); + net_impl::init(dev); +} diff --git a/modules/axnet/src/smoltcp_impl/addr.rs b/modules/axnet/src/smoltcp_impl/addr.rs new file mode 100644 index 000000000..5d683fee0 --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/addr.rs @@ -0,0 +1,34 @@ +use core::net::{IpAddr, SocketAddr}; +use smoltcp::wire::{IpAddress, IpEndpoint, Ipv4Address}; + +pub const fn from_core_ipaddr(ip: IpAddr) -> IpAddress { + match ip { + IpAddr::V4(ipv4) => IpAddress::Ipv4(Ipv4Address(ipv4.octets())), + _ => panic!("IPv6 not supported"), + } +} + +pub const fn into_core_ipaddr(ip: IpAddress) -> IpAddr { + match ip { + IpAddress::Ipv4(ipv4) => IpAddr::V4(unsafe { core::mem::transmute(ipv4.0) }), + // _ => panic!("IPv6 not supported"), + } +} + +pub const fn from_core_sockaddr(addr: SocketAddr) -> IpEndpoint { + IpEndpoint { + addr: from_core_ipaddr(addr.ip()), + port: addr.port(), + } +} + +pub const fn into_core_sockaddr(addr: IpEndpoint) -> SocketAddr { + SocketAddr::new(into_core_ipaddr(addr.addr), addr.port) +} + +pub fn is_unspecified(ip: IpAddress) -> bool { + ip.as_bytes() == [0, 0, 0, 0] +} + +pub const UNSPECIFIED_IP: IpAddress = IpAddress::v4(0, 0, 0, 0); +pub const UNSPECIFIED_ENDPOINT: IpEndpoint = IpEndpoint::new(UNSPECIFIED_IP, 0); diff --git a/modules/axnet/src/smoltcp_impl/bench.rs b/modules/axnet/src/smoltcp_impl/bench.rs new file mode 100644 index 000000000..81f21a69e --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/bench.rs @@ -0,0 +1,74 @@ +use super::{AxNetRxToken, AxNetTxToken, STANDARD_MTU}; +use super::{DeviceWrapper, InterfaceWrapper}; +use smoltcp::phy::{Device, RxToken, TxToken}; + +const GB: usize = 1000 * MB; +const MB: usize = 1000 * KB; +const KB: usize = 1000; + +impl DeviceWrapper { + pub fn bench_transmit_bandwidth(&mut self) { + // 10 Gb + const MAX_SEND_BYTES: usize = 10 * GB; + let mut send_bytes: usize = 0; + let mut past_send_bytes: usize = 0; + let mut past_time = InterfaceWrapper::current_time(); + + // Send bytes + while send_bytes < MAX_SEND_BYTES { + if let Some(tx_token) = self.transmit(InterfaceWrapper::current_time()) { + AxNetTxToken::consume(tx_token, STANDARD_MTU, |tx_buf| { + tx_buf[0..12].fill(1); + // ether type: IPv4 + tx_buf[12..14].copy_from_slice(&[0x08, 0x00]); + tx_buf[14..STANDARD_MTU].fill(1); + }); + send_bytes += STANDARD_MTU; + } + + let current_time = InterfaceWrapper::current_time(); + if (current_time - past_time).secs() == 1 { + let gb = ((send_bytes - past_send_bytes) * 8) / GB; + let mb = (((send_bytes - past_send_bytes) * 8) % GB) / MB; + let gib = (send_bytes - past_send_bytes) / GB; + let mib = ((send_bytes - past_send_bytes) % GB) / MB; + info!( + "Transmit: {}.{:03}GBytes, Bandwidth: {}.{:03}Gbits/sec.", + gib, mib, gb, mb + ); + past_time = current_time; + past_send_bytes = send_bytes; + } + } + } + + pub fn bench_receive_bandwidth(&mut self) { + // 10 Gb + const MAX_RECEIVE_BYTES: usize = 10 * GB; + let mut receive_bytes: usize = 0; + let mut past_receive_bytes: usize = 0; + let mut past_time = InterfaceWrapper::current_time(); + // Receive bytes + while receive_bytes < MAX_RECEIVE_BYTES { + if let Some(rx_token) = self.receive(InterfaceWrapper::current_time()) { + AxNetRxToken::consume(rx_token.0, |rx_buf| { + receive_bytes += rx_buf.len(); + }); + } + + let current_time = InterfaceWrapper::current_time(); + if (current_time - past_time).secs() == 1 { + let gb = ((receive_bytes - past_receive_bytes) * 8) / GB; + let mb = (((receive_bytes - past_receive_bytes) * 8) % GB) / MB; + let gib = (receive_bytes - past_receive_bytes) / GB; + let mib = ((receive_bytes - past_receive_bytes) % GB) / MB; + info!( + "Receive: {}.{:03}GBytes, Bandwidth: {}.{:03}Gbits/sec.", + gib, mib, gb, mb + ); + past_time = current_time; + past_receive_bytes = receive_bytes; + } + } + } +} diff --git a/modules/axnet/src/smoltcp_impl/dns.rs b/modules/axnet/src/smoltcp_impl/dns.rs new file mode 100644 index 000000000..35fd4588f --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/dns.rs @@ -0,0 +1,90 @@ +use alloc::vec::Vec; +use axerrno::{ax_err_type, AxError, AxResult}; +use core::net::IpAddr; + +use smoltcp::iface::SocketHandle; +use smoltcp::socket::dns::{self, GetQueryResultError, StartQueryError}; +use smoltcp::wire::DnsQueryType; + +use super::addr::into_core_ipaddr; +use super::{SocketSetWrapper, ETH0, SOCKET_SET}; + +/// A DNS socket. +struct DnsSocket { + handle: Option, +} + +impl DnsSocket { + #[allow(clippy::new_without_default)] + /// Creates a new DNS socket. + pub fn new() -> Self { + let socket = SocketSetWrapper::new_dns_socket(); + let handle = Some(SOCKET_SET.add(socket)); + Self { handle } + } + + #[allow(dead_code)] + /// Update the list of DNS servers, will replace all existing servers. + pub fn update_servers(self, servers: &[smoltcp::wire::IpAddress]) { + SOCKET_SET.with_socket_mut::(self.handle.unwrap(), |socket| { + socket.update_servers(servers) + }); + } + + /// Query a address with given DNS query type. + pub fn query(&self, name: &str, query_type: DnsQueryType) -> AxResult> { + // let local_addr = self.local_addr.unwrap_or_else(f); + let handle = self.handle.ok_or_else(|| ax_err_type!(InvalidInput))?; + let iface = Ð0.iface; + let query_handle = SOCKET_SET + .with_socket_mut::(handle, |socket| { + socket.start_query(iface.lock().context(), name, query_type) + }) + .map_err(|e| match e { + StartQueryError::NoFreeSlot => { + ax_err_type!(ResourceBusy, "socket query() failed: no free slot") + } + StartQueryError::InvalidName => { + ax_err_type!(InvalidInput, "socket query() failed: invalid name") + } + StartQueryError::NameTooLong => { + ax_err_type!(InvalidInput, "socket query() failed: too long name") + } + })?; + loop { + SOCKET_SET.poll_interfaces(); + match SOCKET_SET.with_socket_mut::(handle, |socket| { + socket.get_query_result(query_handle).map_err(|e| match e { + GetQueryResultError::Pending => AxError::WouldBlock, + GetQueryResultError::Failed => { + ax_err_type!(ConnectionRefused, "socket query() failed") + } + }) + }) { + Ok(n) => { + let mut res = Vec::with_capacity(n.capacity()); + for ip in n { + res.push(into_core_ipaddr(ip)) + } + return Ok(res); + } + Err(AxError::WouldBlock) => axtask::yield_now(), + Err(e) => return Err(e), + } + } + } +} + +impl Drop for DnsSocket { + fn drop(&mut self) { + if let Some(handle) = self.handle { + SOCKET_SET.remove(handle); + } + } +} + +/// Public function for DNS query. +pub fn dns_query(name: &str) -> AxResult> { + let socket = DnsSocket::new(); + socket.query(name, DnsQueryType::A) +} diff --git a/modules/axnet/src/smoltcp_impl/listen_table.rs b/modules/axnet/src/smoltcp_impl/listen_table.rs new file mode 100644 index 000000000..7aac9ef1e --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/listen_table.rs @@ -0,0 +1,155 @@ +use alloc::{boxed::Box, collections::VecDeque}; +use core::ops::{Deref, DerefMut}; + +use axerrno::{ax_err, AxError, AxResult}; +use axsync::Mutex; +use smoltcp::iface::{SocketHandle, SocketSet}; +use smoltcp::socket::tcp::{self, State}; +use smoltcp::wire::{IpAddress, IpEndpoint, IpListenEndpoint}; + +use super::{SocketSetWrapper, LISTEN_QUEUE_SIZE, SOCKET_SET}; + +const PORT_NUM: usize = 65536; + +struct ListenTableEntry { + listen_endpoint: IpListenEndpoint, + syn_queue: VecDeque, +} + +impl ListenTableEntry { + pub fn new(listen_endpoint: IpListenEndpoint) -> Self { + Self { + listen_endpoint, + syn_queue: VecDeque::with_capacity(LISTEN_QUEUE_SIZE), + } + } + + #[inline] + fn can_accept(&self, dst: IpAddress) -> bool { + match self.listen_endpoint.addr { + Some(addr) => addr == dst, + None => true, + } + } +} + +impl Drop for ListenTableEntry { + fn drop(&mut self) { + for &handle in &self.syn_queue { + SOCKET_SET.remove(handle); + } + } +} + +pub struct ListenTable { + tcp: Box<[Mutex>>]>, +} + +impl ListenTable { + pub fn new() -> Self { + let tcp = unsafe { + let mut buf = Box::new_uninit_slice(PORT_NUM); + for i in 0..PORT_NUM { + buf[i].write(Mutex::new(None)); + } + buf.assume_init() + }; + Self { tcp } + } + + pub fn can_listen(&self, port: u16) -> bool { + self.tcp[port as usize].lock().is_none() + } + + pub fn listen(&self, listen_endpoint: IpListenEndpoint) -> AxResult { + let port = listen_endpoint.port; + assert_ne!(port, 0); + let mut entry = self.tcp[port as usize].lock(); + if entry.is_none() { + *entry = Some(Box::new(ListenTableEntry::new(listen_endpoint))); + Ok(()) + } else { + ax_err!(AddrInUse, "socket listen() failed") + } + } + + pub fn unlisten(&self, port: u16) { + debug!("TCP socket unlisten on {}", port); + *self.tcp[port as usize].lock() = None; + } + + pub fn can_accept(&self, port: u16) -> AxResult { + if let Some(entry) = self.tcp[port as usize].lock().deref() { + Ok(entry.syn_queue.iter().any(|&handle| is_connected(handle))) + } else { + ax_err!(InvalidInput, "socket accept() failed: not listen") + } + } + + pub fn accept(&self, port: u16) -> AxResult<(SocketHandle, (IpEndpoint, IpEndpoint))> { + if let Some(entry) = self.tcp[port as usize].lock().deref_mut() { + let syn_queue = &mut entry.syn_queue; + let (idx, addr_tuple) = syn_queue + .iter() + .enumerate() + .find_map(|(idx, &handle)| { + is_connected(handle).then(|| (idx, get_addr_tuple(handle))) + }) + .ok_or(AxError::WouldBlock)?; // wait for connection + if idx > 0 { + warn!( + "slow SYN queue enumeration: index = {}, len = {}!", + idx, + syn_queue.len() + ); + } + let handle = syn_queue.swap_remove_front(idx).unwrap(); + Ok((handle, addr_tuple)) + } else { + ax_err!(InvalidInput, "socket accept() failed: not listen") + } + } + + pub fn incoming_tcp_packet( + &self, + src: IpEndpoint, + dst: IpEndpoint, + sockets: &mut SocketSet<'_>, + ) { + if let Some(entry) = self.tcp[dst.port as usize].lock().deref_mut() { + if !entry.can_accept(dst.addr) { + // not listening on this address + return; + } + if entry.syn_queue.len() >= LISTEN_QUEUE_SIZE { + // SYN queue is full, drop the packet + warn!("SYN queue overflow!"); + return; + } + let mut socket = SocketSetWrapper::new_tcp_socket(); + if socket.listen(entry.listen_endpoint).is_ok() { + let handle = sockets.add(socket); + debug!( + "TCP socket {}: prepare for connection {} -> {}", + handle, src, entry.listen_endpoint + ); + entry.syn_queue.push_back(handle); + } + } + } +} + +fn is_connected(handle: SocketHandle) -> bool { + SOCKET_SET.with_socket::(handle, |socket| { + !matches!(socket.state(), State::Listen | State::SynReceived) + }) +} + +fn get_addr_tuple(handle: SocketHandle) -> (IpEndpoint, IpEndpoint) { + SOCKET_SET.with_socket::(handle, |socket| { + ( + socket.local_endpoint().unwrap(), + socket.remote_endpoint().unwrap(), + ) + }) +} diff --git a/modules/axnet/src/smoltcp_impl/mod.rs b/modules/axnet/src/smoltcp_impl/mod.rs new file mode 100644 index 000000000..5c50e0679 --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/mod.rs @@ -0,0 +1,330 @@ +mod addr; +mod bench; +mod dns; +mod listen_table; +mod tcp; +mod udp; + +use alloc::vec; +use core::cell::RefCell; +use core::ops::DerefMut; + +use axdriver::prelude::*; +use axhal::time::{current_time_nanos, NANOS_PER_MICROS}; +use axsync::Mutex; +use driver_net::{DevError, NetBufPtr}; +use lazy_init::LazyInit; +use smoltcp::iface::{Config, Interface, SocketHandle, SocketSet}; +use smoltcp::phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken}; +use smoltcp::socket::{self, AnySocket}; +use smoltcp::time::Instant; +use smoltcp::wire::{EthernetAddress, HardwareAddress, IpAddress, IpCidr}; + +use self::listen_table::ListenTable; + +pub use self::dns::dns_query; +pub use self::tcp::TcpSocket; +pub use self::udp::UdpSocket; + +macro_rules! env_or_default { + ($key:literal) => { + match option_env!($key) { + Some(val) => val, + None => "", + } + }; +} + +const IP: &str = env_or_default!("AX_IP"); +const GATEWAY: &str = env_or_default!("AX_GW"); +const DNS_SEVER: &str = "8.8.8.8"; +const IP_PREFIX: u8 = 24; + +const STANDARD_MTU: usize = 1500; + +const RANDOM_SEED: u64 = 0xA2CE_05A2_CE05_A2CE; + +const TCP_RX_BUF_LEN: usize = 64 * 1024; +const TCP_TX_BUF_LEN: usize = 64 * 1024; +const UDP_RX_BUF_LEN: usize = 64 * 1024; +const UDP_TX_BUF_LEN: usize = 64 * 1024; +const LISTEN_QUEUE_SIZE: usize = 512; + +static LISTEN_TABLE: LazyInit = LazyInit::new(); +static SOCKET_SET: LazyInit = LazyInit::new(); +static ETH0: LazyInit = LazyInit::new(); + +struct SocketSetWrapper<'a>(Mutex>); + +struct DeviceWrapper { + inner: RefCell, // use `RefCell` is enough since it's wrapped in `Mutex` in `InterfaceWrapper`. +} + +struct InterfaceWrapper { + name: &'static str, + ether_addr: EthernetAddress, + dev: Mutex, + iface: Mutex, +} + +impl<'a> SocketSetWrapper<'a> { + fn new() -> Self { + Self(Mutex::new(SocketSet::new(vec![]))) + } + + pub fn new_tcp_socket() -> socket::tcp::Socket<'a> { + let tcp_rx_buffer = socket::tcp::SocketBuffer::new(vec![0; TCP_RX_BUF_LEN]); + let tcp_tx_buffer = socket::tcp::SocketBuffer::new(vec![0; TCP_TX_BUF_LEN]); + socket::tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer) + } + + pub fn new_udp_socket() -> socket::udp::Socket<'a> { + let udp_rx_buffer = socket::udp::PacketBuffer::new( + vec![socket::udp::PacketMetadata::EMPTY; 8], + vec![0; UDP_RX_BUF_LEN], + ); + let udp_tx_buffer = socket::udp::PacketBuffer::new( + vec![socket::udp::PacketMetadata::EMPTY; 8], + vec![0; UDP_TX_BUF_LEN], + ); + socket::udp::Socket::new(udp_rx_buffer, udp_tx_buffer) + } + + pub fn new_dns_socket() -> socket::dns::Socket<'a> { + let server_addr = DNS_SEVER.parse().expect("invalid DNS server address"); + socket::dns::Socket::new(&[server_addr], vec![]) + } + + pub fn add>(&self, socket: T) -> SocketHandle { + let handle = self.0.lock().add(socket); + debug!("socket {}: created", handle); + handle + } + + pub fn with_socket, R, F>(&self, handle: SocketHandle, f: F) -> R + where + F: FnOnce(&T) -> R, + { + let set = self.0.lock(); + let socket = set.get(handle); + f(socket) + } + + pub fn with_socket_mut, R, F>(&self, handle: SocketHandle, f: F) -> R + where + F: FnOnce(&mut T) -> R, + { + let mut set = self.0.lock(); + let socket = set.get_mut(handle); + f(socket) + } + + pub fn poll_interfaces(&self) { + ETH0.poll(&self.0); + } + + pub fn remove(&self, handle: SocketHandle) { + self.0.lock().remove(handle); + debug!("socket {}: destroyed", handle); + } +} + +impl InterfaceWrapper { + fn new(name: &'static str, dev: AxNetDevice, ether_addr: EthernetAddress) -> Self { + let mut config = Config::new(HardwareAddress::Ethernet(ether_addr)); + config.random_seed = RANDOM_SEED; + + let mut dev = DeviceWrapper::new(dev); + let iface = Mutex::new(Interface::new(config, &mut dev, Self::current_time())); + Self { + name, + ether_addr, + dev: Mutex::new(dev), + iface, + } + } + + fn current_time() -> Instant { + Instant::from_micros_const((current_time_nanos() / NANOS_PER_MICROS) as i64) + } + + pub fn name(&self) -> &str { + self.name + } + + pub fn ethernet_address(&self) -> EthernetAddress { + self.ether_addr + } + + pub fn setup_ip_addr(&self, ip: IpAddress, prefix_len: u8) { + let mut iface = self.iface.lock(); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs.push(IpCidr::new(ip, prefix_len)).unwrap(); + }); + } + + pub fn setup_gateway(&self, gateway: IpAddress) { + let mut iface = self.iface.lock(); + match gateway { + IpAddress::Ipv4(v4) => iface.routes_mut().add_default_ipv4_route(v4).unwrap(), + }; + } + + pub fn poll(&self, sockets: &Mutex) { + let mut dev = self.dev.lock(); + let mut iface = self.iface.lock(); + let mut sockets = sockets.lock(); + let timestamp = Self::current_time(); + iface.poll(timestamp, dev.deref_mut(), &mut sockets); + } +} + +impl DeviceWrapper { + fn new(inner: AxNetDevice) -> Self { + Self { + inner: RefCell::new(inner), + } + } +} + +impl Device for DeviceWrapper { + type RxToken<'a> = AxNetRxToken<'a> where Self: 'a; + type TxToken<'a> = AxNetTxToken<'a> where Self: 'a; + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let mut dev = self.inner.borrow_mut(); + if let Err(e) = dev.recycle_tx_buffers() { + warn!("recycle_tx_buffers failed: {:?}", e); + return None; + } + + if !dev.can_transmit() { + return None; + } + let rx_buf = match dev.receive() { + Ok(buf) => buf, + Err(err) => { + if !matches!(err, DevError::Again) { + warn!("receive failed: {:?}", err); + } + return None; + } + }; + Some((AxNetRxToken(&self.inner, rx_buf), AxNetTxToken(&self.inner))) + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + let mut dev = self.inner.borrow_mut(); + if let Err(e) = dev.recycle_tx_buffers() { + warn!("recycle_tx_buffers failed: {:?}", e); + return None; + } + if dev.can_transmit() { + Some(AxNetTxToken(&self.inner)) + } else { + None + } + } + + fn capabilities(&self) -> DeviceCapabilities { + let mut caps = DeviceCapabilities::default(); + caps.max_transmission_unit = 1514; + caps.max_burst_size = None; + caps.medium = Medium::Ethernet; + caps + } +} + +struct AxNetRxToken<'a>(&'a RefCell, NetBufPtr); +struct AxNetTxToken<'a>(&'a RefCell); + +impl<'a> RxToken for AxNetRxToken<'a> { + fn preprocess(&self, sockets: &mut SocketSet<'_>) { + snoop_tcp_packet(self.1.packet(), sockets).ok(); + } + + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut rx_buf = self.1; + trace!( + "RECV {} bytes: {:02X?}", + rx_buf.packet_len(), + rx_buf.packet() + ); + let result = f(rx_buf.packet_mut()); + self.0.borrow_mut().recycle_rx_buffer(rx_buf).unwrap(); + result + } +} + +impl<'a> TxToken for AxNetTxToken<'a> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut dev = self.0.borrow_mut(); + let mut tx_buf = dev.alloc_tx_buffer(len).unwrap(); + let ret = f(tx_buf.packet_mut()); + trace!("SEND {} bytes: {:02X?}", len, tx_buf.packet()); + dev.transmit(tx_buf).unwrap(); + ret + } +} + +fn snoop_tcp_packet(buf: &[u8], sockets: &mut SocketSet<'_>) -> Result<(), smoltcp::wire::Error> { + use smoltcp::wire::{EthernetFrame, IpProtocol, Ipv4Packet, TcpPacket}; + + let ether_frame = EthernetFrame::new_checked(buf)?; + let ipv4_packet = Ipv4Packet::new_checked(ether_frame.payload())?; + + if ipv4_packet.next_header() == IpProtocol::Tcp { + let tcp_packet = TcpPacket::new_checked(ipv4_packet.payload())?; + let src_addr = (ipv4_packet.src_addr(), tcp_packet.src_port()).into(); + let dst_addr = (ipv4_packet.dst_addr(), tcp_packet.dst_port()).into(); + let is_first = tcp_packet.syn() && !tcp_packet.ack(); + if is_first { + // create a socket for the first incoming TCP packet, as the later accept() returns. + LISTEN_TABLE.incoming_tcp_packet(src_addr, dst_addr, sockets); + } + } + Ok(()) +} + +/// Poll the network stack. +/// +/// It may receive packets from the NIC and process them, and transmit queued +/// packets to the NIC. +pub fn poll_interfaces() { + SOCKET_SET.poll_interfaces(); +} + +/// Benchmark raw socket transmit bandwidth. +pub fn bench_transmit() { + ETH0.dev.lock().bench_transmit_bandwidth(); +} + +/// Benchmark raw socket receive bandwidth. +pub fn bench_receive() { + ETH0.dev.lock().bench_receive_bandwidth(); +} + +pub(crate) fn init(net_dev: AxNetDevice) { + let ether_addr = EthernetAddress(net_dev.mac_address().0); + let eth0 = InterfaceWrapper::new("eth0", net_dev, ether_addr); + + let ip = IP.parse().expect("invalid IP address"); + let gateway = GATEWAY.parse().expect("invalid gateway IP address"); + eth0.setup_ip_addr(ip, IP_PREFIX); + eth0.setup_gateway(gateway); + + ETH0.init_by(eth0); + SOCKET_SET.init_by(SocketSetWrapper::new()); + LISTEN_TABLE.init_by(ListenTable::new()); + + info!("created net interface {:?}:", ETH0.name()); + info!(" ether: {}", ETH0.ethernet_address()); + info!(" ip: {}/{}", ip, IP_PREFIX); + info!(" gateway: {}", gateway); +} diff --git a/modules/axnet/src/smoltcp_impl/tcp.rs b/modules/axnet/src/smoltcp_impl/tcp.rs new file mode 100644 index 000000000..5c9d5dda1 --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/tcp.rs @@ -0,0 +1,527 @@ +use core::cell::UnsafeCell; +use core::net::SocketAddr; +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; + +use axerrno::{ax_err, ax_err_type, AxError, AxResult}; +use axio::PollState; +use axsync::Mutex; + +use smoltcp::iface::SocketHandle; +use smoltcp::socket::tcp::{self, ConnectError, State}; +use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; + +use super::addr::{from_core_sockaddr, into_core_sockaddr, is_unspecified, UNSPECIFIED_ENDPOINT}; +use super::{SocketSetWrapper, ETH0, LISTEN_TABLE, SOCKET_SET}; + +// State transitions: +// CLOSED -(connect)-> BUSY -> CONNECTING -> CONNECTED -(shutdown)-> BUSY -> CLOSED +// | +// |-(listen)-> BUSY -> LISTENING -(shutdown)-> BUSY -> CLOSED +// | +// -(bind)-> BUSY -> CLOSED +const STATE_CLOSED: u8 = 0; +const STATE_BUSY: u8 = 1; +const STATE_CONNECTING: u8 = 2; +const STATE_CONNECTED: u8 = 3; +const STATE_LISTENING: u8 = 4; + +/// A TCP socket that provides POSIX-like APIs. +/// +/// - [`connect`] is for TCP clients. +/// - [`bind`], [`listen`], and [`accept`] are for TCP servers. +/// - Other methods are for both TCP clients and servers. +/// +/// [`connect`]: TcpSocket::connect +/// [`bind`]: TcpSocket::bind +/// [`listen`]: TcpSocket::listen +/// [`accept`]: TcpSocket::accept +pub struct TcpSocket { + state: AtomicU8, + handle: UnsafeCell>, + local_addr: UnsafeCell, + peer_addr: UnsafeCell, + nonblock: AtomicBool, +} + +unsafe impl Sync for TcpSocket {} + +impl TcpSocket { + /// Creates a new TCP socket. + pub const fn new() -> Self { + Self { + state: AtomicU8::new(STATE_CLOSED), + handle: UnsafeCell::new(None), + local_addr: UnsafeCell::new(UNSPECIFIED_ENDPOINT), + peer_addr: UnsafeCell::new(UNSPECIFIED_ENDPOINT), + nonblock: AtomicBool::new(false), + } + } + + /// Creates a new TCP socket that is already connected. + const fn new_connected( + handle: SocketHandle, + local_addr: IpEndpoint, + peer_addr: IpEndpoint, + ) -> Self { + Self { + state: AtomicU8::new(STATE_CONNECTED), + handle: UnsafeCell::new(Some(handle)), + local_addr: UnsafeCell::new(local_addr), + peer_addr: UnsafeCell::new(peer_addr), + nonblock: AtomicBool::new(false), + } + } + + /// Returns the local address and port, or + /// [`Err(NotConnected)`](AxError::NotConnected) if not connected. + #[inline] + pub fn local_addr(&self) -> AxResult { + match self.get_state() { + STATE_CONNECTED | STATE_LISTENING => { + Ok(into_core_sockaddr(unsafe { self.local_addr.get().read() })) + } + _ => Err(AxError::NotConnected), + } + } + + /// Returns the remote address and port, or + /// [`Err(NotConnected)`](AxError::NotConnected) if not connected. + #[inline] + pub fn peer_addr(&self) -> AxResult { + match self.get_state() { + STATE_CONNECTED | STATE_LISTENING => { + Ok(into_core_sockaddr(unsafe { self.peer_addr.get().read() })) + } + _ => Err(AxError::NotConnected), + } + } + + /// Returns whether this socket is in nonblocking mode. + #[inline] + pub fn is_nonblocking(&self) -> bool { + self.nonblock.load(Ordering::Acquire) + } + + /// Moves this TCP stream into or out of nonblocking mode. + /// + /// This will result in `read`, `write`, `recv` and `send` operations + /// becoming nonblocking, i.e., immediately returning from their calls. + /// If the IO operation is successful, `Ok` is returned and no further + /// action is required. If the IO operation could not be completed and needs + /// to be retried, an error with kind [`Err(WouldBlock)`](AxError::WouldBlock) is + /// returned. + #[inline] + pub fn set_nonblocking(&self, nonblocking: bool) { + self.nonblock.store(nonblocking, Ordering::Release); + } + + /// Connects to the given address and port. + /// + /// The local port is generated automatically. + pub fn connect(&self, remote_addr: SocketAddr) -> AxResult { + self.update_state(STATE_CLOSED, STATE_CONNECTING, || { + // SAFETY: no other threads can read or write these fields. + let handle = unsafe { self.handle.get().read() } + .unwrap_or_else(|| SOCKET_SET.add(SocketSetWrapper::new_tcp_socket())); + + // TODO: check remote addr unreachable + let remote_endpoint = from_core_sockaddr(remote_addr); + let bound_endpoint = self.bound_endpoint()?; + let iface = Ð0.iface; + let (local_endpoint, remote_endpoint) = SOCKET_SET + .with_socket_mut::(handle, |socket| { + socket + .connect(iface.lock().context(), remote_endpoint, bound_endpoint) + .or_else(|e| match e { + ConnectError::InvalidState => { + ax_err!(BadState, "socket connect() failed") + } + ConnectError::Unaddressable => { + ax_err!(ConnectionRefused, "socket connect() failed") + } + })?; + Ok(( + socket.local_endpoint().unwrap(), + socket.remote_endpoint().unwrap(), + )) + })?; + unsafe { + // SAFETY: no other threads can read or write these fields as we + // have changed the state to `BUSY`. + self.local_addr.get().write(local_endpoint); + self.peer_addr.get().write(remote_endpoint); + self.handle.get().write(Some(handle)); + } + Ok(()) + }) + .unwrap_or_else(|_| ax_err!(AlreadyExists, "socket connect() failed: already connected"))?; // EISCONN + + // Here our state must be `CONNECTING`, and only one thread can run here. + if self.is_nonblocking() { + Err(AxError::WouldBlock) + } else { + self.block_on(|| { + let PollState { writable, .. } = self.poll_connect()?; + if !writable { + Err(AxError::WouldBlock) + } else if self.get_state() == STATE_CONNECTED { + Ok(()) + } else { + ax_err!(ConnectionRefused, "socket connect() failed") + } + }) + } + } + + /// Binds an unbound socket to the given address and port. + /// + /// If the given port is 0, it generates one automatically. + /// + /// It's must be called before [`listen`](Self::listen) and + /// [`accept`](Self::accept). + pub fn bind(&self, mut local_addr: SocketAddr) -> AxResult { + self.update_state(STATE_CLOSED, STATE_CLOSED, || { + // TODO: check addr is available + if local_addr.port() == 0 { + local_addr.set_port(get_ephemeral_port()?); + } + // SAFETY: no other threads can read or write `self.local_addr` as we + // have changed the state to `BUSY`. + unsafe { + let old = self.local_addr.get().read(); + if old != UNSPECIFIED_ENDPOINT { + return ax_err!(InvalidInput, "socket bind() failed: already bound"); + } + self.local_addr.get().write(from_core_sockaddr(local_addr)); + } + Ok(()) + }) + .unwrap_or_else(|_| ax_err!(InvalidInput, "socket bind() failed: already bound")) + } + + /// Starts listening on the bound address and port. + /// + /// It's must be called after [`bind`](Self::bind) and before + /// [`accept`](Self::accept). + pub fn listen(&self) -> AxResult { + self.update_state(STATE_CLOSED, STATE_LISTENING, || { + let bound_endpoint = self.bound_endpoint()?; + unsafe { + (*self.local_addr.get()).port = bound_endpoint.port; + } + LISTEN_TABLE.listen(bound_endpoint)?; + debug!("TCP socket listening on {}", bound_endpoint); + Ok(()) + }) + .unwrap_or(Ok(())) // ignore simultaneous `listen`s. + } + + /// Accepts a new connection. + /// + /// This function will block the calling thread until a new TCP connection + /// is established. When established, a new [`TcpSocket`] is returned. + /// + /// It's must be called after [`bind`](Self::bind) and [`listen`](Self::listen). + pub fn accept(&self) -> AxResult { + if !self.is_listening() { + return ax_err!(InvalidInput, "socket accept() failed: not listen"); + } + + // SAFETY: `self.local_addr` should be initialized after `bind()`. + let local_port = unsafe { self.local_addr.get().read().port }; + self.block_on(|| { + let (handle, (local_addr, peer_addr)) = LISTEN_TABLE.accept(local_port)?; + debug!("TCP socket accepted a new connection {}", peer_addr); + Ok(TcpSocket::new_connected(handle, local_addr, peer_addr)) + }) + } + + /// Close the connection. + pub fn shutdown(&self) -> AxResult { + // stream + self.update_state(STATE_CONNECTED, STATE_CLOSED, || { + // SAFETY: `self.handle` should be initialized in a connected socket, and + // no other threads can read or write it. + let handle = unsafe { self.handle.get().read().unwrap() }; + SOCKET_SET.with_socket_mut::(handle, |socket| { + debug!("TCP socket {}: shutting down", handle); + socket.close(); + }); + unsafe { self.local_addr.get().write(UNSPECIFIED_ENDPOINT) }; // clear bound address + SOCKET_SET.poll_interfaces(); + Ok(()) + }) + .unwrap_or(Ok(()))?; + + // listener + self.update_state(STATE_LISTENING, STATE_CLOSED, || { + // SAFETY: `self.local_addr` should be initialized in a listening socket, + // and no other threads can read or write it. + let local_port = unsafe { self.local_addr.get().read().port }; + unsafe { self.local_addr.get().write(UNSPECIFIED_ENDPOINT) }; // clear bound address + LISTEN_TABLE.unlisten(local_port); + SOCKET_SET.poll_interfaces(); + Ok(()) + }) + .unwrap_or(Ok(()))?; + + // ignore for other states + Ok(()) + } + + /// Receives data from the socket, stores it in the given buffer. + pub fn recv(&self, buf: &mut [u8]) -> AxResult { + if self.is_connecting() { + return Err(AxError::WouldBlock); + } else if !self.is_connected() { + return ax_err!(NotConnected, "socket recv() failed"); + } + + // SAFETY: `self.handle` should be initialized in a connected socket. + let handle = unsafe { self.handle.get().read().unwrap() }; + self.block_on(|| { + SOCKET_SET.with_socket_mut::(handle, |socket| { + if !socket.is_active() { + // not open + ax_err!(ConnectionRefused, "socket recv() failed") + } else if !socket.may_recv() { + // connection closed + Ok(0) + } else if socket.recv_queue() > 0 { + // data available + // TODO: use socket.recv(|buf| {...}) + let len = socket + .recv_slice(buf) + .map_err(|_| ax_err_type!(BadState, "socket recv() failed"))?; + Ok(len) + } else { + // no more data + Err(AxError::WouldBlock) + } + }) + }) + } + + /// Transmits data in the given buffer. + pub fn send(&self, buf: &[u8]) -> AxResult { + if self.is_connecting() { + return Err(AxError::WouldBlock); + } else if !self.is_connected() { + return ax_err!(NotConnected, "socket send() failed"); + } + + // SAFETY: `self.handle` should be initialized in a connected socket. + let handle = unsafe { self.handle.get().read().unwrap() }; + self.block_on(|| { + SOCKET_SET.with_socket_mut::(handle, |socket| { + if !socket.is_active() || !socket.may_send() { + // closed by remote + ax_err!(ConnectionReset, "socket send() failed") + } else if socket.can_send() { + // connected, and the tx buffer is not full + // TODO: use socket.send(|buf| {...}) + let len = socket + .send_slice(buf) + .map_err(|_| ax_err_type!(BadState, "socket send() failed"))?; + Ok(len) + } else { + // tx buffer is full + Err(AxError::WouldBlock) + } + }) + }) + } + + /// Whether the socket is readable or writable. + pub fn poll(&self) -> AxResult { + match self.get_state() { + STATE_CONNECTING => self.poll_connect(), + STATE_CONNECTED => self.poll_stream(), + STATE_LISTENING => self.poll_listener(), + _ => Ok(PollState { + readable: false, + writable: false, + }), + } + } +} + +/// Private methods +impl TcpSocket { + #[inline] + fn get_state(&self) -> u8 { + self.state.load(Ordering::Acquire) + } + + #[inline] + fn set_state(&self, state: u8) { + self.state.store(state, Ordering::Release); + } + + /// Update the state of the socket atomically. + /// + /// If the current state is `expect`, it first changes the state to `STATE_BUSY`, + /// then calls the given function. If the function returns `Ok`, it changes the + /// state to `new`, otherwise it changes the state back to `expect`. + /// + /// It returns `Ok` if the current state is `expect`, otherwise it returns + /// the current state in `Err`. + fn update_state(&self, expect: u8, new: u8, f: F) -> Result, u8> + where + F: FnOnce() -> AxResult, + { + match self + .state + .compare_exchange(expect, STATE_BUSY, Ordering::Acquire, Ordering::Acquire) + { + Ok(_) => { + let res = f(); + if res.is_ok() { + self.set_state(new); + } else { + self.set_state(expect); + } + Ok(res) + } + Err(old) => Err(old), + } + } + + #[inline] + fn is_connecting(&self) -> bool { + self.get_state() == STATE_CONNECTING + } + + #[inline] + fn is_connected(&self) -> bool { + self.get_state() == STATE_CONNECTED + } + + #[inline] + fn is_listening(&self) -> bool { + self.get_state() == STATE_LISTENING + } + + fn bound_endpoint(&self) -> AxResult { + // SAFETY: no other threads can read or write `self.local_addr`. + let local_addr = unsafe { self.local_addr.get().read() }; + let port = if local_addr.port != 0 { + local_addr.port + } else { + get_ephemeral_port()? + }; + assert_ne!(port, 0); + let addr = if !is_unspecified(local_addr.addr) { + Some(local_addr.addr) + } else { + None + }; + Ok(IpListenEndpoint { addr, port }) + } + + fn poll_connect(&self) -> AxResult { + // SAFETY: `self.handle` should be initialized above. + let handle = unsafe { self.handle.get().read().unwrap() }; + let writable = + SOCKET_SET.with_socket::(handle, |socket| match socket.state() { + State::SynSent => false, // wait for connection + State::Established => { + self.set_state(STATE_CONNECTED); // connected + debug!( + "TCP socket {}: connected to {}", + handle, + socket.remote_endpoint().unwrap(), + ); + true + } + _ => { + unsafe { + self.local_addr.get().write(UNSPECIFIED_ENDPOINT); + self.peer_addr.get().write(UNSPECIFIED_ENDPOINT); + } + self.set_state(STATE_CLOSED); // connection failed + true + } + }); + Ok(PollState { + readable: false, + writable, + }) + } + + fn poll_stream(&self) -> AxResult { + // SAFETY: `self.handle` should be initialized in a connected socket. + let handle = unsafe { self.handle.get().read().unwrap() }; + SOCKET_SET.with_socket::(handle, |socket| { + Ok(PollState { + readable: !socket.may_recv() || socket.can_recv(), + writable: !socket.may_send() || socket.can_send(), + }) + }) + } + + fn poll_listener(&self) -> AxResult { + // SAFETY: `self.local_addr` should be initialized in a listening socket. + let local_addr = unsafe { self.local_addr.get().read() }; + Ok(PollState { + readable: LISTEN_TABLE.can_accept(local_addr.port)?, + writable: false, + }) + } + + /// Block the current thread until the given function completes or fails. + /// + /// If the socket is non-blocking, it calls the function once and returns + /// immediately. Otherwise, it may call the function multiple times if it + /// returns [`Err(WouldBlock)`](AxError::WouldBlock). + fn block_on(&self, mut f: F) -> AxResult + where + F: FnMut() -> AxResult, + { + if self.is_nonblocking() { + f() + } else { + loop { + SOCKET_SET.poll_interfaces(); + match f() { + Ok(t) => return Ok(t), + Err(AxError::WouldBlock) => axtask::yield_now(), + Err(e) => return Err(e), + } + } + } + } +} + +impl Drop for TcpSocket { + fn drop(&mut self) { + self.shutdown().ok(); + // Safe because we have mut reference to `self`. + if let Some(handle) = unsafe { self.handle.get().read() } { + SOCKET_SET.remove(handle); + } + } +} + +fn get_ephemeral_port() -> AxResult { + const PORT_START: u16 = 0xc000; + const PORT_END: u16 = 0xffff; + static CURR: Mutex = Mutex::new(PORT_START); + + let mut curr = CURR.lock(); + let mut tries = 0; + // TODO: more robust + while tries <= PORT_END - PORT_START { + let port = *curr; + if *curr == PORT_END { + *curr = PORT_START; + } else { + *curr += 1; + } + if LISTEN_TABLE.can_listen(port) { + return Ok(port); + } + tries += 1; + } + ax_err!(AddrInUse, "no avaliable ports!") +} diff --git a/modules/axnet/src/smoltcp_impl/udp.rs b/modules/axnet/src/smoltcp_impl/udp.rs new file mode 100644 index 000000000..371640470 --- /dev/null +++ b/modules/axnet/src/smoltcp_impl/udp.rs @@ -0,0 +1,294 @@ +use core::net::SocketAddr; +use core::sync::atomic::{AtomicBool, Ordering}; + +use axerrno::{ax_err, ax_err_type, AxError, AxResult}; +use axio::PollState; +use axsync::Mutex; +use spin::RwLock; + +use smoltcp::iface::SocketHandle; +use smoltcp::socket::udp::{self, BindError, SendError}; +use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; + +use super::addr::{from_core_sockaddr, into_core_sockaddr, is_unspecified, UNSPECIFIED_ENDPOINT}; +use super::{SocketSetWrapper, SOCKET_SET}; + +/// A UDP socket that provides POSIX-like APIs. +pub struct UdpSocket { + handle: SocketHandle, + local_addr: RwLock>, + peer_addr: RwLock>, + nonblock: AtomicBool, +} + +impl UdpSocket { + /// Creates a new UDP socket. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + let socket = SocketSetWrapper::new_udp_socket(); + let handle = SOCKET_SET.add(socket); + Self { + handle, + local_addr: RwLock::new(None), + peer_addr: RwLock::new(None), + nonblock: AtomicBool::new(false), + } + } + + /// Returns the local address and port, or + /// [`Err(NotConnected)`](AxError::NotConnected) if not connected. + pub fn local_addr(&self) -> AxResult { + match self.local_addr.try_read() { + Some(addr) => addr.map(into_core_sockaddr).ok_or(AxError::NotConnected), + None => Err(AxError::NotConnected), + } + } + + /// Returns the remote address and port, or + /// [`Err(NotConnected)`](AxError::NotConnected) if not connected. + pub fn peer_addr(&self) -> AxResult { + self.remote_endpoint().map(into_core_sockaddr) + } + + /// Returns whether this socket is in nonblocking mode. + #[inline] + pub fn is_nonblocking(&self) -> bool { + self.nonblock.load(Ordering::Acquire) + } + + /// Moves this UDP socket into or out of nonblocking mode. + /// + /// This will result in `recv`, `recv_from`, `send`, and `send_to` + /// operations becoming nonblocking, i.e., immediately returning from their + /// calls. If the IO operation is successful, `Ok` is returned and no + /// further action is required. If the IO operation could not be completed + /// and needs to be retried, an error with kind + /// [`Err(WouldBlock)`](AxError::WouldBlock) is returned. + #[inline] + pub fn set_nonblocking(&self, nonblocking: bool) { + self.nonblock.store(nonblocking, Ordering::Release); + } + + /// Binds an unbound socket to the given address and port. + /// + /// It's must be called before [`send_to`](Self::send_to) and + /// [`recv_from`](Self::recv_from). + pub fn bind(&self, mut local_addr: SocketAddr) -> AxResult { + let mut self_local_addr = self.local_addr.write(); + + if local_addr.port() == 0 { + local_addr.set_port(get_ephemeral_port()?); + } + if self_local_addr.is_some() { + return ax_err!(InvalidInput, "socket bind() failed: already bound"); + } + + let local_endpoint = from_core_sockaddr(local_addr); + let endpoint = IpListenEndpoint { + addr: (!is_unspecified(local_endpoint.addr)).then_some(local_endpoint.addr), + port: local_endpoint.port, + }; + SOCKET_SET.with_socket_mut::(self.handle, |socket| { + socket.bind(endpoint).or_else(|e| match e { + BindError::InvalidState => ax_err!(AlreadyExists, "socket bind() failed"), + BindError::Unaddressable => ax_err!(InvalidInput, "socket bind() failed"), + }) + })?; + + *self_local_addr = Some(local_endpoint); + debug!("UDP socket {}: bound on {}", self.handle, endpoint); + Ok(()) + } + + /// Sends data on the socket to the given address. On success, returns the + /// number of bytes written. + pub fn send_to(&self, buf: &[u8], remote_addr: SocketAddr) -> AxResult { + if remote_addr.port() == 0 || remote_addr.ip().is_unspecified() { + return ax_err!(InvalidInput, "socket send_to() failed: invalid address"); + } + self.send_impl(buf, from_core_sockaddr(remote_addr)) + } + + /// Receives a single datagram message on the socket. On success, returns + /// the number of bytes read and the origin. + pub fn recv_from(&self, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)> { + self.recv_impl(|socket| match socket.recv_slice(buf) { + Ok((len, meta)) => Ok((len, into_core_sockaddr(meta.endpoint))), + Err(_) => ax_err!(BadState, "socket recv_from() failed"), + }) + } + + /// Receives a single datagram message on the socket, without removing it from + /// the queue. On success, returns the number of bytes read and the origin. + pub fn peek_from(&self, buf: &mut [u8]) -> AxResult<(usize, SocketAddr)> { + self.recv_impl(|socket| match socket.peek_slice(buf) { + Ok((len, meta)) => Ok((len, into_core_sockaddr(meta.endpoint))), + Err(_) => ax_err!(BadState, "socket recv_from() failed"), + }) + } + + /// Connects this UDP socket to a remote address, allowing the `send` and + /// `recv` to be used to send data and also applies filters to only receive + /// data from the specified address. + /// + /// The local port will be generated automatically if the socket is not bound. + /// It's must be called before [`send`](Self::send) and + /// [`recv`](Self::recv). + pub fn connect(&self, addr: SocketAddr) -> AxResult { + let mut self_peer_addr = self.peer_addr.write(); + + if self.local_addr.read().is_none() { + self.bind(into_core_sockaddr(UNSPECIFIED_ENDPOINT))?; + } + + *self_peer_addr = Some(from_core_sockaddr(addr)); + debug!("UDP socket {}: connected to {}", self.handle, addr); + Ok(()) + } + + /// Sends data on the socket to the remote address to which it is connected. + pub fn send(&self, buf: &[u8]) -> AxResult { + let remote_endpoint = self.remote_endpoint()?; + self.send_impl(buf, remote_endpoint) + } + + /// Receives a single datagram message on the socket from the remote address + /// to which it is connected. On success, returns the number of bytes read. + pub fn recv(&self, buf: &mut [u8]) -> AxResult { + let remote_endpoint = self.remote_endpoint()?; + self.recv_impl(|socket| { + let (len, meta) = socket + .recv_slice(buf) + .map_err(|_| ax_err_type!(BadState, "socket recv() failed"))?; + if !is_unspecified(remote_endpoint.addr) && remote_endpoint.addr != meta.endpoint.addr { + return Err(AxError::WouldBlock); + } + if remote_endpoint.port != 0 && remote_endpoint.port != meta.endpoint.port { + return Err(AxError::WouldBlock); + } + Ok(len) + }) + } + + /// Close the socket. + pub fn shutdown(&self) -> AxResult { + SOCKET_SET.with_socket_mut::(self.handle, |socket| { + debug!("UDP socket {}: shutting down", self.handle); + socket.close(); + }); + SOCKET_SET.poll_interfaces(); + Ok(()) + } + + /// Whether the socket is readable or writable. + pub fn poll(&self) -> AxResult { + if self.local_addr.read().is_none() { + return Ok(PollState { + readable: false, + writable: false, + }); + } + SOCKET_SET.with_socket_mut::(self.handle, |socket| { + Ok(PollState { + readable: socket.can_recv(), + writable: socket.can_send(), + }) + }) + } +} + +/// Private methods +impl UdpSocket { + fn remote_endpoint(&self) -> AxResult { + match self.peer_addr.try_read() { + Some(addr) => addr.ok_or(AxError::NotConnected), + None => Err(AxError::NotConnected), + } + } + + fn send_impl(&self, buf: &[u8], remote_endpoint: IpEndpoint) -> AxResult { + if self.local_addr.read().is_none() { + return ax_err!(NotConnected, "socket send() failed"); + } + + self.block_on(|| { + SOCKET_SET.with_socket_mut::(self.handle, |socket| { + if socket.can_send() { + socket + .send_slice(buf, remote_endpoint) + .map_err(|e| match e { + SendError::BufferFull => AxError::WouldBlock, + SendError::Unaddressable => { + ax_err_type!(ConnectionRefused, "socket send() failed") + } + })?; + Ok(buf.len()) + } else { + // tx buffer is full + Err(AxError::WouldBlock) + } + }) + }) + } + + fn recv_impl(&self, mut op: F) -> AxResult + where + F: FnMut(&mut udp::Socket) -> AxResult, + { + if self.local_addr.read().is_none() { + return ax_err!(NotConnected, "socket send() failed"); + } + + self.block_on(|| { + SOCKET_SET.with_socket_mut::(self.handle, |socket| { + if socket.can_recv() { + // data available + op(socket) + } else { + // no more data + Err(AxError::WouldBlock) + } + }) + }) + } + + fn block_on(&self, mut f: F) -> AxResult + where + F: FnMut() -> AxResult, + { + if self.is_nonblocking() { + f() + } else { + loop { + SOCKET_SET.poll_interfaces(); + match f() { + Ok(t) => return Ok(t), + Err(AxError::WouldBlock) => axtask::yield_now(), + Err(e) => return Err(e), + } + } + } + } +} + +impl Drop for UdpSocket { + fn drop(&mut self) { + self.shutdown().ok(); + SOCKET_SET.remove(self.handle); + } +} + +fn get_ephemeral_port() -> AxResult { + const PORT_START: u16 = 0xc000; + const PORT_END: u16 = 0xffff; + static CURR: Mutex = Mutex::new(PORT_START); + let mut curr = CURR.lock(); + + let port = *curr; + if *curr == PORT_END { + *curr = PORT_START; + } else { + *curr += 1; + } + Ok(port) +} diff --git a/modules/axruntime/Cargo.toml b/modules/axruntime/Cargo.toml new file mode 100644 index 000000000..ccd000b1d --- /dev/null +++ b/modules/axruntime/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "axruntime" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "Runtime library of ArceOS" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axruntime" +documentation = "https://rcore-os.github.io/arceos/axruntime/index.html" + +[features] +default = [] + +smp = ["axhal/smp"] +irq = ["axhal/irq", "axtask?/irq", "percpu", "kernel_guard"] +tls = ["axhal/tls", "axtask?/tls"] +alloc = ["axalloc"] +paging = ["axhal/paging", "lazy_init"] + +multitask = ["axtask/multitask"] +fs = ["axdriver", "axfs"] +net = ["axdriver", "axnet"] +display = ["axdriver", "axdisplay"] + +[dependencies] +axhal = { path = "../axhal" } +axlog = { path = "../axlog" } +axconfig = { path = "../axconfig" } +axalloc = { path = "../axalloc", optional = true } +axdriver = { path = "../axdriver", optional = true } +axfs = { path = "../axfs", optional = true } +axnet = { path = "../axnet", optional = true } +axdisplay = { path = "../axdisplay", optional = true } +axtask = { path = "../axtask", optional = true } + +crate_interface = { path = "../../crates/crate_interface" } +percpu = { path = "../../crates/percpu", optional = true } +kernel_guard = { path = "../../crates/kernel_guard", optional = true } +lazy_init = { path = "../../crates/lazy_init", optional = true } diff --git a/modules/axruntime/src/lang_items.rs b/modules/axruntime/src/lang_items.rs new file mode 100644 index 000000000..6600cdd74 --- /dev/null +++ b/modules/axruntime/src/lang_items.rs @@ -0,0 +1,7 @@ +use core::panic::PanicInfo; + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + error!("{}", info); + axhal::misc::terminate() +} diff --git a/modules/axruntime/src/lib.rs b/modules/axruntime/src/lib.rs new file mode 100644 index 000000000..a62c3055c --- /dev/null +++ b/modules/axruntime/src/lib.rs @@ -0,0 +1,295 @@ +//! Runtime library of [ArceOS](https://github.com/rcore-os/arceos). +//! +//! Any application uses ArceOS should link this library. It does some +//! initialization work before entering the application's `main` function. +//! +//! # Cargo Features +//! +//! - `alloc`: Enable global memory allocator. +//! - `paging`: Enable page table manipulation support. +//! - `irq`: Enable interrupt handling support. +//! - `multitask`: Enable multi-threading support. +//! - `smp`: Enable SMP (symmetric multiprocessing) support. +//! - `fs`: Enable filesystem support. +//! - `net`: Enable networking support. +//! - `display`: Enable graphics support. +//! +//! All the features are optional and disabled by default. + +#![cfg_attr(not(test), no_std)] +#![feature(doc_auto_cfg)] + +#[macro_use] +extern crate axlog; + +#[cfg(all(target_os = "none", not(test)))] +mod lang_items; +mod trap; + +#[cfg(feature = "smp")] +mod mp; + +#[cfg(feature = "smp")] +pub use self::mp::rust_main_secondary; + +const LOGO: &str = r#" + d8888 .d88888b. .d8888b. + d88888 d88P" "Y88b d88P Y88b + d88P888 888 888 Y88b. + d88P 888 888d888 .d8888b .d88b. 888 888 "Y888b. + d88P 888 888P" d88P" d8P Y8b 888 888 "Y88b. + d88P 888 888 888 88888888 888 888 "888 + d8888888888 888 Y88b. Y8b. Y88b. .d88P Y88b d88P +d88P 888 888 "Y8888P "Y8888 "Y88888P" "Y8888P" +"#; + +extern "C" { + fn main(); +} + +struct LogIfImpl; + +#[crate_interface::impl_interface] +impl axlog::LogIf for LogIfImpl { + fn console_write_str(s: &str) { + axhal::console::write_bytes(s.as_bytes()); + } + + fn current_time() -> core::time::Duration { + axhal::time::current_time() + } + + fn current_cpu_id() -> Option { + #[cfg(feature = "smp")] + if is_init_ok() { + Some(axhal::cpu::this_cpu_id()) + } else { + None + } + #[cfg(not(feature = "smp"))] + Some(0) + } + + fn current_task_id() -> Option { + if is_init_ok() { + #[cfg(feature = "multitask")] + { + axtask::current_may_uninit().map(|curr| curr.id().as_u64()) + } + #[cfg(not(feature = "multitask"))] + None + } else { + None + } + } +} + +use core::sync::atomic::{AtomicUsize, Ordering}; + +static INITED_CPUS: AtomicUsize = AtomicUsize::new(0); + +fn is_init_ok() -> bool { + INITED_CPUS.load(Ordering::Acquire) == axconfig::SMP +} + +/// The main entry point of the ArceOS runtime. +/// +/// It is called from the bootstrapping code in [axhal]. `cpu_id` is the ID of +/// the current CPU, and `dtb` is the address of the device tree blob. It +/// finally calls the application's `main` function after all initialization +/// work is done. +/// +/// In multi-core environment, this function is called on the primary CPU, +/// and the secondary CPUs call [`rust_main_secondary`]. +#[cfg_attr(not(test), no_mangle)] +pub extern "C" fn rust_main(cpu_id: usize, dtb: usize) -> ! { + ax_println!("{}", LOGO); + ax_println!( + "\ + arch = {}\n\ + platform = {}\n\ + target = {}\n\ + smp = {}\n\ + build_mode = {}\n\ + log_level = {}\n\ + ", + option_env!("AX_ARCH").unwrap_or(""), + option_env!("AX_PLATFORM").unwrap_or(""), + option_env!("AX_TARGET").unwrap_or(""), + option_env!("AX_SMP").unwrap_or(""), + option_env!("AX_MODE").unwrap_or(""), + option_env!("AX_LOG").unwrap_or(""), + ); + + axlog::init(); + axlog::set_max_level(option_env!("AX_LOG").unwrap_or("")); // no effect if set `log-level-*` features + info!("Logging is enabled."); + info!("Primary CPU {} started, dtb = {:#x}.", cpu_id, dtb); + + info!("Found physcial memory regions:"); + for r in axhal::mem::memory_regions() { + info!( + " [{:x?}, {:x?}) {} ({:?})", + r.paddr, + r.paddr + r.size, + r.name, + r.flags + ); + } + + #[cfg(feature = "alloc")] + init_allocator(); + + #[cfg(feature = "paging")] + { + info!("Initialize kernel page table..."); + remap_kernel_memory().expect("remap kernel memoy failed"); + } + + info!("Initialize platform devices..."); + axhal::platform_init(); + + #[cfg(feature = "multitask")] + axtask::init_scheduler(); + + #[cfg(any(feature = "fs", feature = "net", feature = "display"))] + { + #[allow(unused_variables)] + let all_devices = axdriver::init_drivers(); + + #[cfg(feature = "fs")] + axfs::init_filesystems(all_devices.block); + + #[cfg(feature = "net")] + axnet::init_network(all_devices.net); + + #[cfg(feature = "display")] + axdisplay::init_display(all_devices.display); + } + + #[cfg(feature = "smp")] + self::mp::start_secondary_cpus(cpu_id); + + #[cfg(feature = "irq")] + { + info!("Initialize interrupt handlers..."); + init_interrupt(); + } + + #[cfg(all(feature = "tls", not(feature = "multitask")))] + { + info!("Initialize thread local storage..."); + init_tls(); + } + + info!("Primary CPU {} init OK.", cpu_id); + INITED_CPUS.fetch_add(1, Ordering::Relaxed); + + while !is_init_ok() { + core::hint::spin_loop(); + } + + unsafe { main() }; + + #[cfg(feature = "multitask")] + axtask::exit(0); + #[cfg(not(feature = "multitask"))] + { + debug!("main task exited: exit_code={}", 0); + axhal::misc::terminate(); + } +} + +#[cfg(feature = "alloc")] +fn init_allocator() { + use axhal::mem::{memory_regions, phys_to_virt, MemRegionFlags}; + + info!("Initialize global memory allocator..."); + info!(" use {} allocator.", axalloc::global_allocator().name()); + + let mut max_region_size = 0; + let mut max_region_paddr = 0.into(); + for r in memory_regions() { + if r.flags.contains(MemRegionFlags::FREE) && r.size > max_region_size { + max_region_size = r.size; + max_region_paddr = r.paddr; + } + } + for r in memory_regions() { + if r.flags.contains(MemRegionFlags::FREE) && r.paddr == max_region_paddr { + axalloc::global_init(phys_to_virt(r.paddr).as_usize(), r.size); + break; + } + } + for r in memory_regions() { + if r.flags.contains(MemRegionFlags::FREE) && r.paddr != max_region_paddr { + axalloc::global_add_memory(phys_to_virt(r.paddr).as_usize(), r.size) + .expect("add heap memory region failed"); + } + } +} + +#[cfg(feature = "paging")] +fn remap_kernel_memory() -> Result<(), axhal::paging::PagingError> { + use axhal::mem::{memory_regions, phys_to_virt}; + use axhal::paging::PageTable; + use lazy_init::LazyInit; + + static KERNEL_PAGE_TABLE: LazyInit = LazyInit::new(); + + if axhal::cpu::this_cpu_is_bsp() { + let mut kernel_page_table = PageTable::try_new()?; + for r in memory_regions() { + kernel_page_table.map_region( + phys_to_virt(r.paddr), + r.paddr, + r.size, + r.flags.into(), + true, + )?; + } + KERNEL_PAGE_TABLE.init_by(kernel_page_table); + } + + unsafe { axhal::arch::write_page_table_root(KERNEL_PAGE_TABLE.root_paddr()) }; + Ok(()) +} + +#[cfg(feature = "irq")] +fn init_interrupt() { + use axhal::time::TIMER_IRQ_NUM; + + // Setup timer interrupt handler + const PERIODIC_INTERVAL_NANOS: u64 = + axhal::time::NANOS_PER_SEC / axconfig::TICKS_PER_SEC as u64; + + #[percpu::def_percpu] + static NEXT_DEADLINE: u64 = 0; + + fn update_timer() { + let now_ns = axhal::time::current_time_nanos(); + // Safety: we have disabled preemption in IRQ handler. + let mut deadline = unsafe { NEXT_DEADLINE.read_current_raw() }; + if now_ns >= deadline { + deadline = now_ns + PERIODIC_INTERVAL_NANOS; + } + unsafe { NEXT_DEADLINE.write_current_raw(deadline + PERIODIC_INTERVAL_NANOS) }; + axhal::time::set_oneshot_timer(deadline); + } + + axhal::irq::register_handler(TIMER_IRQ_NUM, || { + update_timer(); + #[cfg(feature = "multitask")] + axtask::on_timer_tick(); + }); + + // Enable IRQs before starting app + axhal::arch::enable_irqs(); +} + +#[cfg(all(feature = "tls", not(feature = "multitask")))] +fn init_tls() { + let main_tls = axhal::tls::TlsArea::alloc(); + unsafe { axhal::arch::write_thread_pointer(main_tls.tls_ptr() as usize) }; + core::mem::forget(main_tls); +} diff --git a/modules/axruntime/src/mp.rs b/modules/axruntime/src/mp.rs new file mode 100644 index 000000000..768ef5188 --- /dev/null +++ b/modules/axruntime/src/mp.rs @@ -0,0 +1,64 @@ +use axconfig::{SMP, TASK_STACK_SIZE}; +use axhal::mem::{virt_to_phys, VirtAddr}; +use core::sync::atomic::{AtomicUsize, Ordering}; + +#[link_section = ".bss.stack"] +static mut SECONDARY_BOOT_STACK: [[u8; TASK_STACK_SIZE]; SMP - 1] = [[0; TASK_STACK_SIZE]; SMP - 1]; + +static ENTERED_CPUS: AtomicUsize = AtomicUsize::new(1); + +pub fn start_secondary_cpus(primary_cpu_id: usize) { + let mut logic_cpu_id = 0; + for i in 0..SMP { + if i != primary_cpu_id { + let stack_top = virt_to_phys(VirtAddr::from(unsafe { + SECONDARY_BOOT_STACK[logic_cpu_id].as_ptr_range().end as usize + })); + + debug!("starting CPU {}...", i); + axhal::mp::start_secondary_cpu(i, stack_top); + logic_cpu_id += 1; + + while ENTERED_CPUS.load(Ordering::Acquire) <= logic_cpu_id { + core::hint::spin_loop(); + } + } + } +} + +/// The main entry point of the ArceOS runtime for secondary CPUs. +/// +/// It is called from the bootstrapping code in [axhal]. +#[no_mangle] +pub extern "C" fn rust_main_secondary(cpu_id: usize) -> ! { + ENTERED_CPUS.fetch_add(1, Ordering::Relaxed); + info!("Secondary CPU {:x} started.", cpu_id); + + #[cfg(feature = "paging")] + super::remap_kernel_memory().unwrap(); + + axhal::platform_init_secondary(); + + #[cfg(feature = "multitask")] + axtask::init_scheduler_secondary(); + + info!("Secondary CPU {:x} init OK.", cpu_id); + super::INITED_CPUS.fetch_add(1, Ordering::Relaxed); + + while !super::is_init_ok() { + core::hint::spin_loop(); + } + + #[cfg(feature = "irq")] + axhal::arch::enable_irqs(); + + #[cfg(all(feature = "tls", not(feature = "multitask")))] + super::init_tls(); + + #[cfg(feature = "multitask")] + axtask::run_idle(); + #[cfg(not(feature = "multitask"))] + loop { + axhal::arch::wait_for_irqs(); + } +} diff --git a/modules/axruntime/src/trap.rs b/modules/axruntime/src/trap.rs new file mode 100644 index 000000000..5a8a80d38 --- /dev/null +++ b/modules/axruntime/src/trap.rs @@ -0,0 +1,13 @@ +struct TrapHandlerImpl; + +#[crate_interface::impl_interface] +impl axhal::trap::TrapHandler for TrapHandlerImpl { + fn handle_irq(_irq_num: usize) { + #[cfg(feature = "irq")] + { + let guard = kernel_guard::NoPreempt::new(); + axhal::irq::dispatch_irq(_irq_num); + drop(guard); // rescheduling may occur when preemption is re-enabled. + } + } +} diff --git a/modules/axsync/Cargo.toml b/modules/axsync/Cargo.toml new file mode 100644 index 000000000..50a4203c3 --- /dev/null +++ b/modules/axsync/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "axsync" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "ArceOS synchronization primitives" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axsync" +documentation = "https://rcore-os.github.io/arceos/axsync/index.html" + +[features] +multitask = ["axtask/multitask"] +default = [] + +[dependencies] +spinlock = { path = "../../crates/spinlock" } +axtask = { path = "../axtask" } + +[dev-dependencies] +rand = "0.8" +axsync = { path = ".", features = ["multitask"] } +axtask = { path = "../axtask", features = ["test"] } diff --git a/modules/axsync/src/lib.rs b/modules/axsync/src/lib.rs new file mode 100644 index 000000000..124585017 --- /dev/null +++ b/modules/axsync/src/lib.rs @@ -0,0 +1,28 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) synchronization primitives. +//! +//! Currently supported primitives: +//! +//! - [`Mutex`]: A mutual exclusion primitive. +//! - mod [`spin`](spinlock): spin-locks. +//! +//! # Cargo Features +//! +//! - `multitask`: For use in the multi-threaded environments. If the feature is +//! not enabled, [`Mutex`] will be an alias of [`spin::SpinNoIrq`]. This +//! feature is enabled by default. + +#![cfg_attr(not(test), no_std)] +#![feature(doc_cfg)] + +pub use spinlock as spin; + +#[cfg(feature = "multitask")] +mod mutex; + +#[cfg(feature = "multitask")] +#[doc(cfg(feature = "multitask"))] +pub use self::mutex::{Mutex, MutexGuard}; + +#[cfg(not(feature = "multitask"))] +#[doc(cfg(not(feature = "multitask")))] +pub use spinlock::{SpinNoIrq as Mutex, SpinNoIrqGuard as MutexGuard}; diff --git a/modules/axsync/src/mutex.rs b/modules/axsync/src/mutex.rs new file mode 100644 index 000000000..0777bcc6b --- /dev/null +++ b/modules/axsync/src/mutex.rs @@ -0,0 +1,252 @@ +//! A naïve sleeping mutex. + +use core::cell::UnsafeCell; +use core::fmt; +use core::ops::{Deref, DerefMut}; +use core::sync::atomic::{AtomicU64, Ordering}; + +use axtask::{current, WaitQueue}; + +/// A mutual exclusion primitive useful for protecting shared data, similar to +/// [`std::sync::Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html). +/// +/// When the mutex is locked, the current task will block and be put into the +/// wait queue. When the mutex is unlocked, all tasks waiting on the queue +/// will be woken up. +pub struct Mutex { + wq: WaitQueue, + owner_id: AtomicU64, + data: UnsafeCell, +} + +/// A guard that provides mutable data access. +/// +/// When the guard falls out of scope it will release the lock. +pub struct MutexGuard<'a, T: ?Sized + 'a> { + lock: &'a Mutex, + data: *mut T, +} + +// Same unsafe impls as `std::sync::Mutex` +unsafe impl Sync for Mutex {} +unsafe impl Send for Mutex {} + +impl Mutex { + /// Creates a new [`Mutex`] wrapping the supplied data. + #[inline(always)] + pub const fn new(data: T) -> Self { + Self { + wq: WaitQueue::new(), + owner_id: AtomicU64::new(0), + data: UnsafeCell::new(data), + } + } + + /// Consumes this [`Mutex`] and unwraps the underlying data. + #[inline(always)] + pub fn into_inner(self) -> T { + // We know statically that there are no outstanding references to + // `self` so there's no need to lock. + let Mutex { data, .. } = self; + data.into_inner() + } +} + +impl Mutex { + /// Returns `true` if the lock is currently held. + /// + /// # Safety + /// + /// This function provides no synchronization guarantees and so its result should be considered 'out of date' + /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. + #[inline(always)] + pub fn is_locked(&self) -> bool { + self.owner_id.load(Ordering::Relaxed) != 0 + } + + /// Locks the [`Mutex`] and returns a guard that permits access to the inner data. + /// + /// The returned value may be dereferenced for data access + /// and the lock will be dropped when the guard falls out of scope. + pub fn lock(&self) -> MutexGuard { + let current_id = current().id().as_u64(); + loop { + // Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock` + // when called in a loop. + match self.owner_id.compare_exchange_weak( + 0, + current_id, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => break, + Err(owner_id) => { + assert_ne!( + owner_id, + current_id, + "{} tried to acquire mutex it already owns.", + current().id_name() + ); + // Wait until the lock looks unlocked before retrying + self.wq.wait_until(|| !self.is_locked()); + } + } + } + MutexGuard { + lock: self, + data: unsafe { &mut *self.data.get() }, + } + } + + /// Try to lock this [`Mutex`], returning a lock guard if successful. + #[inline(always)] + pub fn try_lock(&self) -> Option> { + let current_id = current().id().as_u64(); + // The reason for using a strong compare_exchange is explained here: + // https://github.com/Amanieu/parking_lot/pull/207#issuecomment-575869107 + if self + .owner_id + .compare_exchange(0, current_id, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + Some(MutexGuard { + lock: self, + data: unsafe { &mut *self.data.get() }, + }) + } else { + None + } + } + + /// Force unlock the [`Mutex`]. + /// + /// # Safety + /// + /// This is *extremely* unsafe if the lock is not held by the current + /// thread. However, this can be useful in some instances for exposing + /// the lock to FFI that doesn’t know how to deal with RAII. + pub unsafe fn force_unlock(&self) { + let owner_id = self.owner_id.swap(0, Ordering::Release); + assert_eq!( + owner_id, + current().id().as_u64(), + "{} tried to release mutex it doesn't own", + current().id_name() + ); + self.wq.notify_one(true); + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the [`Mutex`] mutably, and a mutable reference is guaranteed to be exclusive in + /// Rust, no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As + /// such, this is a 'zero-cost' operation. + #[inline(always)] + pub fn get_mut(&mut self) -> &mut T { + // We know statically that there are no other references to `self`, so + // there's no need to lock the inner mutex. + unsafe { &mut *self.data.get() } + } +} + +impl Default for Mutex { + #[inline(always)] + fn default() -> Self { + Self::new(Default::default()) + } +} + +impl fmt::Debug for Mutex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.try_lock() { + Some(guard) => write!(f, "Mutex {{ data: ") + .and_then(|()| (*guard).fmt(f)) + .and_then(|()| write!(f, "}}")), + None => write!(f, "Mutex {{ }}"), + } + } +} + +impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> { + type Target = T; + #[inline(always)] + fn deref(&self) -> &T { + // We know statically that only we are referencing data + unsafe { &*self.data } + } +} + +impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut T { + // We know statically that only we are referencing data + unsafe { &mut *self.data } + } +} + +impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> { + /// The dropping of the [`MutexGuard`] will release the lock it was created from. + fn drop(&mut self) { + unsafe { self.lock.force_unlock() } + } +} + +#[cfg(test)] +mod tests { + use crate::Mutex; + use axtask as thread; + use std::sync::Once; + + static INIT: Once = Once::new(); + + fn may_interrupt() { + // simulate interrupts + if rand::random::() % 3 == 0 { + thread::yield_now(); + } + } + + #[test] + fn lots_and_lots() { + INIT.call_once(thread::init_scheduler); + + const NUM_TASKS: u32 = 10; + const NUM_ITERS: u32 = 10_000; + static M: Mutex = Mutex::new(0); + + fn inc(delta: u32) { + for _ in 0..NUM_ITERS { + let mut val = M.lock(); + *val += delta; + may_interrupt(); + drop(val); + may_interrupt(); + } + } + + for _ in 0..NUM_TASKS { + thread::spawn(|| inc(1)); + thread::spawn(|| inc(2)); + } + + println!("spawn OK"); + loop { + let val = M.lock(); + if *val == NUM_ITERS * NUM_TASKS * 3 { + break; + } + may_interrupt(); + drop(val); + may_interrupt(); + } + + assert_eq!(*M.lock(), NUM_ITERS * NUM_TASKS * 3); + println!("Mutex test OK"); + } +} diff --git a/modules/axtask/Cargo.toml b/modules/axtask/Cargo.toml new file mode 100644 index 000000000..73ce843ff --- /dev/null +++ b/modules/axtask/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "axtask" +version = "0.1.0" +edition = "2021" +authors = ["Yuekai Jia "] +description = "ArceOS task management module" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/axtask" +documentation = "https://rcore-os.github.io/arceos/axtask/index.html" + +[features] +default = [] + +multitask = [ + "dep:axconfig", "dep:percpu", "dep:spinlock", "dep:lazy_init", "dep:memory_addr", + "dep:scheduler", "dep:timer_list", "kernel_guard", "dep:crate_interface", +] +irq = [] +tls = ["axhal/tls"] +preempt = ["irq", "percpu?/preempt", "kernel_guard/preempt"] + +sched_fifo = ["multitask"] +sched_rr = ["multitask", "preempt"] +sched_cfs = ["multitask", "preempt"] + +test = ["percpu?/sp-naive"] + +[dependencies] +cfg-if = "1.0" +log = "0.4" +axhal = { path = "../axhal" } +axconfig = { path = "../axconfig", optional = true } +percpu = { path = "../../crates/percpu", optional = true } +spinlock = { path = "../../crates/spinlock", optional = true } +lazy_init = { path = "../../crates/lazy_init", optional = true } +memory_addr = { path = "../../crates/memory_addr", optional = true } +scheduler = { path = "../../crates/scheduler", optional = true } +timer_list = { path = "../../crates/timer_list", optional = true } +kernel_guard = { path = "../../crates/kernel_guard", optional = true } +crate_interface = { path = "../../crates/crate_interface", optional = true } + +[dev-dependencies] +rand = "0.8" +axhal = { path = "../axhal", features = ["fp_simd"] } +axtask = { path = ".", features = ["test"] } diff --git a/modules/axtask/src/api.rs b/modules/axtask/src/api.rs new file mode 100644 index 000000000..8e75f270e --- /dev/null +++ b/modules/axtask/src/api.rs @@ -0,0 +1,166 @@ +//! Task APIs for multi-task configuration. + +use alloc::{string::String, sync::Arc}; + +pub(crate) use crate::run_queue::{AxRunQueue, RUN_QUEUE}; + +#[doc(cfg(feature = "multitask"))] +pub use crate::task::{CurrentTask, TaskId, TaskInner}; +#[doc(cfg(feature = "multitask"))] +pub use crate::wait_queue::WaitQueue; + +/// The reference type of a task. +pub type AxTaskRef = Arc; + +cfg_if::cfg_if! { + if #[cfg(feature = "sched_rr")] { + const MAX_TIME_SLICE: usize = 5; + pub(crate) type AxTask = scheduler::RRTask; + pub(crate) type Scheduler = scheduler::RRScheduler; + } else if #[cfg(feature = "sched_cfs")] { + pub(crate) type AxTask = scheduler::CFSTask; + pub(crate) type Scheduler = scheduler::CFScheduler; + } else { + // If no scheduler features are set, use FIFO as the default. + pub(crate) type AxTask = scheduler::FifoTask; + pub(crate) type Scheduler = scheduler::FifoScheduler; + } +} + +#[cfg(feature = "preempt")] +struct KernelGuardIfImpl; + +#[cfg(feature = "preempt")] +#[crate_interface::impl_interface] +impl kernel_guard::KernelGuardIf for KernelGuardIfImpl { + fn disable_preempt() { + if let Some(curr) = current_may_uninit() { + curr.disable_preempt(); + } + } + + fn enable_preempt() { + if let Some(curr) = current_may_uninit() { + curr.enable_preempt(true); + } + } +} + +/// Gets the current task, or returns [`None`] if the current task is not +/// initialized. +pub fn current_may_uninit() -> Option { + CurrentTask::try_get() +} + +/// Gets the current task. +/// +/// # Panics +/// +/// Panics if the current task is not initialized. +pub fn current() -> CurrentTask { + CurrentTask::get() +} + +/// Initializes the task scheduler (for the primary CPU). +pub fn init_scheduler() { + info!("Initialize scheduling..."); + + crate::run_queue::init(); + #[cfg(feature = "irq")] + crate::timers::init(); + + info!(" use {} scheduler.", Scheduler::scheduler_name()); +} + +/// Initializes the task scheduler for secondary CPUs. +pub fn init_scheduler_secondary() { + crate::run_queue::init_secondary(); +} + +/// Handles periodic timer ticks for the task manager. +/// +/// For example, advance scheduler states, checks timed events, etc. +#[cfg(feature = "irq")] +#[doc(cfg(feature = "irq"))] +pub fn on_timer_tick() { + crate::timers::check_events(); + RUN_QUEUE.lock().scheduler_timer_tick(); +} + +/// Spawns a new task with the given parameters. +/// +/// Returns the task reference. +pub fn spawn_raw(f: F, name: String, stack_size: usize) -> AxTaskRef +where + F: FnOnce() + Send + 'static, +{ + let task = TaskInner::new(f, name, stack_size); + RUN_QUEUE.lock().add_task(task.clone()); + task +} + +/// Spawns a new task with the default parameters. +/// +/// The default task name is an empty string. The default task stack size is +/// [`axconfig::TASK_STACK_SIZE`]. +/// +/// Returns the task reference. +pub fn spawn(f: F) -> AxTaskRef +where + F: FnOnce() + Send + 'static, +{ + spawn_raw(f, "".into(), axconfig::TASK_STACK_SIZE) +} + +/// Set the priority for current task. +/// +/// The range of the priority is dependent on the underlying scheduler. For +/// example, in the [CFS] scheduler, the priority is the nice value, ranging from +/// -20 to 19. +/// +/// Returns `true` if the priority is set successfully. +/// +/// [CFS]: https://en.wikipedia.org/wiki/Completely_Fair_Scheduler +pub fn set_priority(prio: isize) -> bool { + RUN_QUEUE.lock().set_current_priority(prio) +} + +/// Current task gives up the CPU time voluntarily, and switches to another +/// ready task. +pub fn yield_now() { + RUN_QUEUE.lock().yield_current(); +} + +/// Current task is going to sleep for the given duration. +/// +/// If the feature `irq` is not enabled, it uses busy-wait instead. +pub fn sleep(dur: core::time::Duration) { + sleep_until(axhal::time::current_time() + dur); +} + +/// Current task is going to sleep, it will be woken up at the given deadline. +/// +/// If the feature `irq` is not enabled, it uses busy-wait instead. +pub fn sleep_until(deadline: axhal::time::TimeValue) { + #[cfg(feature = "irq")] + RUN_QUEUE.lock().sleep_until(deadline); + #[cfg(not(feature = "irq"))] + axhal::time::busy_wait_until(deadline); +} + +/// Exits the current task. +pub fn exit(exit_code: i32) -> ! { + RUN_QUEUE.lock().exit_current(exit_code) +} + +/// The idle task routine. +/// +/// It runs an infinite loop that keeps calling [`yield_now()`]. +pub fn run_idle() -> ! { + loop { + yield_now(); + debug!("idle task: waiting for IRQs..."); + #[cfg(feature = "irq")] + axhal::arch::wait_for_irqs(); + } +} diff --git a/modules/axtask/src/api_s.rs b/modules/axtask/src/api_s.rs new file mode 100644 index 000000000..aacbf805a --- /dev/null +++ b/modules/axtask/src/api_s.rs @@ -0,0 +1,22 @@ +//! Task APIs for single-task configuration. + +/// For single-task situation, we just relax the CPU and wait for incoming +/// interrupts. +pub fn yield_now() { + if cfg!(feature = "irq") { + axhal::arch::wait_for_irqs(); + } else { + core::hint::spin_loop(); + } +} + +/// For single-task situation, we just busy wait for the given duration. +pub fn sleep(dur: core::time::Duration) { + axhal::time::busy_wait(dur); +} + +/// For single-task situation, we just busy wait until reaching the given +/// deadline. +pub fn sleep_until(deadline: axhal::time::TimeValue) { + axhal::time::busy_wait_until(deadline); +} diff --git a/modules/axtask/src/lib.rs b/modules/axtask/src/lib.rs new file mode 100644 index 000000000..2147f4646 --- /dev/null +++ b/modules/axtask/src/lib.rs @@ -0,0 +1,53 @@ +//! [ArceOS](https://github.com/rcore-os/arceos) task management module. +//! +//! This module provides primitives for task management, including task +//! creation, scheduling, sleeping, termination, etc. The scheduler algorithm +//! is configurable by cargo features. +//! +//! # Cargo Features +//! +//! - `multitask`: Enable multi-task support. If it's enabled, complex task +//! management and scheduling is used, as well as more task-related APIs. +//! Otherwise, only a few APIs with naive implementation is available. +//! - `irq`: Interrupts are enabled. If this feature is enabled, timer-based +//! APIs can be used, such as [`sleep`], [`sleep_until`], and +//! [`WaitQueue::wait_timeout`]. +//! - `preempt`: Enable preemptive scheduling. +//! - `sched_fifo`: Use the [FIFO cooperative scheduler][1]. It also enables the +//! `multitask` feature if it is enabled. This feature is enabled by default, +//! and it can be overriden by other scheduler features. +//! - `sched_rr`: Use the [Round-robin preemptive scheduler][2]. It also enables +//! the `multitask` and `preempt` features if it is enabled. +//! - `sched_cfs`: Use the [Completely Fair Scheduler][3]. It also enables the +//! the `multitask` and `preempt` features if it is enabled. +//! +//! [1]: scheduler::FifoScheduler +//! [2]: scheduler::RRScheduler +//! [3]: scheduler::CFScheduler + +#![cfg_attr(not(test), no_std)] +#![feature(doc_cfg)] +#![feature(doc_auto_cfg)] + +cfg_if::cfg_if! { + if #[cfg(feature = "multitask")] { + #[macro_use] + extern crate log; + extern crate alloc; + + mod run_queue; + mod task; + mod api; + mod wait_queue; + + #[cfg(feature = "irq")] + mod timers; + + #[doc(cfg(feature = "multitask"))] + pub use self::api::*; + pub use self::api::{sleep, sleep_until, yield_now}; + } else { + mod api_s; + pub use self::api_s::{sleep, sleep_until, yield_now}; + } +} diff --git a/modules/axtask/src/run_queue.rs b/modules/axtask/src/run_queue.rs new file mode 100644 index 000000000..9c31f4384 --- /dev/null +++ b/modules/axtask/src/run_queue.rs @@ -0,0 +1,233 @@ +use alloc::collections::VecDeque; +use alloc::sync::Arc; +use lazy_init::LazyInit; +use scheduler::BaseScheduler; +use spinlock::SpinNoIrq; + +use crate::task::{CurrentTask, TaskState}; +use crate::{AxTaskRef, Scheduler, TaskInner, WaitQueue}; + +// TODO: per-CPU +pub(crate) static RUN_QUEUE: LazyInit> = LazyInit::new(); + +// TODO: per-CPU +static EXITED_TASKS: SpinNoIrq> = SpinNoIrq::new(VecDeque::new()); + +static WAIT_FOR_EXIT: WaitQueue = WaitQueue::new(); + +#[percpu::def_percpu] +static IDLE_TASK: LazyInit = LazyInit::new(); + +pub(crate) struct AxRunQueue { + scheduler: Scheduler, +} + +impl AxRunQueue { + pub fn new() -> SpinNoIrq { + let gc_task = TaskInner::new(gc_entry, "gc".into(), axconfig::TASK_STACK_SIZE); + let mut scheduler = Scheduler::new(); + scheduler.add_task(gc_task); + SpinNoIrq::new(Self { scheduler }) + } + + pub fn add_task(&mut self, task: AxTaskRef) { + debug!("task spawn: {}", task.id_name()); + assert!(task.is_ready()); + self.scheduler.add_task(task); + } + + #[cfg(feature = "irq")] + pub fn scheduler_timer_tick(&mut self) { + let curr = crate::current(); + if !curr.is_idle() && self.scheduler.task_tick(curr.as_task_ref()) { + #[cfg(feature = "preempt")] + curr.set_preempt_pending(true); + } + } + + pub fn yield_current(&mut self) { + let curr = crate::current(); + trace!("task yield: {}", curr.id_name()); + assert!(curr.is_running()); + self.resched(false); + } + + pub fn set_current_priority(&mut self, prio: isize) -> bool { + self.scheduler + .set_priority(crate::current().as_task_ref(), prio) + } + + #[cfg(feature = "preempt")] + pub fn preempt_resched(&mut self) { + let curr = crate::current(); + assert!(curr.is_running()); + + // When we get the mutable reference of the run queue, we must + // have held the `SpinNoIrq` lock with both IRQs and preemption + // disabled. So we need to set `current_disable_count` to 1 in + // `can_preempt()` to obtain the preemption permission before + // locking the run queue. + let can_preempt = curr.can_preempt(1); + + debug!( + "current task is to be preempted: {}, allow={}", + curr.id_name(), + can_preempt + ); + if can_preempt { + self.resched(true); + } else { + curr.set_preempt_pending(true); + } + } + + pub fn exit_current(&mut self, exit_code: i32) -> ! { + let curr = crate::current(); + debug!("task exit: {}, exit_code={}", curr.id_name(), exit_code); + assert!(curr.is_running()); + assert!(!curr.is_idle()); + if curr.is_init() { + EXITED_TASKS.lock().clear(); + axhal::misc::terminate(); + } else { + curr.set_state(TaskState::Exited); + curr.notify_exit(exit_code, self); + EXITED_TASKS.lock().push_back(curr.clone()); + WAIT_FOR_EXIT.notify_one_locked(false, self); + self.resched(false); + } + unreachable!("task exited!"); + } + + pub fn block_current(&mut self, wait_queue_push: F) + where + F: FnOnce(AxTaskRef), + { + let curr = crate::current(); + debug!("task block: {}", curr.id_name()); + assert!(curr.is_running()); + assert!(!curr.is_idle()); + + // we must not block current task with preemption disabled. + #[cfg(feature = "preempt")] + assert!(curr.can_preempt(1)); + + curr.set_state(TaskState::Blocked); + wait_queue_push(curr.clone()); + self.resched(false); + } + + pub fn unblock_task(&mut self, task: AxTaskRef, resched: bool) { + debug!("task unblock: {}", task.id_name()); + if task.is_blocked() { + task.set_state(TaskState::Ready); + self.scheduler.add_task(task); // TODO: priority + if resched { + #[cfg(feature = "preempt")] + crate::current().set_preempt_pending(true); + } + } + } + + #[cfg(feature = "irq")] + pub fn sleep_until(&mut self, deadline: axhal::time::TimeValue) { + let curr = crate::current(); + debug!("task sleep: {}, deadline={:?}", curr.id_name(), deadline); + assert!(curr.is_running()); + assert!(!curr.is_idle()); + + let now = axhal::time::current_time(); + if now < deadline { + crate::timers::set_alarm_wakeup(deadline, curr.clone()); + curr.set_state(TaskState::Blocked); + self.resched(false); + } + } +} + +impl AxRunQueue { + /// Common reschedule subroutine. If `preempt`, keep current task's time + /// slice, otherwise reset it. + fn resched(&mut self, preempt: bool) { + let prev = crate::current(); + if prev.is_running() { + prev.set_state(TaskState::Ready); + if !prev.is_idle() { + self.scheduler.put_prev_task(prev.clone(), preempt); + } + } + let next = self.scheduler.pick_next_task().unwrap_or_else(|| unsafe { + // Safety: IRQs must be disabled at this time. + IDLE_TASK.current_ref_raw().get_unchecked().clone() + }); + self.switch_to(prev, next); + } + + fn switch_to(&mut self, prev_task: CurrentTask, next_task: AxTaskRef) { + trace!( + "context switch: {} -> {}", + prev_task.id_name(), + next_task.id_name() + ); + #[cfg(feature = "preempt")] + next_task.set_preempt_pending(false); + next_task.set_state(TaskState::Running); + if prev_task.ptr_eq(&next_task) { + return; + } + + unsafe { + let prev_ctx_ptr = prev_task.ctx_mut_ptr(); + let next_ctx_ptr = next_task.ctx_mut_ptr(); + + // The strong reference count of `prev_task` will be decremented by 1, + // but won't be dropped until `gc_entry()` is called. + assert!(Arc::strong_count(prev_task.as_task_ref()) > 1); + assert!(Arc::strong_count(&next_task) >= 1); + + CurrentTask::set_current(prev_task, next_task); + (*prev_ctx_ptr).switch_to(&*next_ctx_ptr); + } + } +} + +fn gc_entry() { + loop { + // Drop all exited tasks and recycle resources. + let n = EXITED_TASKS.lock().len(); + for _ in 0..n { + // Do not do the slow drops in the critical section. + let task = EXITED_TASKS.lock().pop_front(); + if let Some(task) = task { + if Arc::strong_count(&task) == 1 { + // If I'm the last holder of the task, drop it immediately. + drop(task); + } else { + // Otherwise (e.g, `switch_to` is not compeleted, held by the + // joiner, etc), push it back and wait for them to drop first. + EXITED_TASKS.lock().push_back(task); + } + } + } + WAIT_FOR_EXIT.wait(); + } +} + +pub(crate) fn init() { + const IDLE_TASK_STACK_SIZE: usize = 4096; + let idle_task = TaskInner::new(|| crate::run_idle(), "idle".into(), IDLE_TASK_STACK_SIZE); + IDLE_TASK.with_current(|i| i.init_by(idle_task.clone())); + + let main_task = TaskInner::new_init("main".into()); + main_task.set_state(TaskState::Running); + + RUN_QUEUE.init_by(AxRunQueue::new()); + unsafe { CurrentTask::init_current(main_task) } +} + +pub(crate) fn init_secondary() { + let idle_task = TaskInner::new_init("idle".into()); + idle_task.set_state(TaskState::Running); + IDLE_TASK.with_current(|i| i.init_by(idle_task.clone())); + unsafe { CurrentTask::init_current(idle_task) } +} diff --git a/modules/axtask/src/task.rs b/modules/axtask/src/task.rs new file mode 100644 index 000000000..7db9d78b6 --- /dev/null +++ b/modules/axtask/src/task.rs @@ -0,0 +1,391 @@ +use alloc::{boxed::Box, string::String, sync::Arc}; +use core::ops::Deref; +use core::sync::atomic::{AtomicBool, AtomicI32, AtomicU64, AtomicU8, Ordering}; +use core::{alloc::Layout, cell::UnsafeCell, fmt, ptr::NonNull}; + +#[cfg(feature = "preempt")] +use core::sync::atomic::AtomicUsize; + +#[cfg(feature = "tls")] +use axhal::tls::TlsArea; + +use axhal::arch::TaskContext; +use memory_addr::{align_up_4k, VirtAddr}; + +use crate::{AxRunQueue, AxTask, AxTaskRef, WaitQueue}; + +/// A unique identifier for a thread. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub struct TaskId(u64); + +/// The possible states of a task. +#[repr(u8)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(crate) enum TaskState { + Running = 1, + Ready = 2, + Blocked = 3, + Exited = 4, +} + +/// The inner task structure. +pub struct TaskInner { + id: TaskId, + name: String, + is_idle: bool, + is_init: bool, + + entry: Option<*mut dyn FnOnce()>, + state: AtomicU8, + + in_wait_queue: AtomicBool, + #[cfg(feature = "irq")] + in_timer_list: AtomicBool, + + #[cfg(feature = "preempt")] + need_resched: AtomicBool, + #[cfg(feature = "preempt")] + preempt_disable_count: AtomicUsize, + + exit_code: AtomicI32, + wait_for_exit: WaitQueue, + + kstack: Option, + ctx: UnsafeCell, + + #[cfg(feature = "tls")] + tls: TlsArea, +} + +impl TaskId { + fn new() -> Self { + static ID_COUNTER: AtomicU64 = AtomicU64::new(1); + Self(ID_COUNTER.fetch_add(1, Ordering::Relaxed)) + } + + /// Convert the task ID to a `u64`. + pub const fn as_u64(&self) -> u64 { + self.0 + } +} + +impl From for TaskState { + #[inline] + fn from(state: u8) -> Self { + match state { + 1 => Self::Running, + 2 => Self::Ready, + 3 => Self::Blocked, + 4 => Self::Exited, + _ => unreachable!(), + } + } +} + +unsafe impl Send for TaskInner {} +unsafe impl Sync for TaskInner {} + +impl TaskInner { + /// Gets the ID of the task. + pub const fn id(&self) -> TaskId { + self.id + } + + /// Gets the name of the task. + pub fn name(&self) -> &str { + self.name.as_str() + } + + /// Get a combined string of the task ID and name. + pub fn id_name(&self) -> alloc::string::String { + alloc::format!("Task({}, {:?})", self.id.as_u64(), self.name) + } + + /// Wait for the task to exit, and return the exit code. + /// + /// It will return immediately if the task has already exited (but not dropped). + pub fn join(&self) -> Option { + self.wait_for_exit + .wait_until(|| self.state() == TaskState::Exited); + Some(self.exit_code.load(Ordering::Acquire)) + } +} + +// private methods +impl TaskInner { + fn new_common(id: TaskId, name: String) -> Self { + Self { + id, + name, + is_idle: false, + is_init: false, + entry: None, + state: AtomicU8::new(TaskState::Ready as u8), + in_wait_queue: AtomicBool::new(false), + #[cfg(feature = "irq")] + in_timer_list: AtomicBool::new(false), + #[cfg(feature = "preempt")] + need_resched: AtomicBool::new(false), + #[cfg(feature = "preempt")] + preempt_disable_count: AtomicUsize::new(0), + exit_code: AtomicI32::new(0), + wait_for_exit: WaitQueue::new(), + kstack: None, + ctx: UnsafeCell::new(TaskContext::new()), + #[cfg(feature = "tls")] + tls: TlsArea::alloc(), + } + } + + /// Create a new task with the given entry function and stack size. + pub(crate) fn new(entry: F, name: String, stack_size: usize) -> AxTaskRef + where + F: FnOnce() + Send + 'static, + { + let mut t = Self::new_common(TaskId::new(), name); + debug!("new task: {}", t.id_name()); + let kstack = TaskStack::alloc(align_up_4k(stack_size)); + + #[cfg(feature = "tls")] + let tls = VirtAddr::from(t.tls.tls_ptr() as usize); + #[cfg(not(feature = "tls"))] + let tls = VirtAddr::from(0); + + t.entry = Some(Box::into_raw(Box::new(entry))); + t.ctx.get_mut().init(task_entry as usize, kstack.top(), tls); + t.kstack = Some(kstack); + if t.name == "idle" { + t.is_idle = true; + } + Arc::new(AxTask::new(t)) + } + + /// Creates an "init task" using the current CPU states, to use as the + /// current task. + /// + /// As it is the current task, no other task can switch to it until it + /// switches out. + /// + /// And there is no need to set the `entry`, `kstack` or `tls` fields, as + /// they will be filled automatically when the task is switches out. + pub(crate) fn new_init(name: String) -> AxTaskRef { + let mut t = Self::new_common(TaskId::new(), name); + t.is_init = true; + if t.name == "idle" { + t.is_idle = true; + } + Arc::new(AxTask::new(t)) + } + + #[inline] + pub(crate) fn state(&self) -> TaskState { + self.state.load(Ordering::Acquire).into() + } + + #[inline] + pub(crate) fn set_state(&self, state: TaskState) { + self.state.store(state as u8, Ordering::Release) + } + + #[inline] + pub(crate) fn is_running(&self) -> bool { + matches!(self.state(), TaskState::Running) + } + + #[inline] + pub(crate) fn is_ready(&self) -> bool { + matches!(self.state(), TaskState::Ready) + } + + #[inline] + pub(crate) fn is_blocked(&self) -> bool { + matches!(self.state(), TaskState::Blocked) + } + + #[inline] + pub(crate) const fn is_init(&self) -> bool { + self.is_init + } + + #[inline] + pub(crate) const fn is_idle(&self) -> bool { + self.is_idle + } + + #[inline] + pub(crate) fn in_wait_queue(&self) -> bool { + self.in_wait_queue.load(Ordering::Acquire) + } + + #[inline] + pub(crate) fn set_in_wait_queue(&self, in_wait_queue: bool) { + self.in_wait_queue.store(in_wait_queue, Ordering::Release); + } + + #[inline] + #[cfg(feature = "irq")] + pub(crate) fn in_timer_list(&self) -> bool { + self.in_timer_list.load(Ordering::Acquire) + } + + #[inline] + #[cfg(feature = "irq")] + pub(crate) fn set_in_timer_list(&self, in_timer_list: bool) { + self.in_timer_list.store(in_timer_list, Ordering::Release); + } + + #[inline] + #[cfg(feature = "preempt")] + pub(crate) fn set_preempt_pending(&self, pending: bool) { + self.need_resched.store(pending, Ordering::Release) + } + + #[inline] + #[cfg(feature = "preempt")] + pub(crate) fn can_preempt(&self, current_disable_count: usize) -> bool { + self.preempt_disable_count.load(Ordering::Acquire) == current_disable_count + } + + #[inline] + #[cfg(feature = "preempt")] + pub(crate) fn disable_preempt(&self) { + self.preempt_disable_count.fetch_add(1, Ordering::Relaxed); + } + + #[inline] + #[cfg(feature = "preempt")] + pub(crate) fn enable_preempt(&self, resched: bool) { + if self.preempt_disable_count.fetch_sub(1, Ordering::Relaxed) == 1 && resched { + // If current task is pending to be preempted, do rescheduling. + Self::current_check_preempt_pending(); + } + } + + #[cfg(feature = "preempt")] + fn current_check_preempt_pending() { + let curr = crate::current(); + if curr.need_resched.load(Ordering::Acquire) && curr.can_preempt(0) { + let mut rq = crate::RUN_QUEUE.lock(); + if curr.need_resched.load(Ordering::Acquire) { + rq.preempt_resched(); + } + } + } + + pub(crate) fn notify_exit(&self, exit_code: i32, rq: &mut AxRunQueue) { + self.exit_code.store(exit_code, Ordering::Release); + self.wait_for_exit.notify_all_locked(false, rq); + } + + #[inline] + pub(crate) const unsafe fn ctx_mut_ptr(&self) -> *mut TaskContext { + self.ctx.get() + } +} + +impl fmt::Debug for TaskInner { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("TaskInner") + .field("id", &self.id) + .field("name", &self.name) + .field("state", &self.state()) + .finish() + } +} + +impl Drop for TaskInner { + fn drop(&mut self) { + debug!("task drop: {}", self.id_name()); + } +} + +struct TaskStack { + ptr: NonNull, + layout: Layout, +} + +impl TaskStack { + pub fn alloc(size: usize) -> Self { + let layout = Layout::from_size_align(size, 16).unwrap(); + Self { + ptr: NonNull::new(unsafe { alloc::alloc::alloc(layout) }).unwrap(), + layout, + } + } + + pub const fn top(&self) -> VirtAddr { + unsafe { core::mem::transmute(self.ptr.as_ptr().add(self.layout.size())) } + } +} + +impl Drop for TaskStack { + fn drop(&mut self) { + unsafe { alloc::alloc::dealloc(self.ptr.as_ptr(), self.layout) } + } +} + +use core::mem::ManuallyDrop; + +/// A wrapper of [`AxTaskRef`] as the current task. +pub struct CurrentTask(ManuallyDrop); + +impl CurrentTask { + pub(crate) fn try_get() -> Option { + let ptr: *const super::AxTask = axhal::cpu::current_task_ptr(); + if !ptr.is_null() { + Some(Self(unsafe { ManuallyDrop::new(AxTaskRef::from_raw(ptr)) })) + } else { + None + } + } + + pub(crate) fn get() -> Self { + Self::try_get().expect("current task is uninitialized") + } + + /// Converts [`CurrentTask`] to [`AxTaskRef`]. + pub fn as_task_ref(&self) -> &AxTaskRef { + &self.0 + } + + pub(crate) fn clone(&self) -> AxTaskRef { + self.0.deref().clone() + } + + pub(crate) fn ptr_eq(&self, other: &AxTaskRef) -> bool { + Arc::ptr_eq(&self.0, other) + } + + pub(crate) unsafe fn init_current(init_task: AxTaskRef) { + #[cfg(feature = "tls")] + axhal::arch::write_thread_pointer(init_task.tls.tls_ptr() as usize); + let ptr = Arc::into_raw(init_task); + axhal::cpu::set_current_task_ptr(ptr); + } + + pub(crate) unsafe fn set_current(prev: Self, next: AxTaskRef) { + let Self(arc) = prev; + ManuallyDrop::into_inner(arc); // `call Arc::drop()` to decrease prev task reference count. + let ptr = Arc::into_raw(next); + axhal::cpu::set_current_task_ptr(ptr); + } +} + +impl Deref for CurrentTask { + type Target = TaskInner; + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +extern "C" fn task_entry() -> ! { + // release the lock that was implicitly held across the reschedule + unsafe { crate::RUN_QUEUE.force_unlock() }; + #[cfg(feature = "irq")] + axhal::arch::enable_irqs(); + let task = crate::current(); + if let Some(entry) = task.entry { + unsafe { Box::from_raw(entry)() }; + } + crate::exit(0); +} diff --git a/modules/axtask/src/tests.rs b/modules/axtask/src/tests.rs new file mode 100644 index 000000000..c3dbe0af9 --- /dev/null +++ b/modules/axtask/src/tests.rs @@ -0,0 +1,130 @@ +use core::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Mutex, Once}; + +use crate::{self as axtask, current, WaitQueue}; + +static INIT: Once = Once::new(); +static SERIAL: Mutex<()> = Mutex::new(()); + +#[test] +fn test_sched_fifo() { + let _lock = SERIAL.lock(); + INIT.call_once(axtask::init_scheduler); + + const NUM_TASKS: usize = 10; + static FINISHED_TASKS: AtomicUsize = AtomicUsize::new(0); + + for i in 0..NUM_TASKS { + axtask::spawn_raw( + move || { + println!("sched_fifo: Hello, task {}! ({})", i, current().id_name()); + axtask::yield_now(); + let order = FINISHED_TASKS.fetch_add(1, Ordering::Relaxed); + assert_eq!(order, i); // FIFO scheduler + }, + format!("T{}", i), + 0x1000, + ); + } + + while FINISHED_TASKS.load(Ordering::Relaxed) < NUM_TASKS { + axtask::yield_now(); + } +} + +#[test] +fn test_fp_state_switch() { + let _lock = SERIAL.lock(); + INIT.call_once(axtask::init_scheduler); + + const NUM_TASKS: usize = 5; + const FLOATS: [f64; NUM_TASKS] = [ + 3.141592653589793, + 2.718281828459045, + -1.4142135623730951, + 0.0, + 0.618033988749895, + ]; + static FINISHED_TASKS: AtomicUsize = AtomicUsize::new(0); + + for (i, float) in FLOATS.iter().enumerate() { + axtask::spawn(move || { + let mut value = float + i as f64; + axtask::yield_now(); + value -= i as f64; + + println!("fp_state_switch: Float {} = {}", i, value); + assert!((value - float).abs() < 1e-9); + FINISHED_TASKS.fetch_add(1, Ordering::Relaxed); + }); + } + while FINISHED_TASKS.load(Ordering::Relaxed) < NUM_TASKS { + axtask::yield_now(); + } +} + +#[test] +fn test_wait_queue() { + let _lock = SERIAL.lock(); + INIT.call_once(axtask::init_scheduler); + + const NUM_TASKS: usize = 10; + + static WQ1: WaitQueue = WaitQueue::new(); + static WQ2: WaitQueue = WaitQueue::new(); + static COUNTER: AtomicUsize = AtomicUsize::new(0); + + for _ in 0..NUM_TASKS { + axtask::spawn(move || { + COUNTER.fetch_add(1, Ordering::Relaxed); + println!("wait_queue: task {:?} started", current().id()); + WQ1.notify_one(true); // WQ1.wait_until() + WQ2.wait(); + + assert!(!current().in_wait_queue()); + + COUNTER.fetch_sub(1, Ordering::Relaxed); + println!("wait_queue: task {:?} finished", current().id()); + WQ1.notify_one(true); // WQ1.wait_until() + }); + } + + println!("task {:?} is waiting for tasks to start...", current().id()); + WQ1.wait_until(|| COUNTER.load(Ordering::Relaxed) == NUM_TASKS); + assert_eq!(COUNTER.load(Ordering::Relaxed), NUM_TASKS); + assert!(!current().in_wait_queue()); + WQ2.notify_all(true); // WQ2.wait() + + println!( + "task {:?} is waiting for tasks to finish...", + current().id() + ); + WQ1.wait_until(|| COUNTER.load(Ordering::Relaxed) == 0); + assert_eq!(COUNTER.load(Ordering::Relaxed), 0); + assert!(!current().in_wait_queue()); +} + +#[test] +fn test_task_join() { + let _lock = SERIAL.lock(); + INIT.call_once(axtask::init_scheduler); + + const NUM_TASKS: usize = 10; + let mut tasks = Vec::with_capacity(NUM_TASKS); + + for i in 0..NUM_TASKS { + tasks.push(axtask::spawn_raw( + move || { + println!("task_join: task {}! ({})", i, current().id_name()); + axtask::yield_now(); + axtask::exit(i as _); + }, + format!("T{}", i), + 0x1000, + )); + } + + for i in 0..NUM_TASKS { + assert_eq!(tasks[i].join(), Some(i as _)); + } +} diff --git a/modules/axtask/src/timers.rs b/modules/axtask/src/timers.rs new file mode 100644 index 000000000..f68a9f0c8 --- /dev/null +++ b/modules/axtask/src/timers.rs @@ -0,0 +1,48 @@ +use alloc::sync::Arc; +use axhal::time::current_time; +use lazy_init::LazyInit; +use spinlock::SpinNoIrq; +use timer_list::{TimeValue, TimerEvent, TimerList}; + +use crate::{AxTaskRef, RUN_QUEUE}; + +// TODO: per-CPU +static TIMER_LIST: LazyInit>> = LazyInit::new(); + +struct TaskWakeupEvent(AxTaskRef); + +impl TimerEvent for TaskWakeupEvent { + fn callback(self, _now: TimeValue) { + let mut rq = RUN_QUEUE.lock(); + self.0.set_in_timer_list(false); + rq.unblock_task(self.0, true); + } +} + +pub fn set_alarm_wakeup(deadline: TimeValue, task: AxTaskRef) { + let mut timers = TIMER_LIST.lock(); + task.set_in_timer_list(true); + timers.set(deadline, TaskWakeupEvent(task)); +} + +pub fn cancel_alarm(task: &AxTaskRef) { + let mut timers = TIMER_LIST.lock(); + task.set_in_timer_list(false); + timers.cancel(|t| Arc::ptr_eq(&t.0, task)); +} + +pub fn check_events() { + loop { + let now = current_time(); + let event = TIMER_LIST.lock().expire_one(now); + if let Some((_deadline, event)) = event { + event.callback(now); + } else { + break; + } + } +} + +pub fn init() { + TIMER_LIST.init_by(SpinNoIrq::new(TimerList::new())); +} diff --git a/modules/axtask/src/wait_queue.rs b/modules/axtask/src/wait_queue.rs new file mode 100644 index 000000000..0fc06c3a8 --- /dev/null +++ b/modules/axtask/src/wait_queue.rs @@ -0,0 +1,216 @@ +use alloc::collections::VecDeque; +use alloc::sync::Arc; +use spinlock::SpinRaw; + +use crate::{AxRunQueue, AxTaskRef, CurrentTask, RUN_QUEUE}; + +/// A queue to store sleeping tasks. +/// +/// # Examples +/// +/// ``` +/// use axtask::WaitQueue; +/// use core::sync::atomic::{AtomicU32, Ordering}; +/// +/// static VALUE: AtomicU32 = AtomicU32::new(0); +/// static WQ: WaitQueue = WaitQueue::new(); +/// +/// axtask::init_scheduler(); +/// // spawn a new task that updates `VALUE` and notifies the main task +/// axtask::spawn(|| { +/// assert_eq!(VALUE.load(Ordering::Relaxed), 0); +/// VALUE.fetch_add(1, Ordering::Relaxed); +/// WQ.notify_one(true); // wake up the main task +/// }); +/// +/// WQ.wait(); // block until `notify()` is called +/// assert_eq!(VALUE.load(Ordering::Relaxed), 1); +/// ``` +pub struct WaitQueue { + queue: SpinRaw>, // we already disabled IRQs when lock the `RUN_QUEUE` +} + +impl WaitQueue { + /// Creates an empty wait queue. + pub const fn new() -> Self { + Self { + queue: SpinRaw::new(VecDeque::new()), + } + } + + /// Creates an empty wait queue with space for at least `capacity` elements. + pub fn with_capacity(capacity: usize) -> Self { + Self { + queue: SpinRaw::new(VecDeque::with_capacity(capacity)), + } + } + + fn cancel_events(&self, curr: CurrentTask) { + // A task can be wake up only one events (timer or `notify()`), remove + // the event from another queue. + if curr.in_wait_queue() { + // wake up by timer (timeout). + // `RUN_QUEUE` is not locked here, so disable IRQs. + let _guard = kernel_guard::IrqSave::new(); + self.queue.lock().retain(|t| !curr.ptr_eq(t)); + curr.set_in_wait_queue(false); + } + #[cfg(feature = "irq")] + if curr.in_timer_list() { + // timeout was set but not triggered (wake up by `WaitQueue::notify()`) + crate::timers::cancel_alarm(curr.as_task_ref()); + } + } + + /// Blocks the current task and put it into the wait queue, until other task + /// notifies it. + pub fn wait(&self) { + RUN_QUEUE.lock().block_current(|task| { + task.set_in_wait_queue(true); + self.queue.lock().push_back(task) + }); + self.cancel_events(crate::current()); + } + + /// Blocks the current task and put it into the wait queue, until the given + /// `condition` becomes true. + /// + /// Note that even other tasks notify this task, it will not wake up until + /// the condition becomes true. + pub fn wait_until(&self, condition: F) + where + F: Fn() -> bool, + { + loop { + let mut rq = RUN_QUEUE.lock(); + if condition() { + break; + } + rq.block_current(|task| { + task.set_in_wait_queue(true); + self.queue.lock().push_back(task); + }); + } + self.cancel_events(crate::current()); + } + + /// Blocks the current task and put it into the wait queue, until other tasks + /// notify it, or the given duration has elapsed. + #[cfg(feature = "irq")] + pub fn wait_timeout(&self, dur: core::time::Duration) -> bool { + let curr = crate::current(); + let deadline = axhal::time::current_time() + dur; + debug!( + "task wait_timeout: {} deadline={:?}", + curr.id_name(), + deadline + ); + crate::timers::set_alarm_wakeup(deadline, curr.clone()); + + RUN_QUEUE.lock().block_current(|task| { + task.set_in_wait_queue(true); + self.queue.lock().push_back(task) + }); + let timeout = curr.in_wait_queue(); // still in the wait queue, must have timed out + self.cancel_events(curr); + timeout + } + + /// Blocks the current task and put it into the wait queue, until the given + /// `condition` becomes true, or the given duration has elapsed. + /// + /// Note that even other tasks notify this task, it will not wake up until + /// the above conditions are met. + #[cfg(feature = "irq")] + pub fn wait_timeout_until(&self, dur: core::time::Duration, condition: F) -> bool + where + F: Fn() -> bool, + { + let curr = crate::current(); + let deadline = axhal::time::current_time() + dur; + debug!( + "task wait_timeout: {}, deadline={:?}", + curr.id_name(), + deadline + ); + crate::timers::set_alarm_wakeup(deadline, curr.clone()); + + let mut timeout = true; + while axhal::time::current_time() < deadline { + let mut rq = RUN_QUEUE.lock(); + if condition() { + timeout = false; + break; + } + rq.block_current(|task| { + task.set_in_wait_queue(true); + self.queue.lock().push_back(task); + }); + } + self.cancel_events(curr); + timeout + } + + /// Wakes up one task in the wait queue, usually the first one. + /// + /// If `resched` is true, the current task will be preempted when the + /// preemption is enabled. + pub fn notify_one(&self, resched: bool) -> bool { + let mut rq = RUN_QUEUE.lock(); + if !self.queue.lock().is_empty() { + self.notify_one_locked(resched, &mut rq) + } else { + false + } + } + + /// Wakes all tasks in the wait queue. + /// + /// If `resched` is true, the current task will be preempted when the + /// preemption is enabled. + pub fn notify_all(&self, resched: bool) { + loop { + let mut rq = RUN_QUEUE.lock(); + if let Some(task) = self.queue.lock().pop_front() { + task.set_in_wait_queue(false); + rq.unblock_task(task, resched); + } else { + break; + } + drop(rq); // we must unlock `RUN_QUEUE` after unlocking `self.queue`. + } + } + + /// Wake up the given task in the wait queue. + /// + /// If `resched` is true, the current task will be preempted when the + /// preemption is enabled. + pub fn notify_task(&mut self, resched: bool, task: &AxTaskRef) -> bool { + let mut rq = RUN_QUEUE.lock(); + let mut wq = self.queue.lock(); + if let Some(index) = wq.iter().position(|t| Arc::ptr_eq(t, task)) { + task.set_in_wait_queue(false); + rq.unblock_task(wq.remove(index).unwrap(), resched); + true + } else { + false + } + } + + pub(crate) fn notify_one_locked(&self, resched: bool, rq: &mut AxRunQueue) -> bool { + if let Some(task) = self.queue.lock().pop_front() { + task.set_in_wait_queue(false); + rq.unblock_task(task, resched); + true + } else { + false + } + } + + pub(crate) fn notify_all_locked(&self, resched: bool, rq: &mut AxRunQueue) { + while let Some(task) = self.queue.lock().pop_front() { + task.set_in_wait_queue(false); + rq.unblock_task(task, resched); + } + } +} diff --git a/platforms/aarch64-bsta1000b.toml b/platforms/aarch64-bsta1000b.toml new file mode 100644 index 000000000..097556a9f --- /dev/null +++ b/platforms/aarch64-bsta1000b.toml @@ -0,0 +1,54 @@ +# Architecture identifier. +arch = "aarch64" +# Platform identifier. +platform = "aarch64-bsta1000b" +# Platform family. +family = "aarch64-bsta1000b" + +# Base address of the whole physical memory. +phys-memory-base = "0x80000000" +# Size of the whole physical memory. +phys-memory-size = "0x70000000" +# Base physical address of the kernel image. +kernel-base-paddr = "0x81000000" +# Base virtual address of the kernel image. +kernel-base-vaddr = "0xffff_0000_8100_0000" +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0xffff_0000_0000_0000" +# MMIO regions with format (`base_paddr`, `size`). +mmio-regions = [ + ["0x20008000", "0x1000"], # uart8250 UART0 + ["0x32000000", "0x8000"], # arm,gic-400 + ["0x32011000", "0x1000"], # CPU CSR + ["0x33002000", "0x1000"], # Top CRM + ["0x70035000", "0x1000"], # CRM reg + ["0x70038000", "0x1000"], # aon pinmux +] + +virtio-mmio-regions = [] + +# Base physical address of the PCIe ECAM space. +# pci-ecam-base = "0x40_1000_0000" +# End PCI bus number (`bus-range` property in device tree). +# pci-bus-end = "0xff" +# PCI device memory ranges (`ranges` property in device tree). +# pci-ranges = [] + +# UART Address +uart-paddr = "0x20008000" +# UART irq from device tree +uart-irq = "0xd5" +# GICD Address +gicd-paddr = "0x32001000" +# GICC Address +gicc-paddr = "0x32002000" + +# BST A1000B board registers +CPU_CSR_BASE = "0x32011000" +A1000BASE_TOPCRM = "0x33002000" +A1000BASE_SAFETYCRM = "0x70035000" +A1000BASE_AONCFG = "0x70038000" + +# PSCI +psci-method = "smc" diff --git a/platforms/aarch64-qemu-virt.toml b/platforms/aarch64-qemu-virt.toml new file mode 100644 index 000000000..723bba7eb --- /dev/null +++ b/platforms/aarch64-qemu-virt.toml @@ -0,0 +1,81 @@ +# Architecture identifier. +arch = "aarch64" +# Platform identifier. +platform = "aarch64-qemu-virt" +# Platform family. +family = "aarch64-qemu-virt" + +# Base address of the whole physical memory. +phys-memory-base = "0x4000_0000" +# Size of the whole physical memory. +phys-memory-size = "0x800_0000" # 128M +# Base physical address of the kernel image. +kernel-base-paddr = "0x4008_0000" +# Base virtual address of the kernel image. +kernel-base-vaddr = "0xffff_0000_4008_0000" +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0xffff_0000_0000_0000" +# MMIO regions with format (`base_paddr`, `size`). +mmio-regions = [ + ["0x0900_0000", "0x1000"], # PL011 UART + ["0x0800_0000", "0x2_0000"], # GICv2 + ["0x0a00_0000", "0x4000"], # VirtIO + ["0x1000_0000", "0x2eff_0000"], # PCI memory ranges (ranges 1: 32-bit MMIO space) + ["0x40_1000_0000", "0x1000_0000"], # PCI config space +] +# VirtIO MMIO regions with format (`base_paddr`, `size`). +virtio-mmio-regions = [ + ["0x0a00_0000", "0x200"], + ["0x0a00_0200", "0x200"], + ["0x0a00_0400", "0x200"], + ["0x0a00_0600", "0x200"], + ["0x0a00_0800", "0x200"], + ["0x0a00_0a00", "0x200"], + ["0x0a00_0c00", "0x200"], + ["0x0a00_0e00", "0x200"], + ["0x0a00_1000", "0x200"], + ["0x0a00_1200", "0x200"], + ["0x0a00_1400", "0x200"], + ["0x0a00_1600", "0x200"], + ["0x0a00_1800", "0x200"], + ["0x0a00_1a00", "0x200"], + ["0x0a00_1c00", "0x200"], + ["0x0a00_1e00", "0x200"], + ["0x0a00_3000", "0x200"], + ["0x0a00_2200", "0x200"], + ["0x0a00_2400", "0x200"], + ["0x0a00_2600", "0x200"], + ["0x0a00_2800", "0x200"], + ["0x0a00_2a00", "0x200"], + ["0x0a00_2c00", "0x200"], + ["0x0a00_2e00", "0x200"], + ["0x0a00_3000", "0x200"], + ["0x0a00_3200", "0x200"], + ["0x0a00_3400", "0x200"], + ["0x0a00_3600", "0x200"], + ["0x0a00_3800", "0x200"], + ["0x0a00_3a00", "0x200"], + ["0x0a00_3c00", "0x200"], + ["0x0a00_3e00", "0x200"], +] +# Base physical address of the PCIe ECAM space. +pci-ecam-base = "0x40_1000_0000" +# End PCI bus number (`bus-range` property in device tree). +pci-bus-end = "0xff" +# PCI device memory ranges (`ranges` property in device tree). +pci-ranges = [ + ["0x3ef_f0000", "0x1_0000"], # PIO space + ["0x1000_0000", "0x2eff_0000"], # 32-bit MMIO space + ["0x80_0000_0000", "0x80_0000_0000"], # 64-but MMIO space +] +# UART Address +uart-paddr = "0x0900_0000" +uart-irq = "1" + +# GICC Address +gicc-paddr = "0x0801_0000" +gicd-paddr = "0x0800_0000" + +# PSCI +psci-method = "hvc" diff --git a/platforms/aarch64-raspi4.toml b/platforms/aarch64-raspi4.toml new file mode 100644 index 000000000..f204d1d1a --- /dev/null +++ b/platforms/aarch64-raspi4.toml @@ -0,0 +1,31 @@ +# Architecture identifier. +arch = "aarch64" +# Platform identifier. +platform = "aarch64-raspi4" +# Platform family. +family = "aarch64-raspi" + +# Base address of the whole physical memory. +phys-memory-base = "0x0" +# Size of the whole physical memory. +phys-memory-size = "0xFC00_0000" # 3G 960M +# Base physical address of the kernel image. +kernel-base-paddr = "0x8_0000" +# Base virtual address of the kernel image. +kernel-base-vaddr = "0xffff_0000_0008_0000" +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0xffff_0000_0000_0000" +# MMIO regions with format (`base_paddr`, `size`). +mmio-regions = [ + ["0xFE20_1000", "0x1000"], # PL011 UART + ["0xFF84_1000", "0x8000"], # GICv2 +] +virtio-mmio-regions = [] +# UART Address +uart-paddr = "0xFE20_1000" +uart-irq = "0x79" + +# GIC Address +gicc-paddr = "0xFF84_2000" +gicd-paddr = "0xFF84_1000" diff --git a/platforms/riscv64-qemu-virt.toml b/platforms/riscv64-qemu-virt.toml new file mode 100644 index 000000000..8c1051c89 --- /dev/null +++ b/platforms/riscv64-qemu-virt.toml @@ -0,0 +1,50 @@ +# Architecture identifier. +arch = "riscv64" +# Platform identifier. +platform = "riscv64-qemu-virt" +# Platform family. +family = "riscv64-qemu-virt" + +# Base address of the whole physical memory. +phys-memory-base = "0x8000_0000" +# Size of the whole physical memory. +phys-memory-size = "0x800_0000" # 128M +# Base physical address of the kernel image. +kernel-base-paddr = "0x8020_0000" +# Base virtual address of the kernel image. +kernel-base-vaddr = "0xffff_ffc0_8020_0000" +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0xffff_ffc0_0000_0000" +# MMIO regions with format (`base_paddr`, `size`). +mmio-regions = [ + ["0x0c00_0000", "0x21_0000"], # PLIC + ["0x1000_0000", "0x1000"], # UART + ["0x1000_1000", "0x8000"], # VirtIO + ["0x3000_0000", "0x1000_0000"], # PCI config space + ["0x4000_0000", "0x4000_0000"], # PCI memory ranges (ranges 1: 32-bit MMIO space) +] +# VirtIO MMIO regions with format (`base_paddr`, `size`). +virtio-mmio-regions = [ + ["0x1000_1000", "0x1000"], + ["0x1000_2000", "0x1000"], + ["0x1000_3000", "0x1000"], + ["0x1000_4000", "0x1000"], + ["0x1000_5000", "0x1000"], + ["0x1000_6000", "0x1000"], + ["0x1000_7000", "0x1000"], + ["0x1000_8000", "0x1000"], +] +# Base physical address of the PCIe ECAM space. +pci-ecam-base = "0x3000_0000" +# End PCI bus number (`bus-range` property in device tree). +pci-bus-end = "0xff" +# PCI device memory ranges (`ranges` property in device tree). +pci-ranges = [ + ["0x0300_0000", "0x1_0000"], # PIO space + ["0x4000_0000", "0x4000_0000"], # 32-bit MMIO space + ["0x4_0000_0000", "0x4_0000_0000"], # 64-but MMIO space +] + +# Timer interrupt frequency in Hz. +timer-frequency = "10_000_000" # 10MHz diff --git a/platforms/x86_64-pc-oslab.toml b/platforms/x86_64-pc-oslab.toml new file mode 100644 index 000000000..03363d962 --- /dev/null +++ b/platforms/x86_64-pc-oslab.toml @@ -0,0 +1,37 @@ +# Architecture identifier. +arch = "x86_64" +# Platform identifier. +platform = "x86_64-pc-oslab" +# Platform family. +family = "x86-pc" + +# Base address of the whole physical memory. +phys-memory-base = "0" +# Size of the whole physical memory. +phys-memory-size = "0x8000_0000" # 2G +# Base physical address of the kernel image. +kernel-base-paddr = "0x20_0000" +# Base virtual address of the kernel image. +kernel-base-vaddr = "0xffff_ff80_0020_0000" +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0xffff_ff80_0000_0000" +# MMIO regions with format (`base_paddr`, `size`). +mmio-regions = [ + ["0xfec0_0000", "0x1000"], # IO APIC + ["0xfed0_0000", "0x1000"], # HPET + ["0xfee0_0000", "0x1000"], # Local APIC + ["0xf000_0000", "0x0800_0000"], # PCI config space + ["0xfcd8_0000", "0x0008_0000"], # Ixgbe BAR0 +] +# VirtIO MMIO regions with format (`base_paddr`, `size`). +virtio-mmio-regions = [] +# Base physical address of the PCIe ECAM space (should read from ACPI 'MCFG' table). +pci-ecam-base = "0xf000_0000" +# End PCI bus number. +pci-bus-end = "0x7f" +# PCI device memory ranges (not used on x86). +pci-ranges = [] + +# Timer interrupt frequencyin Hz. +timer-frequency = "4_000_000_000" # 4.0GHz diff --git a/platforms/x86_64-qemu-q35.toml b/platforms/x86_64-qemu-q35.toml new file mode 100644 index 000000000..da931dafa --- /dev/null +++ b/platforms/x86_64-qemu-q35.toml @@ -0,0 +1,37 @@ +# Architecture identifier. +arch = "x86_64" +# Platform identifier. +platform = "x86_64-qemu-q35" +# Platform family. +family = "x86-pc" + +# Base address of the whole physical memory. +phys-memory-base = "0" +# Size of the whole physical memory. +phys-memory-size = "0x800_0000" # 128M +# Base physical address of the kernel image. +kernel-base-paddr = "0x20_0000" +# Base virtual address of the kernel image. +kernel-base-vaddr = "0xffff_ff80_0020_0000" +# Linear mapping offset, for quick conversions between physical and virtual +# addresses. +phys-virt-offset = "0xffff_ff80_0000_0000" +# MMIO regions with format (`base_paddr`, `size`). +mmio-regions = [ + ["0xb000_0000", "0x1000_0000"], # PCI config space + ["0xfe00_0000", "0xc0_0000"], # PCI devices + ["0xfec0_0000", "0x1000"], # IO APIC + ["0xfed0_0000", "0x1000"], # HPET + ["0xfee0_0000", "0x1000"], # Local APIC +] +# VirtIO MMIO regions with format (`base_paddr`, `size`). +virtio-mmio-regions = [] +# Base physical address of the PCIe ECAM space (should read from ACPI 'MCFG' table). +pci-ecam-base = "0xb000_0000" +# End PCI bus number. +pci-bus-end = "0xff" +# PCI device memory ranges (not used on x86). +pci-ranges = [] + +# Timer interrupt frequencyin Hz. +timer-frequency = "4_000_000_000" # 4.0GHz diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 000000000..9cda0c1fe --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +profile = "minimal" +channel = "nightly" +components = ["rust-src", "llvm-tools-preview", "rustfmt", "clippy"] +targets = ["x86_64-unknown-none", "riscv64gc-unknown-none-elf", "aarch64-unknown-none-softfloat"] diff --git a/scripts/make/bsta1000b-fada.mk b/scripts/make/bsta1000b-fada.mk new file mode 100644 index 000000000..7a9efe400 --- /dev/null +++ b/scripts/make/bsta1000b-fada.mk @@ -0,0 +1,4 @@ +fada: build + gzip -9 -cvf $(OUT_BIN) > arceos-fada.bin.gz + mkimage -f tools/bsta1000b/bsta1000b-fada-arceos.its arceos-fada.itb + @echo 'Built the FIT-uImage arceos-fada.itb' diff --git a/scripts/make/build.mk b/scripts/make/build.mk new file mode 100644 index 000000000..5c815f503 --- /dev/null +++ b/scripts/make/build.mk @@ -0,0 +1,49 @@ +# Main building script + +include scripts/make/cargo.mk +include scripts/make/features.mk + +ifeq ($(APP_TYPE), c) + include scripts/make/build_c.mk +else + rust_package := $(shell cat $(APP)/Cargo.toml | sed -n 's/^name = "\([a-z0-9A-Z_\-]*\)"/\1/p') + rust_target_dir := $(CURDIR)/target/$(TARGET)/$(MODE) + rust_elf := $(rust_target_dir)/$(rust_package) +endif + +ifneq ($(filter $(MAKECMDGOALS),doc doc_check_missing),) # run `cargo doc` + $(if $(V), $(info RUSTDOCFLAGS: "$(RUSTDOCFLAGS)")) + export RUSTDOCFLAGS +else ifeq ($(filter $(MAKECMDGOALS),clippy unittest unittest_no_fail_fast),) # not run `cargo test` or `cargo clippy` + ifneq ($(V),) + $(info APP: "$(APP)") + $(info APP_TYPE: "$(APP_TYPE)") + $(info FEATURES: "$(FEATURES)") + $(info arceos features: "$(AX_FEAT)") + $(info lib features: "$(LIB_FEAT)") + $(info app features: "$(APP_FEAT)") + endif + ifeq ($(APP_TYPE), c) + $(if $(V), $(info CFLAGS: "$(CFLAGS)") $(info LDFLAGS: "$(LDFLAGS)")) + else + $(if $(V), $(info RUSTFLAGS: "$(RUSTFLAGS)")) + export RUSTFLAGS + endif +endif + +_cargo_build: + @printf " $(GREEN_C)Building$(END_C) App: $(APP_NAME), Arch: $(ARCH), Platform: $(PLATFORM_NAME), App type: $(APP_TYPE)\n" +ifeq ($(APP_TYPE), rust) + $(call cargo_build,--manifest-path $(APP)/Cargo.toml,$(AX_FEAT) $(LIB_FEAT) $(APP_FEAT)) + @cp $(rust_elf) $(OUT_ELF) +else ifeq ($(APP_TYPE), c) + $(call cargo_build,-p axlibc,$(AX_FEAT) $(LIB_FEAT)) +endif + +$(OUT_DIR): + $(call run_cmd,mkdir,-p $@) + +$(OUT_BIN): _cargo_build $(OUT_ELF) + $(call run_cmd,$(OBJCOPY),$(OUT_ELF) --strip-all -O binary $@) + +.PHONY: _cargo_build diff --git a/scripts/make/build_c.mk b/scripts/make/build_c.mk new file mode 100644 index 000000000..cb92d1673 --- /dev/null +++ b/scripts/make/build_c.mk @@ -0,0 +1,77 @@ +rust_lib_name := axlibc +rust_lib := target/$(TARGET)/$(MODE)/lib$(rust_lib_name).a + +ulib_dir := ulib/axlibc +src_dir := $(ulib_dir)/c +obj_dir := $(ulib_dir)/build_$(ARCH) +inc_dir := $(ulib_dir)/include +c_lib := $(obj_dir)/libc.a +libgcc := + +last_cflags := $(obj_dir)/.cflags + +ulib_src := $(wildcard $(src_dir)/*.c) +ulib_hdr := $(wildcard $(inc_dir)/*.h) +ulib_obj := $(patsubst $(src_dir)/%.c,$(obj_dir)/%.o,$(ulib_src)) + +CFLAGS += $(addprefix -DAX_CONFIG_,$(shell echo $(lib_feat) | tr 'a-z' 'A-Z' | tr '-' '_')) +CFLAGS += -DAX_LOG_$(shell echo $(LOG) | tr 'a-z' 'A-Z') + +CFLAGS += -nostdinc -fno-builtin -ffreestanding -Wall +CFLAGS += -I$(CURDIR)/$(inc_dir) +LDFLAGS += -nostdlib -static -no-pie --gc-sections -T$(LD_SCRIPT) + +ifeq ($(MODE), release) + CFLAGS += -O3 +endif + +ifeq ($(ARCH), x86_64) + LDFLAGS += --no-relax +else ifeq ($(ARCH), riscv64) + CFLAGS += -march=rv64gc -mabi=lp64d -mcmodel=medany +endif + +ifeq ($(findstring fp_simd,$(FEATURES)),) + ifeq ($(ARCH), x86_64) + CFLAGS += -mno-sse + else ifeq ($(ARCH), aarch64) + CFLAGS += -mgeneral-regs-only + endif +else + ifeq ($(ARCH), riscv64) + # for compiler-rt fallbacks like `__addtf3`, `__multf3`, ... + libgcc := $(shell $(CC) -print-libgcc-file-name) + endif +endif + +_check_need_rebuild: $(obj_dir) + @if [ "$(CFLAGS)" != "`cat $(last_cflags) 2>&1`" ]; then \ + echo "CFLAGS changed, rebuild"; \ + echo "$(CFLAGS)" > $(last_cflags); \ + fi + +$(obj_dir): + $(call run_cmd,mkdir,-p $@) + +$(obj_dir)/%.o: $(src_dir)/%.c $(last_cflags) + $(call run_cmd,$(CC),$(CFLAGS) -c -o $@ $<) + +$(c_lib): $(obj_dir) _check_need_rebuild $(ulib_obj) + $(call run_cmd,$(AR),rcs $@ $(ulib_obj)) + +app-objs := main.o + +-include $(APP)/axbuild.mk # override `app-objs` + +app-objs := $(addprefix $(APP)/,$(app-objs)) + +$(APP)/%.o: $(APP)/%.c $(ulib_hdr) + $(call run_cmd,$(CC),$(CFLAGS) $(APP_CFLAGS) -c -o $@ $<) + +$(OUT_ELF): $(c_lib) $(rust_lib) $(libgcc) $(app-objs) + @printf " $(CYAN_C)Linking$(END_C) $(OUT_ELF)\n" + $(call run_cmd,$(LD),$(LDFLAGS) $^ -o $@) + +$(APP)/axbuild.mk: ; + +.PHONY: _check_need_rebuild diff --git a/scripts/make/cargo.mk b/scripts/make/cargo.mk new file mode 100644 index 000000000..fc07ea2da --- /dev/null +++ b/scripts/make/cargo.mk @@ -0,0 +1,52 @@ +# Cargo features and build args + +ifeq ($(V),1) + verbose := -v +else ifeq ($(V),2) + verbose := -vv +else + verbose := +endif + +build_args-release := --release + +build_args := \ + --target $(TARGET) \ + --target-dir $(CURDIR)/target \ + $(build_args-$(MODE)) \ + $(verbose) + +RUSTFLAGS := -C link-arg=-T$(LD_SCRIPT) -C link-arg=-no-pie +RUSTDOCFLAGS := --enable-index-page -Zunstable-options -D rustdoc::broken_intra_doc_links + +ifeq ($(ARCH), x86_64) + RUSTFLAGS += -C link-arg=--no-relax +endif + +ifeq ($(MAKECMDGOALS), doc_check_missing) + RUSTDOCFLAGS += -D missing-docs +endif + +define cargo_build + $(call run_cmd,cargo build,$(build_args) $(1) --features "$(strip $(2))") +endef + +define cargo_clippy + $(call run_cmd,cargo clippy,--all-features --workspace --exclude axlog $(1) $(verbose)) + $(call run_cmd,cargo clippy,-p axlog -p percpu -p percpu_macros $(1) $(verbose)) +endef + +all_packages := \ + $(shell ls $(CURDIR)/crates) \ + $(shell ls $(CURDIR)/modules) \ + axfeat arceos_api axstd axlibc + +define cargo_doc + $(call run_cmd,cargo doc,--no-deps --all-features --workspace --exclude "arceos-*" $(verbose)) + @# run twice to fix broken hyperlinks + $(foreach p,$(all_packages), \ + $(call run_cmd,cargo rustdoc,--all-features -p $(p) $(verbose)) + ) + @# for some crates, re-generate without `--all-features` + $(call run_cmd,cargo doc,--no-deps -p percpu $(verbose)) +endef diff --git a/scripts/make/features.mk b/scripts/make/features.mk new file mode 100644 index 000000000..2ae6276ad --- /dev/null +++ b/scripts/make/features.mk @@ -0,0 +1,60 @@ +# Features resolving. +# +# Inputs: +# - `FEATURES`: a list of features to be enabled split by spaces or commas. +# The features can be selected from the crate `axfeat` or the user library +# (crate `axstd` or `axlibc`). +# - `APP_FEATURES`: a list of features to be enabled for the Rust app. +# +# Outputs: +# - `AX_FEAT`: features to be enabled for ArceOS modules (crate `axfeat`). +# - `LIB_FEAT`: features to be enabled for the user library (crate `axstd`, `axlibc`). +# - `APP_FEAT`: features to be enabled for the Rust app. + +ifeq ($(APP_TYPE),c) + ax_feat_prefix := axfeat/ + lib_feat_prefix := axlibc/ + lib_features := fp_simd alloc multitask fs net fd pipe select epoll +else + # TODO: it's better to use `axfeat/` as `ax_feat_prefix`, but all apps need to have `axfeat` as a dependency + ax_feat_prefix := axstd/ + lib_feat_prefix := axstd/ + lib_features := +endif + +override FEATURES := $(shell echo $(FEATURES) | tr ',' ' ') + +ifeq ($(APP_TYPE), c) + ifneq ($(wildcard $(APP)/features.txt),) # check features.txt exists + override FEATURES += $(shell cat $(APP)/features.txt) + endif + ifneq ($(filter fs net pipe select epoll,$(FEATURES)),) + override FEATURES += fd + endif +endif + +override FEATURES := $(strip $(FEATURES)) + +ax_feat := +lib_feat := + +ifneq ($(filter $(LOG),off error warn info debug trace),) + ax_feat += log-level-$(LOG) +else + $(error "LOG" must be one of "off", "error", "warn", "info", "debug", "trace") +endif + +ifeq ($(BUS),pci) + ax_feat += bus-pci +endif + +ifeq ($(shell test $(SMP) -gt 1; echo $$?),0) + lib_feat += smp +endif + +ax_feat += $(filter-out $(lib_features),$(FEATURES)) +lib_feat += $(filter $(lib_features),$(FEATURES)) + +AX_FEAT := $(strip $(addprefix $(ax_feat_prefix),$(ax_feat))) +LIB_FEAT := $(strip $(addprefix $(lib_feat_prefix),$(lib_feat))) +APP_FEAT := $(strip $(shell echo $(APP_FEATURES) | tr ',' ' ')) diff --git a/scripts/make/qemu.mk b/scripts/make/qemu.mk new file mode 100644 index 000000000..3ed432614 --- /dev/null +++ b/scripts/make/qemu.mk @@ -0,0 +1,77 @@ +# QEMU arguments + +QEMU := qemu-system-$(ARCH) + +ifeq ($(BUS), mmio) + vdev-suffix := device +else ifeq ($(BUS), pci) + vdev-suffix := pci +else + $(error "BUS" must be one of "mmio" or "pci") +endif + +qemu_args-x86_64 := \ + -machine q35 \ + -kernel $(OUT_ELF) + +qemu_args-riscv64 := \ + -machine virt \ + -bios default \ + -kernel $(OUT_BIN) + +qemu_args-aarch64 := \ + -cpu cortex-a72 \ + -machine virt \ + -kernel $(OUT_BIN) + +qemu_args-y := -m 128M -smp $(SMP) $(qemu_args-$(ARCH)) + +qemu_args-$(BLK) += \ + -device virtio-blk-$(vdev-suffix),drive=disk0 \ + -drive id=disk0,if=none,format=raw,file=$(DISK_IMG) + +qemu_args-$(NET) += \ + -device virtio-net-$(vdev-suffix),netdev=net0 + +ifeq ($(NET_DEV), user) + qemu_args-$(NET) += -netdev user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555 +else ifeq ($(NET_DEV), tap) + qemu_args-$(NET) += -netdev tap,id=net0,ifname=tap0,script=no,downscript=no +else + $(error "NET_DEV" must be one of "user" or "tap") +endif + +ifeq ($(NET_DUMP), y) + qemu_args-$(NET) += -object filter-dump,id=dump0,netdev=net0,file=netdump.pcap +endif + +qemu_args-$(GRAPHIC) += \ + -device virtio-gpu-$(vdev-suffix) -vga none \ + -serial mon:stdio + +ifeq ($(GRAPHIC), n) + qemu_args-y += -nographic +endif + +ifeq ($(QEMU_LOG), y) + qemu_args-y += -D qemu.log -d in_asm,int,mmu,pcall,cpu_reset,guest_errors +endif + +qemu_args-debug := $(qemu_args-y) -s -S + +# Do not use KVM for debugging +ifeq ($(shell uname), Darwin) + qemu_args-$(ACCEL) += -cpu host -accel hvf +else + qemu_args-$(ACCEL) += -cpu host -accel kvm +endif + +define run_qemu + @printf " $(CYAN_C)Running$(END_C) on qemu...\n" + $(call run_cmd,$(QEMU),$(qemu_args-y)) +endef + +define run_qemu_debug + @printf " $(CYAN_C)Debugging$(END_C) on qemu...\n" + $(call run_cmd,$(QEMU),$(qemu_args-debug)) +endef diff --git a/scripts/make/raspi4.mk b/scripts/make/raspi4.mk new file mode 100644 index 000000000..66eea697b --- /dev/null +++ b/scripts/make/raspi4.mk @@ -0,0 +1,94 @@ +include tools/raspi4/common/docker.mk +include tools/raspi4/common/format.mk +include tools/raspi4/common/operating_system.mk + +##-------------------------------------------------------------------------------------------------- +## Optional, user-provided configuration values +##-------------------------------------------------------------------------------------------------- + +# Default to the RPi4. +BSP ?= rpi4 + +# Default to a serial device name that is common in Linux. +DEV_SERIAL ?= /dev/ttyUSB0 + +##-------------------------------------------------------------------------------------------------- +## BSP-specific configuration values +##-------------------------------------------------------------------------------------------------- +QEMU_MISSING_STRING = "This board is not yet supported for QEMU." + +ifeq ($(BSP),rpi4) + TARGET = aarch64-unknown-none-softfloat + KERNEL_BIN := $(OUT_BIN) + OBJDUMP_BINARY = aarch64-none-elf-objdump + NM_BINARY = aarch64-none-elf-nm + READELF_BINARY = aarch64-none-elf-readelf + OPENOCD_ARG = -f /openocd/tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f /openocd/rpi4.cfg + JTAG_BOOT_IMAGE = tools/raspi4/X1_JTAG_boot/jtag_boot_rpi4.img + RUSTC_MISC_ARGS = -C target-cpu=cortex-a72 +endif + +EXEC_MINIPUSH = ruby tools/raspi4/common/serial/minipush.rb + +##------------------------------------------------------------------------------ +## Dockerization +##------------------------------------------------------------------------------ +DOCKER_CMD = docker run -t --rm -v $(shell pwd):/work/tutorial -w /work/tutorial +DOCKER_CMD_INTERACT = $(DOCKER_CMD) -i +DOCKER_ARG_DIR_COMMON = -v $(shell pwd)/tools/raspi4/common:/work/common +DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/tools/raspi4/X1_JTAG_boot:/work/X1_JTAG_boot +DOCKER_ARG_DEV = --privileged -v /dev:/dev +DOCKER_ARG_NET = --network host + +# DOCKER_IMAGE defined in include file (see top of this file). +DOCKER_GDB = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) + +# Dockerize commands, which require USB device passthrough, only on Linux. +ifeq ($(shell uname -s),Linux) + DOCKER_CMD_DEV = $(DOCKER_CMD_INTERACT) $(DOCKER_ARG_DEV) + DOCKER_CHAINBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_IMAGE) + DOCKER_JTAGBOOT = $(DOCKER_CMD_DEV) $(DOCKER_ARG_DIR_COMMON) $(DOCKER_ARG_DIR_JTAG) $(DOCKER_IMAGE) + DOCKER_OPENOCD = $(DOCKER_CMD_DEV) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) +else + DOCKER_OPENOCD = echo "Not yet supported on non-Linux systems."; \# +endif + +##-------------------------------------------------------------------------------------------------- +## Targets +##-------------------------------------------------------------------------------------------------- +.PHONY: all chainboot + +all: $(KERNEL_BIN) + +##------------------------------------------------------------------------------ +## Push the kernel to the real HW target +##------------------------------------------------------------------------------ +chainboot: $(KERNEL_BIN) + @$(DOCKER_CHAINBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(KERNEL_BIN) + + +##-------------------------------------------------------------------------------------------------- +## Debugging targets +##-------------------------------------------------------------------------------------------------- +.PHONY: jtagboot openocd gdb gdb-opt0 + +##------------------------------------------------------------------------------ +## Push the JTAG boot image to the real HW target +##------------------------------------------------------------------------------ +jtagboot: $(KERNEL_BIN) + @$(DOCKER_JTAGBOOT) $(EXEC_MINIPUSH) $(DEV_SERIAL) $(JTAG_BOOT_IMAGE) + +##------------------------------------------------------------------------------ +## Start OpenOCD session +##------------------------------------------------------------------------------ +openocd: + $(call color_header, "Launching OpenOCD") + @$(DOCKER_OPENOCD) openocd $(OPENOCD_ARG) + +##------------------------------------------------------------------------------ +## Start GDB session +##------------------------------------------------------------------------------ +gdb: RUSTC_MISC_ARGS += -C debuginfo=2 +gdb: $(KERNEL_ELF) + $(call color_header, "Launching GDB") + @$(DOCKER_GDB) gdb-multiarch -q $(KERNEL_ELF) diff --git a/scripts/make/test.mk b/scripts/make/test.mk new file mode 100644 index 000000000..a29d670b8 --- /dev/null +++ b/scripts/make/test.mk @@ -0,0 +1,16 @@ +# Test scripts + +define unit_test + $(call run_cmd,cargo test,-p percpu $(1) -- --nocapture) + $(call run_cmd,cargo test,-p axfs $(1) --features "myfs" -- --nocapture) + $(call run_cmd,cargo test,--workspace --exclude "arceos-*" $(1) -- --nocapture) +endef + +test_app := +ifneq ($(filter command line,$(origin A) $(origin APP)),) + test_app := $(APP) +endif + +define app_test + $(CURDIR)/scripts/test/app_test.sh $(test_app) +endef diff --git a/scripts/make/utils.mk b/scripts/make/utils.mk new file mode 100644 index 000000000..21bac486f --- /dev/null +++ b/scripts/make/utils.mk @@ -0,0 +1,23 @@ +# Utility definitions and functions + +GREEN_C := \033[92;1m +CYAN_C := \033[96;1m +YELLOW_C := \033[93;1m +GRAY_C := \033[90m +WHITE_C := \033[37m +END_C := \033[0m + +define run_cmd + @printf '$(WHITE_C)$(1)$(END_C) $(GRAY_C)$(2)$(END_C)\n' + @$(1) $(2) +endef + +define make_disk_image_fat32 + @printf " $(GREEN_C)Creating$(END_C) FAT32 disk image \"$(1)\" ...\n" + @dd if=/dev/zero of=$(1) bs=1M count=64 + @mkfs.fat -F 32 $(1) +endef + +define make_disk_image + $(if $(filter $(1),fat32), $(call make_disk_image_fat32,$(2))) +endef diff --git a/scripts/net/qemu-tap-ifdown.sh b/scripts/net/qemu-tap-ifdown.sh new file mode 100755 index 000000000..12622a36b --- /dev/null +++ b/scripts/net/qemu-tap-ifdown.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +HOST_IF=$1 + +if [ -z "$HOST_IF" ]; then + echo "Usage: $0 " + exit 1 +fi + +echo "Deleting tap interface for QEMU" + +ip link del tap0 +sysctl -w net.ipv4.ip_forward=0 +iptables -t nat -D POSTROUTING -s 10.0.2.0/24 -o ${HOST_IF} -j MASQUERADE diff --git a/scripts/net/qemu-tap-ifup.sh b/scripts/net/qemu-tap-ifup.sh new file mode 100755 index 000000000..1adabc9b8 --- /dev/null +++ b/scripts/net/qemu-tap-ifup.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +HOST_IF=$1 + +if [ -z "$HOST_IF" ]; then + echo "Usage: $0 " + exit 1 +fi + +echo "Setting up tap interface for QEMU" + +ip tuntap add tap0 mode tap +ip addr add 10.0.2.2/24 dev tap0 +ip link set up dev tap0 + +sysctl -w net.ipv4.ip_forward=1 +iptables -t nat -A POSTROUTING -s 10.0.2.0/24 -o ${HOST_IF} -j MASQUERADE diff --git a/scripts/test/app_test.sh b/scripts/test/app_test.sh new file mode 100755 index 000000000..d7a1d28de --- /dev/null +++ b/scripts/test/app_test.sh @@ -0,0 +1,142 @@ +#!/bin/bash + +APP= +ROOT=$(realpath $(dirname $0))/../../ +TIMEOUT=60s +EXIT_STATUS=0 + +S_PASS=0 +S_FAILED=1 +S_TIMEOUT=2 +S_BUILD_FAILED=3 + +RED_C="\x1b[31;1m" +GREEN_C="\x1b[32;1m" +YELLOW_C="\x1b[33;1m" +CYAN_C="\x1b[36;1m" +BLOD_C="\x1b[1m" +END_C="\x1b[0m" + +if [ -z "$ARCH" ]; then + ARCH=x86_64 +fi +if [ "$ARCH" != "x86_64" ] && [ "$ARCH" != "riscv64" ] && [ "$ARCH" != "aarch64" ]; then + echo "Unknown architecture: $ARCH" + exit $S_FAILED +fi + +function compare() { + local actual=$1 + local expect=$2 + if [ ! -f "$expect" ]; then + MSG="expected output file \"${BLOD_C}$expect${END_C}\" not found!" + return $S_FAILED + fi + IFS='' + while read -r line; do + local matched=$(grep -m1 "$line" < "$actual") + if [ -z "$matched" ]; then + MSG="pattern \"${BLOD_C}$line${END_C}\" not matched!" + unset IFS + return $S_FAILED + fi + done < "$expect" + unset IFS + return $S_PASS +} + +function run_and_compare() { + local args=$1 + local expect=$2 + local actual=$3 + + echo -ne " run with \"${BLOD_C}$args${END_C}\": " + make -C "$ROOT" A="$APP" $args > "$actual" 2>&1 + if [ $? -ne 0 ]; then + return $S_BUILD_FAILED + fi + + TIMEFORMAT='%3Rs' + RUN_TIME=$( { time { timeout --foreground $TIMEOUT make -C "$ROOT" A="$APP" $args justrun > "$actual" 2>&1; }; } 2>&1 ) + local res=$? + if [ $res == 124 ]; then + return $S_TIMEOUT + elif [ $res -ne 0 ]; then + return $S_FAILED + fi + + compare "$actual" "$expect" + if [ $? -ne 0 ]; then + return $S_FAILED + else + return $S_PASS + fi +} + +function test_one() { + local args=$1 + local expect="$APP/$2" + local actual="$APP/actual.out" + args="$args ARCH=$ARCH ACCEL=n" + rm -f "$actual" + + MSG= + run_and_compare "$args" "$expect" "$actual" + local res=$? + + if [ $res -ne $S_PASS ]; then + EXIT_STATUS=$res + if [ $res == $S_FAILED ]; then + echo -e "${RED_C}failed!${END_C} $RUN_TIME" + elif [ $res == $S_TIMEOUT ]; then + echo -e "${YELLOW_C}timeout!${END_C} $RUN_TIME" + elif [ $res == $S_BUILD_FAILED ]; then + echo -e "${RED_C}build failed!${END_C}" + fi + if [ ! -z "$MSG" ]; then + echo -e " $MSG" + fi + echo -e "${RED_C}actual output${END_C}:" + cat "$actual" + else + echo -e "${GREEN_C}passed!${END_C} $RUN_TIME" + rm -f "$actual" + fi +} + +if [ -z "$1" ]; then + test_list=( + "apps/helloworld" + "apps/memtest" + "apps/exception" + "apps/task/yield" + "apps/task/parallel" + "apps/task/sleep" + "apps/task/priority" + "apps/task/tls" + "apps/net/httpclient" + "apps/c/helloworld" + "apps/c/memtest" + "apps/c/sqlite3" + "apps/c/httpclient" + "apps/c/pthread/basic" + "apps/c/pthread/sleep" + "apps/c/pthread/pipe" + "apps/c/pthread/parallel" + ) +else + test_list="$@" +fi + +for t in ${test_list[@]}; do + if [ -z "$1" ]; then + APP=$(realpath "$ROOT/$t") + else + APP=$(realpath "$(pwd)/$t") + fi + echo -e "${CYAN_C}Testing${END_C} $t:" + source "$APP/test_cmd" +done + +echo -e "test script exited with: $EXIT_STATUS" +exit $EXIT_STATUS diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 000000000..632baf2a0 --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1,5 @@ +deptool/Cargo.lock +deptool/target +deptool/output.txt +bwbench_client/target +bwbench_client/Cargo.lock \ No newline at end of file diff --git a/tools/bsta1000b/bsta1000b-fada-arceos.its b/tools/bsta1000b/bsta1000b-fada-arceos.its new file mode 100755 index 000000000..8062b6354 --- /dev/null +++ b/tools/bsta1000b/bsta1000b-fada-arceos.its @@ -0,0 +1,50 @@ +/* + * U-Boot uImage source file with multiple kernels, ramdisks and FDT blobs + */ + +/dts-v1/; + +/ { + description = "Various kernels, ramdisks and FDT blobs"; + #address-cells = <1>; + + images { + kernel { + description = "ArceOS for BST A1000B"; + data = /incbin/("../../arceos-fada.bin.gz"); + type = "kernel"; + arch = "arm64"; + os = "linux"; + compression = "gzip"; + load = <0x81000000>; + entry = <0x81000000>; + hash-1 { + algo = "md5"; + }; + hash-2 { + algo = "sha1"; + }; + }; + + fdt-fada { + description = "bsta1000b-fada fdt"; + data = /incbin/("./bsta1000b-fada.dtb"); + type = "flat_dt"; + arch = "arm64"; + compression = "none"; + hash-1 { + algo = "crc32"; + }; + }; + }; + + configurations { + default = "config-fada"; + + config-fada { + description = "bsta1000b fada configuration"; + kernel = "kernel"; + fdt = "fdt-fada"; + }; + }; +}; diff --git a/tools/bsta1000b/bsta1000b-fada.dtb b/tools/bsta1000b/bsta1000b-fada.dtb new file mode 100644 index 0000000000000000000000000000000000000000..ff0ea9a42d899a118de720648a7c88ee8c8e2fcb GIT binary patch literal 59621 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

VmAGw~$QIAD@^Jh@hUs8CwnfeU+GDmqsobnoe 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 gV7~}*aU#@dSg}>2bB(1q5uE@ literal 0 HcmV?d00001 diff --git a/tools/bwbench_client/Cargo.toml b/tools/bwbench_client/Cargo.toml new file mode 100644 index 000000000..5f20be709 --- /dev/null +++ b/tools/bwbench_client/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bwbench-client" +version = "0.1.0" +edition = "2021" +authors = ["ChengXiang Qi "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = "0.4" +libc = "0.2.18" + +[workspace] \ No newline at end of file diff --git a/tools/bwbench_client/README.md b/tools/bwbench_client/README.md new file mode 100644 index 000000000..0d447ccc0 --- /dev/null +++ b/tools/bwbench_client/README.md @@ -0,0 +1,37 @@ +# Benchmark BandWidth Client + +Benchmark BandWidth Client is a performance testing tool for measuring the network card's ability to send Ethernet packets. It can test both the transmission throughput and the reception throughput. + +## Usage +In client: +```shell +cargo build --release +sudo ./target/release/bwbench_client [sender|receiver] [interface] +``` + +By reading the source code, you can control the behavior of the benchmark by modifying constants such as `MAX_BYTES`. + +In arceos: + +```shell +make A=apps/net/bwbench LOG=info NET=y run +``` + +By default, arceos `bebench` uses `bench_transmit`. You can uncomment the line and add `bench_receive`, but please note that currently only one of either `bench_transmit` or `bench_receive` is allowed to be enabled. + + +## Example: benchmark bandwidth of QEMU tap netdev + +In client: + +```shell +cargo build --release +sudo ./scripts/net/qemu-tap-ifup.sh enp8s0 +sudo ./target/release/bwbench_client [sender|receiver] tap0 +``` + +In arceos: + +```shell +make A=apps/net/bwbench LOG=info NET=y NET_DEV=tap run +``` diff --git a/tools/bwbench_client/src/device.rs b/tools/bwbench_client/src/device.rs new file mode 100644 index 000000000..67097482a --- /dev/null +++ b/tools/bwbench_client/src/device.rs @@ -0,0 +1,217 @@ +use std::os::unix::io::AsRawFd; + +const ETH_P_ALL: libc::c_short = 0x0003; +const SIOCGIFINDEX: libc::c_ulong = 0x8933; +const SIOCGIFMTU: libc::c_ulong = 0x8921; +const SIOCSIFMTU: libc::c_ulong = 0x8922; +const SIOCGIFHWADDR: libc::c_ulong = 0x8927; + +#[repr(C)] +#[derive(Clone, Copy)] +#[allow(non_camel_case_types)] +union ifreq_data { + ifr_mtu: libc::c_int, + mac_addr: libc::sockaddr, +} + +#[repr(C)] +#[allow(non_camel_case_types)] +struct ifreq { + ifr_name: [libc::c_char; libc::IF_NAMESIZE], + ifr_data: ifreq_data, +} + +impl ifreq { + #[cfg(target_os = "linux")] + fn ifreq_for(interface: &str) -> ifreq { + let mut ifreq = ifreq { + ifr_name: [0; libc::IF_NAMESIZE], + ifr_data: ifreq_data { ifr_mtu: 0 }, + }; + for (i, byte) in interface.as_bytes().iter().enumerate() { + ifreq.ifr_name[i] = *byte as libc::c_char + } + ifreq + } + + #[cfg(target_os = "linux")] + fn ioctl(&mut self, lower: libc::c_int, cmd: libc::c_ulong) -> std::io::Result { + unsafe { + if libc::ioctl(lower, cmd as _, self as *mut Self) < 0 { + return Err(std::io::Error::last_os_error()); + } + } + Ok(self.ifr_data) + } +} + +pub struct NetDevice { + fd: libc::c_int, + ifreq: ifreq, + mac_addr: [u8; 6], +} + +impl AsRawFd for NetDevice { + fn as_raw_fd(&self) -> std::os::unix::io::RawFd { + self.fd + } +} + +impl NetDevice { + pub fn new(interface: &str) -> std::io::Result { + #[cfg(target_os = "linux")] + { + let lower = unsafe { + let lower = libc::socket( + libc::AF_PACKET, + libc::SOCK_RAW | libc::SOCK_NONBLOCK, + ETH_P_ALL.to_be() as i32, + ); + if lower == -1 { + return Err(std::io::Error::last_os_error()); + } + lower + }; + + let mut ifreq = ifreq::ifreq_for(interface); + + let ifreq_mac_addr = unsafe { ifreq.ioctl(lower, SIOCGIFHWADDR)?.mac_addr }; + let mut mac_addr = [0u8; 6]; + for i in 0..6 { + mac_addr[i] = ifreq_mac_addr.sa_data[i] as u8; + } + + println!( + "Device MAC: {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5] + ); + + let mut dev = Self { + fd: lower, + ifreq, + mac_addr, + }; + + dev.bind_interface()?; + + let mtu = dev.interface_mtu()?; + println!("DEVICE MTU: {}", mtu); + + Ok(dev) + } + #[cfg(not(target_os = "linux"))] + { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Not supported", + )) + } + } + + pub fn mac_addr(&self) -> [u8; 6] { + self.mac_addr + } + + pub fn bind_interface(&mut self) -> std::io::Result<()> { + #[cfg(target_os = "linux")] + { + let sockaddr = libc::sockaddr_ll { + sll_family: libc::AF_PACKET as u16, + sll_protocol: ETH_P_ALL.to_be() as u16, + sll_ifindex: unsafe { + self.ifreq.ioctl(self.fd, SIOCGIFINDEX)?.ifr_mtu as libc::c_int + }, + sll_hatype: 1, + sll_pkttype: 0, + sll_halen: 6, + sll_addr: [0; 8], + }; + + unsafe { + let res = libc::bind( + self.fd, + &sockaddr as *const libc::sockaddr_ll as *const libc::sockaddr, + std::mem::size_of::() as libc::socklen_t, + ); + if res == -1 { + return Err(std::io::Error::last_os_error()); + } + } + + Ok(()) + } + #[cfg(not(target_os = "linux"))] + { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Not supported", + )) + } + } + + pub fn interface_mtu(&mut self) -> std::io::Result { + #[cfg(target_os = "linux")] + { + self.ifreq + .ioctl(self.fd, SIOCGIFMTU) + .map(|mtu| unsafe { mtu.ifr_mtu as usize }) + } + #[cfg(not(target_os = "linux"))] + { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Not supported", + )) + } + } + + pub fn recv(&mut self, buffer: &mut [u8]) -> std::io::Result { + let len = unsafe { + libc::recv( + self.fd, + buffer.as_mut_ptr() as *mut libc::c_void, + buffer.len(), + 0, + ) + }; + + if len == -1 { + let err = std::io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::WouldBlock { + return Err(err); + } else { + panic!("err: {:?}", err); + } + } + Ok(len as usize) + } + + pub fn send(&mut self, buffer: &[u8]) -> std::io::Result { + let len = unsafe { + libc::send( + self.fd, + buffer.as_ptr() as *const libc::c_void, + buffer.len(), + 0, + ) + }; + + if len == -1 { + let err = std::io::Error::last_os_error(); + if err.kind() == std::io::ErrorKind::WouldBlock { + return Err(err); + } else { + panic!("err: {:?}", err); + } + } + Ok(len as usize) + } +} + +impl Drop for NetDevice { + fn drop(&mut self) { + unsafe { + libc::close(self.fd); + } + } +} diff --git a/tools/bwbench_client/src/main.rs b/tools/bwbench_client/src/main.rs new file mode 100644 index 000000000..f9b24c69c --- /dev/null +++ b/tools/bwbench_client/src/main.rs @@ -0,0 +1,133 @@ +//! A raw socket benchmark client. + +#![deny(warnings)] +#![deny(missing_docs)] +#![allow(dead_code, unused_variables)] + +use crate::device::NetDevice; +use chrono::Local; +use std::env; +use std::fmt::Display; + +mod device; + +const STANDARD_MTU: usize = 1500; + +const MAX_BYTES: usize = 10 * GB; +const MB: usize = 1000 * 1000; +const GB: usize = 1000 * MB; + +struct EthernetMacAddress([u8; 6]); + +impl Display for EthernetMacAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mac = self.0; + write!( + f, + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] + ) + } +} + +enum Client { + Sender, + Receiver, +} + +fn transmit_benchmark(interface: &str) { + println!("Sender Mode!"); + let mut dev = NetDevice::new(interface).unwrap(); + + let mut tx_buf = [1u8; STANDARD_MTU]; + // ether type: IPv4 + tx_buf[12..14].copy_from_slice(&[0x08, 0x00]); + + let mut send_bytes = 0; + let mut past_send_bytes = 0; + let mut past_time = Local::now(); + + loop { + if let Ok(len) = dev.send(&tx_buf[..]) { + send_bytes += len; + let current_time = Local::now(); + if current_time.signed_duration_since(past_time).num_seconds() == 1 { + let gb = ((send_bytes - past_send_bytes) * 8) / GB; + let mb = (((send_bytes - past_send_bytes) * 8) % GB) / MB; + let gib = (send_bytes - past_send_bytes) / GB; + let mib = ((send_bytes - past_send_bytes) % GB) / MB; + println!( + "Transfer: {}.{:03}GBytes, Bandwidth: {}.{:03}Gbits/sec.", + gib, mib, gb, mb + ); + past_send_bytes = send_bytes; + past_time = current_time; + } + } + + if send_bytes >= MAX_BYTES { + break; + } + } +} + +fn receive_benchmark(interface: &str) { + println!("Receiver Mode!"); + let mut dev = NetDevice::new(interface).unwrap(); + + let mut receive_bytes = 0; + let mut past_receive_bytes = 0; + let mut past_time = Local::now(); + + let mut rx_buffer = [0; STANDARD_MTU]; + + loop { + if let Ok(len) = dev.recv(&mut rx_buffer) { + receive_bytes += len; + } + + let current_time = Local::now(); + if current_time.signed_duration_since(past_time).num_seconds() == 1 { + let gb = ((receive_bytes - past_receive_bytes) * 8) / GB; + let mb = (((receive_bytes - past_receive_bytes) * 8) % GB) / MB; + let gib = (receive_bytes - past_receive_bytes) / GB; + let mib = ((receive_bytes - past_receive_bytes) % GB) / MB; + println!( + "Receive: {}.{:03}GBytes, Bandwidth: {}.{:03}Gbits/sec.", + gib, mib, gb, mb + ); + past_receive_bytes = receive_bytes; + past_time = current_time; + } + + if receive_bytes >= MAX_BYTES { + break; + } + } +} + +fn benchmark_bandwidth(client: Client, interface: &str) { + match client { + Client::Sender => transmit_benchmark(interface), + Client::Receiver => receive_benchmark(interface), + } +} + +fn main() { + let args: Vec = env::args().collect(); + + if args.len() < 3 { + panic!("Usage: cargo run --release [send|receive] "); + } + + let kind = args[1].as_str(); + let client = match kind.chars().next().unwrap() { + 's' => Client::Sender, + 'r' => Client::Receiver, + _ => panic!("Unknown Mode!"), + }; + + let interface = args[2].as_str(); + + benchmark_bandwidth(client, interface); +} diff --git a/tools/deptool/Cargo.toml b/tools/deptool/Cargo.toml new file mode 100644 index 000000000..3a2e95435 --- /dev/null +++ b/tools/deptool/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "deptool" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + clap = {version = "4.3.5"} +[workspace] diff --git a/tools/deptool/Makefile b/tools/deptool/Makefile new file mode 100644 index 000000000..e046bbeb9 --- /dev/null +++ b/tools/deptool/Makefile @@ -0,0 +1,34 @@ +FEATURES ?= default +DEFAULT ?= y +FORMAT ?= mermaid +TARGET ?= helloworld +SAVE_PATH ?= output.txt +_DEFAULT_OPT = +_FEATURES_OPT = +BUILD_DIR = ./target + +ifeq ($(DEFAULT), y) + _DEFAULT_OPT = --no-default +endif + +ifneq ($(FEATURES), default) + _FEATURES_OPT = --name $(FEATURES) +endif + +ifeq ($(TARGET),) + $(error must specify a target using TARGET=... which should be a valid module, crate or app path) +endif + +clean: + cargo clean + rm $(SAVE_PATH) + +run: + @cargo build + @./target/debug/deptool $(_DEFAULT_OPT) \ + $(_FEATURES_OPT) \ + --format $(FORMAT) \ + --target $(TARGET) \ + --save-path $(SAVE_PATH) + +.PHONY: build run clean diff --git a/tools/deptool/README.md b/tools/deptool/README.md new file mode 100644 index 000000000..0d43a5718 --- /dev/null +++ b/tools/deptool/README.md @@ -0,0 +1,17 @@ +## Usage of this tool + +``` +make run FORMAT=mermaid FEATURES=f1,f2,f3 DEFAULT=n +``` + +the `FORMAT` can be either **mermaid** or **d2** (default is mermaid), it will out put the result under the deptool directory. + +the `TARGET` should be any existed crate or module name, or the path under app directory: eg helloworld, net/httpserver + +the `FEATURES` should be the features you want to use, this should be separated by "," + +the `DEFAULT` is used to control enable default features or not **n** for no and **y** for yes + +the first time you run this tool to analyze a crate/module/app will be slow or blocked for downloading the needed crates for the target + +if you think this makefile too naive, you can just run `cargo build`, and then use `./target/debug/deptool -h` to see the available options to use diff --git a/tools/deptool/src/cmd_builder.rs b/tools/deptool/src/cmd_builder.rs new file mode 100644 index 000000000..5a6b389d7 --- /dev/null +++ b/tools/deptool/src/cmd_builder.rs @@ -0,0 +1,18 @@ +use crate::Config; + +pub fn build_cargo_tree_cmd(cfg: &Config) -> String { + let default_opt = match cfg.no_default { + true => "", + false => "--no-default-features" + }; + + let features_opt = match cfg.features.len() { + 0 => "".to_string(), + _ => "-F ".to_string() + cfg.features.join(" ").as_str() + }; + let path = &cfg.loc; + let cmd_str = format!( + "cd {path} && cargo tree -e normal,build {default_opt} {features_opt} --format {{p}} --prefix depth", + ); + cmd_str.to_string() +} diff --git a/tools/deptool/src/cmd_parser.rs b/tools/deptool/src/cmd_parser.rs new file mode 100644 index 000000000..d3bc109bc --- /dev/null +++ b/tools/deptool/src/cmd_parser.rs @@ -0,0 +1,91 @@ +use std::{fs, path::Path}; +use clap::{Arg, ArgAction, Command}; +use crate::{Config, GraphFormat}; + +static APP_ROOT: &str = "../../apps/"; +static CRATE_ROOT: &str = "../../crates/"; +static MODULE_ROOT: &str = "../../modules/"; +static ULIB_ROOT: &str = "../../ulib/"; + + +/// Ex: exe --default=false --format=mermaid --features=f1 f2 f3 +pub fn parse_cmd() -> Result { + let matches = Command::new("Dependency analysis tool for Arceos") + .version("1.0") + .author("ctr") + .about("Generate d2 or mermaid dependency graph for Arceos based on cargo tree") + .arg( + Arg::new("no-default").short('d').long("no-default").action(ArgAction::SetFalse) + ) + .arg( + Arg::new("features").short('f').long("name").action(ArgAction::Append) + ) + .arg( + Arg::new("format").short('o').long("format").default_value("mermaid") + ) + .arg( + Arg::new("target").short('t').long("target").required(true) + ) + .arg( + Arg::new("save-path").short('s').long("save-path").default_value("out.txt") + ) + .get_matches(); + + let is_default = matches.get_flag("no-default"); + let features = matches.get_many::("features").unwrap_or_default() + .map(|f| f.to_string()) + .collect(); + let format = match matches.get_one::("format").unwrap().as_str() { + "d2" => GraphFormat::D2, + _ => GraphFormat::Mermaid + }; + let target = matches.get_one::("target").unwrap().to_string(); + if !is_arceos_crate(&target) { + return Err("target not exist, should be valid arceos crate, module or app"); + } + + let loc; + if check_crate_name(&target) { + loc = CRATE_ROOT.to_string() + ⌖ + } else if check_module_name(&target) { + loc = MODULE_ROOT.to_string() + ⌖ + } else { + loc = APP_ROOT.to_string() + ⌖ + } + let output_loc = matches.get_one::("save-path").unwrap().to_string(); + Ok(gen_config(is_default, features, format, loc, output_loc)) +} + +fn gen_config(is_default: bool, features: Vec::, format: GraphFormat, loc: String, output_loc: String) -> Config { + Config::build(is_default, features, format, loc, output_loc) +} + +pub fn check_crate_name(name: &String) -> bool { + let crates = fs::read_dir(CRATE_ROOT).unwrap(); + crates.into_iter().map(|p| p.unwrap().file_name()).any(|n| n.to_str().unwrap() == name) +} + +pub fn check_module_name(name: &String) -> bool { + let crates = fs::read_dir(MODULE_ROOT).unwrap(); + crates.into_iter().map(|p| p.unwrap().file_name()).any(|n| n.to_str().unwrap() == name) +} + +pub fn check_app_name(name: &String) -> bool { + Path::new(&(APP_ROOT.to_string() + name)).exists() +} + +pub fn check_lib_name(name: &String) -> bool { + Path::new(&(ULIB_ROOT.to_string() + name)).exists() +} + +pub fn is_arceos_crate(name: &String) -> bool { + check_crate_name(&name) || check_module_name(&name) || check_app_name(name) || check_lib_name(name) +} + +pub fn build_loc(name: &String) -> String { + if check_module_name(name) { + MODULE_ROOT.to_string() + name + } else { + CRATE_ROOT.to_string() + name + } +} diff --git a/tools/deptool/src/d2_generator.rs b/tools/deptool/src/d2_generator.rs new file mode 100644 index 000000000..0a76e107f --- /dev/null +++ b/tools/deptool/src/d2_generator.rs @@ -0,0 +1,39 @@ +use std::collections::HashMap; + +use crate::{parse_deps, cmd_parser::is_arceos_crate}; + +/// without further exploiting the feature of d2 graph, this is almost the same syntax with mermaid +/// except that d2 use " -> ", instead of "-->" +pub fn gen_d2_script(deps: &String, result: &mut String) { + let deps_parsed = parse_deps(&deps); + let dep_root = &deps_parsed[0]; + + let mut parsed_crates: Vec<&String> = Vec::new(); + let mut lastest_dep_map: HashMap = HashMap::new(); + let mut idx: usize = 1; + + lastest_dep_map.insert(0, &dep_root.1); + while idx < deps_parsed.len() { + let (level, name) = deps_parsed.get(idx).unwrap(); + if !is_arceos_crate(&name) { + idx += 1; + continue; + } + *result += &format!("{} -> {}\n", lastest_dep_map[&(level - 1)], name); + if parsed_crates.contains(&name) { + let mut skip_idx: usize = idx + 1; + if skip_idx >= deps_parsed.len() { + break; + } + while deps_parsed.get(skip_idx).unwrap().0 > *level { + idx += 1; + skip_idx += 1; + } + idx += 1; + } else { + parsed_crates.push(&name); + lastest_dep_map.insert(*level, name); + idx += 1; + } + } +} diff --git a/tools/deptool/src/lib.rs b/tools/deptool/src/lib.rs new file mode 100644 index 000000000..aa24bc2f0 --- /dev/null +++ b/tools/deptool/src/lib.rs @@ -0,0 +1,100 @@ +mod cmd_parser; +mod cmd_builder; +mod mermaid_generator; +mod d2_generator; + +use std::process::Command; +use std::fs::File; +use std::io::Write; + +use cmd_builder::build_cargo_tree_cmd; +pub use cmd_parser::{parse_cmd, build_loc}; +use d2_generator::gen_d2_script; +use mermaid_generator::gen_mermaid_script; + +#[derive(Clone, Copy, Debug)] +pub enum GraphFormat { + Mermaid, + D2, +} + +#[derive(Debug)] +pub struct Config { + pub no_default: bool, + pub format: GraphFormat, + pub features: Vec::, + loc: String, + output_loc: String +} + +impl Config { + pub fn build(no_default: bool, features: Vec::, format: GraphFormat, loc: String, output_loc: String) -> Config { + Config { no_default, format, features, loc, output_loc } + } +} + +fn get_deps_by_crate_name(cfg: &Config) -> String { + let cmd_ct = build_cargo_tree_cmd(&cfg); + let cmds = ["-c", &cmd_ct]; + let output = if cfg!(target_os = "windows") { + Command::new("cmd") + .args(cmds) + .output() + .expect("failed to execute process") + } else { + Command::new("sh") + .args(cmds) + .output() + .expect("failed to execute process") + }; + + let deps = output.stdout; + String::from_utf8(deps).unwrap() +} + +fn parse_deps(deps: &String) -> Vec<(i32, String)> { + let mut rst = vec!(); + for line in deps.lines() { + let level_name = line.split_whitespace().next().unwrap(); + let level = level_name.get(0..1).unwrap().parse().unwrap(); + let name = level_name.get(1..).unwrap(); + rst.push((level, name.to_string())); + } + rst +} + +fn generate_mermaid(config: &Config) -> String { + let mut result = String::from(""); + let deps = get_deps_by_crate_name(config); + gen_mermaid_script(&deps, &mut result); + "graph TD;\n".to_string() + &result +} + +fn generate_d2(config: &Config) -> String { + let mut result = String::from(""); + let deps = get_deps_by_crate_name(config); + gen_d2_script(&deps, &mut result); + result +} + +fn generate_deps_graph(config: &Config) -> String { + match config.format { + GraphFormat::D2 => generate_d2(config), + _ => generate_mermaid(config) + } +} + +fn output_deps_graph(rst: &String) -> std::io::Result<()> { + let mut file = File::create("output.txt")?; + file.write_all(rst.as_bytes())?; + Ok(()) +} + +pub fn run(config: &Config) { + let rst = generate_deps_graph(config); + print!("{}", rst); + match output_deps_graph(&rst) { + Ok(()) => {}, + Err(error) => println!("Error during writing file {}, {}", config.output_loc, error) + } +} diff --git a/tools/deptool/src/main.rs b/tools/deptool/src/main.rs new file mode 100644 index 000000000..b6b0fb75f --- /dev/null +++ b/tools/deptool/src/main.rs @@ -0,0 +1,11 @@ +use deptool::{run, parse_cmd}; +use std::process; + +fn main() { + let config = parse_cmd().unwrap_or_else(|err| { + eprintln!("problem parsinig arguments: {err}"); + process::exit(1); + }); + + run(&config); +} diff --git a/tools/deptool/src/mermaid_generator.rs b/tools/deptool/src/mermaid_generator.rs new file mode 100644 index 000000000..bb43552b2 --- /dev/null +++ b/tools/deptool/src/mermaid_generator.rs @@ -0,0 +1,37 @@ +use std::collections::HashMap; + +use crate::{parse_deps, cmd_parser::is_arceos_crate}; + +pub fn gen_mermaid_script(deps: &String, result: &mut String) { + let deps_parsed = parse_deps(&deps); + let dep_root = &deps_parsed[0]; + + let mut parsed_crates: Vec<&String> = Vec::new(); + let mut lastest_dep_map: HashMap = HashMap::new(); + let mut idx: usize = 1; + + lastest_dep_map.insert(0, &dep_root.1); + while idx < deps_parsed.len() { + let (level, name) = deps_parsed.get(idx).unwrap(); + if !is_arceos_crate(&name) { + idx += 1; + continue; + } + *result += &format!("{}-->{}\n", lastest_dep_map[&(level - 1)], name); + if parsed_crates.contains(&name) { + let mut skip_idx: usize = idx + 1; + if skip_idx >= deps_parsed.len() { + break; + } + while deps_parsed.get(skip_idx).unwrap().0 > *level { + idx += 1; + skip_idx += 1; + } + idx += 1; + } else { + parsed_crates.push(&name); + lastest_dep_map.insert(*level, name); + idx += 1; + } + } +} diff --git a/tools/raspi4/common/docker.mk b/tools/raspi4/common/docker.mk new file mode 100644 index 000000000..61355768f --- /dev/null +++ b/tools/raspi4/common/docker.mk @@ -0,0 +1 @@ +DOCKER_IMAGE := rustembedded/osdev-utils:2021.12 diff --git a/tools/raspi4/common/format.mk b/tools/raspi4/common/format.mk new file mode 100644 index 000000000..e392304ad --- /dev/null +++ b/tools/raspi4/common/format.mk @@ -0,0 +1,12 @@ +define color_header + @tput setaf 6 2> /dev/null || true + @printf '\n%s\n' $(1) + @tput sgr0 2> /dev/null || true +endef + +define color_progress_prefix + @tput setaf 2 2> /dev/null || true + @tput bold 2 2> /dev/null || true + @printf '%12s ' $(1) + @tput sgr0 2> /dev/null || true +endef diff --git a/tools/raspi4/common/operating_system.mk b/tools/raspi4/common/operating_system.mk new file mode 100644 index 000000000..90172f775 --- /dev/null +++ b/tools/raspi4/common/operating_system.mk @@ -0,0 +1,9 @@ +ifeq ($(shell uname -s),Linux) + DU_ARGUMENTS = --block-size=1024 --apparent-size +else ifeq ($(shell uname -s),Darwin) + DU_ARGUMENTS = -k -A +endif + +define disk_usage_KiB + @printf '%s KiB\n' `du $(DU_ARGUMENTS) $(1) | cut -f1` +endef diff --git a/tools/raspi4/common/serial/minipush.rb b/tools/raspi4/common/serial/minipush.rb new file mode 100755 index 000000000..262ce20a0 --- /dev/null +++ b/tools/raspi4/common/serial/minipush.rb @@ -0,0 +1,131 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2020-2022 Andre Richter + +require_relative 'miniterm' +require 'ruby-progressbar' +require_relative 'minipush/progressbar_patch' +require 'timeout' + +class ProtocolError < StandardError; end + +# The main class +class MiniPush < MiniTerm + def initialize(serial_name, payload_path) + super(serial_name) + + @name_short = 'MP' # override + @payload_path = payload_path + @payload_size = nil + @payload_data = nil + end + + private + + # The three characters signaling the request token form the consecutive sequence "\x03\x03\x03". + def wait_for_payload_request + puts "[#{@name_short}] 🔌 Please power the target now" + + # Timeout for the request token starts after the first sign of life was received. + received = @target_serial.readpartial(4096) + Timeout.timeout(10) do + count = 0 + + loop do + raise ProtocolError if received.nil? + + received.chars.each do |c| + if c == "\u{3}" + count += 1 + return true if count == 3 + else + # A normal character resets token counting. + count = 0 + + print c + end + end + + received = @target_serial.readpartial(4096) + end + end + end + + def load_payload + @payload_size = File.size(@payload_path) + @payload_data = File.binread(@payload_path) + end + + def send_size + @target_serial.print([@payload_size].pack('L<')) + raise ProtocolError if @target_serial.read(2) != 'OK' + end + + def send_payload + pb = ProgressBar.create( + total: @payload_size, + format: "[#{@name_short}] ⏩ Pushing %k KiB %b🦀%i %p%% %r KiB/s %a", + rate_scale: ->(rate) { rate / 1024 }, + length: 92, + output: $stdout + ) + + # Send in 512 byte chunks. + while pb.progress < pb.total + part = @payload_data.slice(pb.progress, 512) + pb.progress += @target_serial.write(part) + end + end + + # override + def handle_reconnect(_error) + connection_reset + + puts + puts "[#{@name_short}] ⚡ " \ + "#{'Connection or protocol Error: '.light_red}" \ + "#{'Remove power and USB serial. Reinsert serial first, then power'.light_red}" + sleep(1) while serial_connected? + end + + public + + # override + def run + open_serial + wait_for_payload_request + load_payload + send_size + send_payload + terminal + rescue ConnectionError, EOFError, Errno::EIO, ProtocolError, Timeout::Error => e + handle_reconnect(e) + retry + rescue StandardError => e + handle_unexpected(e) + ensure + connection_reset + puts + puts "[#{@name_short}] Bye 👋" + end +end + +##-------------------------------------------------------------------------------------------------- +## Execution starts here +##-------------------------------------------------------------------------------------------------- +if __FILE__ == $PROGRAM_NAME + puts + puts 'Minipush 1.0'.cyan + puts + + # CTRL + C handler. Only here to suppress Ruby's default exception print. + trap('INT') do + # The `ensure` block from `MiniPush::run` will run after exit, restoring console state. + exit + end + + MiniPush.new(ARGV[0], ARGV[1]).run +end diff --git a/tools/raspi4/common/serial/minipush/progressbar_patch.rb b/tools/raspi4/common/serial/minipush/progressbar_patch.rb new file mode 100644 index 000000000..1862a234c --- /dev/null +++ b/tools/raspi4/common/serial/minipush/progressbar_patch.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2020-2022 Andre Richter + +# Monkey-patch ruby-progressbar so that it supports reporting the progress in KiB instead of Byte. + +class ProgressBar + # Add kibi version of progress + class Progress + def progress_kibi + @progress / 1024 + end + end + + module Format + # Add new formatting option + class Molecule + MOLECULES_EXTENDED = MOLECULES.dup + MOLECULES_EXTENDED[:k] = %i[progressable progress_kibi] + + def initialize(letter) + self.key = letter + self.method_name = MOLECULES_EXTENDED.fetch(key.to_sym) + end + end + end +end diff --git a/tools/raspi4/common/serial/miniterm.rb b/tools/raspi4/common/serial/miniterm.rb new file mode 100755 index 000000000..038ed978f --- /dev/null +++ b/tools/raspi4/common/serial/miniterm.rb @@ -0,0 +1,144 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# SPDX-License-Identifier: MIT OR Apache-2.0 +# +# Copyright (c) 2020-2022 Andre Richter + +require 'rubygems' +require 'bundler/setup' + +require 'colorize' +require 'io/console' +require 'serialport' + +SERIAL_BAUD = 921_600 + +class ConnectionError < StandardError; end + +# The main class +class MiniTerm + def initialize(serial_name) + @name_short = 'MT' + @target_serial_name = serial_name + @target_serial = nil + @host_console = IO.console + end + + private + + def serial_connected? + File.exist?(@target_serial_name) + end + + def wait_for_serial + return if serial_connected? + + puts "[#{@name_short}] ⏳ Waiting for #{@target_serial_name}" + loop do + sleep(1) + break if serial_connected? + end + end + + def open_serial + wait_for_serial + + @target_serial = SerialPort.new(@target_serial_name, SERIAL_BAUD, 8, 1, SerialPort::NONE) + + # Ensure all output is immediately flushed to the device. + @target_serial.sync = true + rescue Errno::EACCES => e + puts "[#{@name_short}] 🚫 #{e.message} - Maybe try with 'sudo'" + exit + else + puts "[#{@name_short}] ✅ Serial connected" + end + + def terminal + @host_console.raw! + + Thread.abort_on_exception = true + Thread.report_on_exception = false + + # Receive from target and print on host console. + target_to_host = Thread.new do + loop do + char = @target_serial.getc + + raise ConnectionError if char.nil? + + # Translate incoming newline to newline + carriage return. + @host_console.putc("\r") if char == "\n" + @host_console.putc(char) + end + end + + # Transmit host console input to target. + loop do + c = @host_console.getc + + # CTRL + C in raw mode was pressed. + if c == "\u{3}" + target_to_host.kill + break + end + + @target_serial.putc(c) + end + end + + def connection_reset + @target_serial&.close + @target_serial = nil + @host_console.cooked! + end + + # When the serial lost power or was removed during R/W operation. + def handle_reconnect(_error) + connection_reset + + puts + puts "[#{@name_short}] ⚡ #{'Connection Error: Reinsert the USB serial again'.light_red}" + end + + def handle_unexpected(error) + connection_reset + + puts + puts "[#{@name_short}] ⚡ #{"Unexpected Error: #{error.inspect}".light_red}" + end + + public + + def run + open_serial + terminal + rescue ConnectionError, EOFError, Errno::EIO => e + handle_reconnect(e) + retry + rescue StandardError => e + handle_unexpected(e) + ensure + connection_reset + puts + puts "[#{@name_short}] Bye 👋" + end +end + +##-------------------------------------------------------------------------------------------------- +## Execution starts here +##-------------------------------------------------------------------------------------------------- +if __FILE__ == $PROGRAM_NAME + puts + puts 'Miniterm 1.0'.cyan + puts + + # CTRL + C handler. Only here to suppress Ruby's default exception print. + trap('INT') do + # The `ensure` block from `MiniTerm::run` will run after exit, restoring console state. + exit + end + + MiniTerm.new(ARGV[0]).run +end diff --git a/ulib/axlibc/.gitignore b/ulib/axlibc/.gitignore new file mode 100644 index 000000000..b5b452628 --- /dev/null +++ b/ulib/axlibc/.gitignore @@ -0,0 +1,3 @@ +src/libctypes_gen.rs +include/ax_pthread_mutex.h +build_* diff --git a/ulib/axlibc/Cargo.toml b/ulib/axlibc/Cargo.toml new file mode 100644 index 000000000..703e79e01 --- /dev/null +++ b/ulib/axlibc/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "axlibc" +version = "0.1.0" +edition = "2021" +authors = [ + "Yuekai Jia ", + "yanjuguang ", + "wudashuai ", + "yfblock <321353225@qq.com>", + "scPointer ", + "Shiping Yuan ", +] +description = "ArceOS user program library for C apps" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/ulib/axlibc" +documentation = "https://rcore-os.github.io/arceos/axlibc/index.html" + +[lib] +crate-type = ["staticlib"] + +[features] +default = [] + +# Multicore +smp = ["arceos_posix_api/smp"] + +# Floating point/SIMD +fp_simd = ["axfeat/fp_simd"] + +# Memory +alloc = ["arceos_posix_api/alloc"] +tls = ["alloc", "axfeat/tls"] + +# Multi-task +multitask = ["arceos_posix_api/multitask"] + +# File system +fs = ["arceos_posix_api/fs", "fd"] + +# Networking +net = ["arceos_posix_api/net", "fd"] + +# Libc features +fd = [] +pipe = ["arceos_posix_api/pipe"] +select = ["arceos_posix_api/select"] +epoll = ["arceos_posix_api/epoll"] + +[dependencies] +axfeat = { path = "../../api/axfeat" } +arceos_posix_api = { path = "../../api/arceos_posix_api" } +axio = { path = "../../crates/axio" } +axerrno = { path = "../../crates/axerrno" } + +[build-dependencies] +bindgen ={ version = "0.66" } diff --git a/ulib/axlibc/build.rs b/ulib/axlibc/build.rs new file mode 100644 index 000000000..6c33c929c --- /dev/null +++ b/ulib/axlibc/build.rs @@ -0,0 +1,24 @@ +fn main() { + fn gen_c_to_rust_bindings(in_file: &str, out_file: &str) { + println!("cargo:rerun-if-changed={in_file}"); + + let allow_types = ["tm", "jmp_buf"]; + let mut builder = bindgen::Builder::default() + .header(in_file) + .clang_arg("-I./include") + .derive_default(true) + .size_t_is_usize(false) + .use_core(); + for ty in allow_types { + builder = builder.allowlist_type(ty); + } + + builder + .generate() + .expect("Unable to generate c->rust bindings") + .write_to_file(out_file) + .expect("Couldn't write bindings!"); + } + + gen_c_to_rust_bindings("ctypes.h", "src/libctypes_gen.rs"); +} diff --git a/ulib/axlibc/c/assert.c b/ulib/axlibc/c/assert.c new file mode 100644 index 000000000..ee071677d --- /dev/null +++ b/ulib/axlibc/c/assert.c @@ -0,0 +1,8 @@ +#include +#include + +_Noreturn void __assert_fail(const char *expr, const char *file, int line, const char *func) +{ + fprintf(stderr, "Assertion failed: %s (%s: %s: %d)\n", expr, file, func, line); + abort(); +} diff --git a/ulib/axlibc/c/ctype.c b/ulib/axlibc/c/ctype.c new file mode 100644 index 000000000..08e8e5e07 --- /dev/null +++ b/ulib/axlibc/c/ctype.c @@ -0,0 +1,17 @@ +#include +#include +#include + +int tolower(int c) +{ + if (isupper(c)) + return c | 32; + return c; +} + +int toupper(int c) +{ + if (islower(c)) + return c & 0x5f; + return c; +} diff --git a/ulib/axlibc/c/dirent.c b/ulib/axlibc/c/dirent.c new file mode 100644 index 000000000..a69094a7d --- /dev/null +++ b/ulib/axlibc/c/dirent.c @@ -0,0 +1,98 @@ +#ifdef AX_CONFIG_FS + +#include +#include +#include +#include +#include +#include +#include +#include + +int closedir(DIR *dir) +{ + int ret = close(dir->fd); + free(dir); + return ret; +} + +DIR *fdopendir(int fd) +{ + DIR *dir; + struct stat st; + + if (fstat(fd, &st) < 0) { + return 0; + } + if (fcntl(fd, F_GETFL) & O_PATH) { + errno = EBADF; + return 0; + } + if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + return 0; + } + if (!(dir = calloc(1, sizeof(*dir)))) { + return 0; + } + + fcntl(fd, F_SETFD, FD_CLOEXEC); + dir->fd = fd; + return dir; +} + +int dirfd(DIR *d) +{ + return d->fd; +} + +// TODO +DIR *opendir(const char *__name) +{ + unimplemented(); + return NULL; +} + +// TODO +struct dirent *readdir(DIR *__dirp) +{ + unimplemented(); + return NULL; +} + +// TODO +int readdir_r(DIR *restrict dir, struct dirent *restrict buf, struct dirent **restrict result) +{ + struct dirent *de; + int errno_save = errno; + int ret; + + // LOCK(dir->lock); + errno = 0; + de = readdir(dir); + if ((ret = errno)) { + // UNLOCK(dir->lock); + return ret; + } + errno = errno_save; + if (de) + memcpy(buf, de, de->d_reclen); + else + buf = NULL; + + // UNLOCK(dir->lock); + *result = buf; + return 0; +} + +// TODO +void rewinddir(DIR *dir) +{ + // LOCK(dir->lock); + lseek(dir->fd, 0, SEEK_SET); + dir->buf_pos = dir->buf_end = 0; + dir->tell = 0; + // UNLOCK(dir->lock); +} + +#endif // AX_CONFIG_FS diff --git a/ulib/axlibc/c/dlfcn.c b/ulib/axlibc/c/dlfcn.c new file mode 100644 index 000000000..a539e7d0c --- /dev/null +++ b/ulib/axlibc/c/dlfcn.c @@ -0,0 +1,39 @@ +#include +#include +#include + +// TODO +int dladdr(const void *__address, Dl_info *__info) +{ + unimplemented(); + return 0; +} + +// TODO +void *dlopen(const char *__file, int __mode) +{ + unimplemented(); + return NULL; +} + +// TODO +char *dlerror() +{ + unimplemented(); + return NULL; +} + +// TODO +void *dlsym(void *__restrict__ __handle, const char *__restrict__ __name) +{ + + unimplemented(); + return NULL; +} + +// TODO +int dlclose(void *p) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/env.c b/ulib/axlibc/c/env.c new file mode 100644 index 000000000..a0505a9ee --- /dev/null +++ b/ulib/axlibc/c/env.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include + +char *environ_[2] = {"dummy", NULL}; +char **environ = (char **)environ_; + +char *getenv(const char *name) +{ + size_t l = strchrnul(name, '=') - name; + if (l && !name[l] && environ) + for (char **e = environ; *e; e++) + if (!strncmp(name, *e, l) && l[*e] == '=') + return *e + l + 1; + return 0; +} + +// TODO +int setenv(const char *__name, const char *__value, int __replace) +{ + unimplemented(); + return 0; +} + +// TODO +int unsetenv(const char *__name) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/fcntl.c b/ulib/axlibc/c/fcntl.c new file mode 100644 index 000000000..fa9091713 --- /dev/null +++ b/ulib/axlibc/c/fcntl.c @@ -0,0 +1,56 @@ +#include +#include +#include + +#ifdef AX_CONFIG_FD + +// TODO: remove this function in future work +int ax_fcntl(int fd, int cmd, size_t arg); + +int fcntl(int fd, int cmd, ... /* arg */) +{ + unsigned long arg; + va_list ap; + va_start(ap, cmd); + arg = va_arg(ap, unsigned long); + va_end(ap); + + return ax_fcntl(fd, cmd, arg); +} + +#endif // AX_CONFIG_FD + +#ifdef AX_CONFIG_FS + +// TODO: remove this function in future work +int ax_open(const char *filename, int flags, mode_t mode); + +int open(const char *filename, int flags, ...) +{ + mode_t mode = 0; + + if ((flags & O_CREAT) || (flags & O_TMPFILE) == O_TMPFILE) { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, mode_t); + va_end(ap); + } + + return ax_open(filename, flags, mode); +} + +// TODO +int posix_fadvise(int __fd, unsigned long __offset, unsigned long __len, int __advise) +{ + unimplemented(); + return 0; +} + +// TODO +int sync_file_range(int fd, off_t pos, off_t len, unsigned flags) +{ + unimplemented(); + return 0; +} + +#endif // AX_CONFIG_FS diff --git a/ulib/axlibc/c/flock.c b/ulib/axlibc/c/flock.c new file mode 100644 index 000000000..8ac9beec5 --- /dev/null +++ b/ulib/axlibc/c/flock.c @@ -0,0 +1,9 @@ +#include +#include + +// TODO +int flock(int __fd, int __operation) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/fnmatch.c b/ulib/axlibc/c/fnmatch.c new file mode 100644 index 000000000..3895fa2c8 --- /dev/null +++ b/ulib/axlibc/c/fnmatch.c @@ -0,0 +1,15 @@ +#include +#include + +#define END 0 +#define UNMATCHABLE -2 +#define BRACKET -3 +#define QUESTION -4 +#define STAR -5 + +// TODO +int fnmatch(const char *pat, const char *str, int flags) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/glob.c b/ulib/axlibc/c/glob.c new file mode 100644 index 000000000..841a226dd --- /dev/null +++ b/ulib/axlibc/c/glob.c @@ -0,0 +1,329 @@ +#ifdef AX_CONFIG_FS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct match { + struct match *next; + char name[]; +}; + +static int append(struct match **tail, const char *name, size_t len, int mark) +{ + struct match *new = malloc(sizeof(struct match) + len + 2); + if (!new) + return -1; + (*tail)->next = new; + new->next = NULL; + memcpy(new->name, name, len + 1); + if (mark && len && name[len - 1] != '/') { + new->name[len] = '/'; + new->name[len + 1] = 0; + } + *tail = new; + return 0; +} + +static int do_glob(char *buf, size_t pos, int type, char *pat, int flags, + int (*errfunc)(const char *path, int err), struct match **tail) +{ + /* If GLOB_MARK is unused, we don't care about type. */ + if (!type && !(flags & GLOB_MARK)) + type = DT_REG; + + /* Special-case the remaining pattern being all slashes, in + * which case we can use caller-passed type if it's a dir. */ + if (*pat && type != DT_DIR) + type = 0; + while (pos + 1 < PATH_MAX && *pat == '/') buf[pos++] = *pat++; + + /* Consume maximal [escaped-]literal prefix of pattern, copying + * and un-escaping it to the running buffer as we go. */ + long i = 0, j = 0; + int in_bracket = 0, overflow = 0; + for (; pat[i] != '*' && pat[i] != '?' && (!in_bracket || pat[i] != ']'); i++) { + if (!pat[i]) { + if (overflow) + return 0; + pat += i; + pos += j; + i = j = 0; + break; + } else if (pat[i] == '[') { + in_bracket = 1; + } else if (pat[i] == '\\' && !(flags & GLOB_NOESCAPE)) { + /* Backslashes inside a bracket are (at least by + * our interpretation) non-special, so if next + * char is ']' we have a complete expression. */ + if (in_bracket && pat[i + 1] == ']') + break; + /* Unpaired final backslash never matches. */ + if (!pat[i + 1]) + return 0; + i++; + } + if (pat[i] == '/') { + if (overflow) + return 0; + in_bracket = 0; + pat += i + 1; + i = -1; + pos += j + 1; + j = -1; + } + /* Only store a character if it fits in the buffer, but if + * a potential bracket expression is open, the overflow + * must be remembered and handled later only if the bracket + * is unterminated (and thereby a literal), so as not to + * disallow long bracket expressions with short matches. */ + if (pos + (j + 1) < PATH_MAX) { + buf[pos + j++] = pat[i]; + } else if (in_bracket) { + overflow = 1; + } else { + return 0; + } + /* If we consume any new components, the caller-passed type + * or dummy type from above is no longer valid. */ + type = 0; + } + buf[pos] = 0; + if (!*pat) { + /* If we consumed any components above, or if GLOB_MARK is + * requested and we don't yet know if the match is a dir, + * we must confirm the file exists and/or determine its type. + * + * If marking dirs, symlink type is inconclusive; we need the + * type for the symlink target, and therefore must try stat + * first unless type is known not to be a symlink. Otherwise, + * or if that fails, use lstat for determining existence to + * avoid false negatives in the case of broken symlinks. */ + struct stat st; + if ((flags & GLOB_MARK) && (!type || type == DT_LNK) && !stat(buf, &st)) { + if (S_ISDIR(st.st_mode)) + type = DT_DIR; + else + type = DT_REG; + } + if (!type && lstat(buf, &st)) { + if (errno != ENOENT && (errfunc(buf, errno) || (flags & GLOB_ERR))) + return GLOB_ABORTED; + return 0; + } + if (append(tail, buf, pos, (flags & GLOB_MARK) && type == DT_DIR)) + return GLOB_NOSPACE; + return 0; + } + char *p2 = strchr(pat, '/'), saved_sep = '/'; + /* Check if the '/' was escaped and, if so, remove the escape char + * so that it will not be unpaired when passed to fnmatch. */ + if (p2 && !(flags & GLOB_NOESCAPE)) { + char *p; + for (p = p2; p > pat && p[-1] == '\\'; p--) + ; + if ((p2 - p) % 2) { + p2--; + saved_sep = '\\'; + } + } + DIR *dir = opendir(pos ? buf : "."); + if (!dir) { + if (errfunc(buf, errno) || (flags & GLOB_ERR)) + return GLOB_ABORTED; + return 0; + } + int old_errno = errno; + struct dirent *de; + while (errno = 0, de = readdir(dir)) { + /* Quickly skip non-directories when there's pattern left. */ + if (p2 && de->d_type && de->d_type != DT_DIR && de->d_type != DT_LNK) + continue; + + size_t l = strlen(de->d_name); + if (l >= PATH_MAX - pos) + continue; + + if (p2) + *p2 = 0; + + int fnm_flags = ((flags & GLOB_NOESCAPE) ? FNM_NOESCAPE : 0) | + ((!(flags & GLOB_PERIOD)) ? FNM_PERIOD : 0); + + if (fnmatch(pat, de->d_name, fnm_flags)) + continue; + + /* With GLOB_PERIOD, don't allow matching . or .. unless + * fnmatch would match them with FNM_PERIOD rules in effect. */ + if (p2 && (flags & GLOB_PERIOD) && de->d_name[0] == '.' && + (!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2])) && + fnmatch(pat, de->d_name, fnm_flags | FNM_PERIOD)) + continue; + + memcpy(buf + pos, de->d_name, l + 1); + if (p2) + *p2 = saved_sep; + int r = do_glob(buf, pos + l, de->d_type, p2 ? p2 : "", flags, errfunc, tail); + if (r) { + closedir(dir); + return r; + } + } + int readerr = errno; + if (p2) + *p2 = saved_sep; + closedir(dir); + if (readerr && (errfunc(buf, errno) || (flags & GLOB_ERR))) + return GLOB_ABORTED; + errno = old_errno; + return 0; +} + +static int ignore_err(const char *path, int err) +{ + return 0; +} + +static void freelist(struct match *head) +{ + struct match *match, *next; + for (match = head->next; match; match = next) { + next = match->next; + free(match); + } +} + +static int sort(const void *a, const void *b) +{ + return strcmp(*(const char **)a, *(const char **)b); +} + +static int expand_tilde(char **pat, char *buf, size_t *pos) +{ + char *p = *pat + 1; + size_t i = 0; + + char delim, *name_end = strchrnul(p, '/'); + if ((delim = *name_end)) + *name_end++ = 0; + *pat = name_end; + + char *home = *p ? NULL : getenv("HOME"); + if (!home) { + struct passwd pw, *res; + switch (*p ? getpwnam_r(p, &pw, buf, PATH_MAX, &res) + : getpwuid_r(getuid(), &pw, buf, PATH_MAX, &res)) { + case ENOMEM: + return GLOB_NOSPACE; + case 0: + if (!res) + default: + return GLOB_NOMATCH; + } + home = pw.pw_dir; + } + while (i < PATH_MAX - 2 && *home) buf[i++] = *home++; + if (*home) + return GLOB_NOMATCH; + if ((buf[i] = delim)) + buf[++i] = 0; + *pos = i; + return 0; +} + +int glob(const char *restrict pat, int flags, int (*errfunc)(const char *path, int err), + glob_t *restrict g) +{ + struct match head = {.next = NULL}, *tail = &head; + size_t cnt, i; + size_t offs = (flags & GLOB_DOOFFS) ? g->gl_offs : 0; + int error = 0; + char buf[PATH_MAX]; + + if (!errfunc) + errfunc = ignore_err; + + if (!(flags & GLOB_APPEND)) { + g->gl_offs = offs; + g->gl_pathc = 0; + g->gl_pathv = NULL; + } + + if (*pat) { + char *p = strdup(pat); + if (!p) + return GLOB_NOSPACE; + buf[0] = 0; + size_t pos = 0; + char *s = p; + if ((flags & (GLOB_TILDE | GLOB_TILDE_CHECK)) && *p == '~') + error = expand_tilde(&s, buf, &pos); + if (!error) + error = do_glob(buf, pos, 0, s, flags, errfunc, &tail); + free(p); + } + + if (error == GLOB_NOSPACE) { + freelist(&head); + return error; + } + + for (cnt = 0, tail = head.next; tail; tail = tail->next, cnt++) + ; + if (!cnt) { + if (flags & GLOB_NOCHECK) { + tail = &head; + if (append(&tail, pat, strlen(pat), 0)) + return GLOB_NOSPACE; + cnt++; + } else + return GLOB_NOMATCH; + } + + if (flags & GLOB_APPEND) { + char **pathv = realloc(g->gl_pathv, (offs + g->gl_pathc + cnt + 1) * sizeof(char *)); + if (!pathv) { + freelist(&head); + return GLOB_NOSPACE; + } + g->gl_pathv = pathv; + offs += g->gl_pathc; + } else { + g->gl_pathv = malloc((offs + cnt + 1) * sizeof(char *)); + if (!g->gl_pathv) { + freelist(&head); + return GLOB_NOSPACE; + } + for (i = 0; i < offs; i++) g->gl_pathv[i] = NULL; + } + for (i = 0, tail = head.next; i < cnt; tail = tail->next, i++) + g->gl_pathv[offs + i] = tail->name; + g->gl_pathv[offs + i] = NULL; + g->gl_pathc += cnt; + + if (!(flags & GLOB_NOSORT)) + qsort(g->gl_pathv + offs, cnt, sizeof(char *), sort); + + return error; +} + +void globfree(glob_t *g) +{ + size_t i; + for (i = 0; i < g->gl_pathc; i++) + free(g->gl_pathv[g->gl_offs + i] - offsetof(struct match, name)); + free(g->gl_pathv); + g->gl_pathc = 0; + g->gl_pathv = NULL; +} + +#endif // AX_CONFIG_FS diff --git a/ulib/axlibc/c/ioctl.c b/ulib/axlibc/c/ioctl.c new file mode 100644 index 000000000..df2aad167 --- /dev/null +++ b/ulib/axlibc/c/ioctl.c @@ -0,0 +1,9 @@ +#include +#include + +// TODO +int ioctl(int __fd, int __request, ...) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/libgen.c b/ulib/axlibc/c/libgen.c new file mode 100644 index 000000000..fbce88154 --- /dev/null +++ b/ulib/axlibc/c/libgen.c @@ -0,0 +1,33 @@ +#include +#include + +char *dirname(char *s) +{ + size_t i; + if (!s || !*s) + return "."; + i = strlen(s) - 1; + for (; s[i] == '/'; i--) + if (!i) + return "/"; + for (; s[i] != '/'; i--) + if (!i) + return "."; + for (; s[i] == '/'; i--) + if (!i) + return "/"; + s[i + 1] = 0; + return s; +} + +char *basename(char *s) +{ + size_t i; + if (!s || !*s) + return "."; + i = strlen(s) - 1; + for (; i && s[i] == '/'; i--) s[i] = 0; + for (; i && s[i - 1] != '/'; i--) + ; + return s + i; +} diff --git a/ulib/axlibc/c/libm.c b/ulib/axlibc/c/libm.c new file mode 100644 index 000000000..c8420112e --- /dev/null +++ b/ulib/axlibc/c/libm.c @@ -0,0 +1,17 @@ +#ifdef AX_CONFIG_FP_SIMD + +#include + +#include "libm.h" + +double __math_divzero(uint32_t sign) +{ + return fp_barrier(sign ? -1.0 : 1.0) / 0.0; +} + +double __math_invalid(double x) +{ + return (x - x) / (x - x); +} + +#endif // AX_CONFIG_FP_SIMD diff --git a/ulib/axlibc/c/libm.h b/ulib/axlibc/c/libm.h new file mode 100644 index 000000000..ac9130d7b --- /dev/null +++ b/ulib/axlibc/c/libm.h @@ -0,0 +1,233 @@ +#ifndef _LIBM_H +#define _LIBM_H + +#if AX_CONFIG_FP_SIMD + +#include +#include +#include + +#if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 +#elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __LITTLE_ENDIAN +union ldshape { + long double f; + struct { + uint64_t m; + uint16_t se; + } i; +}; +#elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __BIG_ENDIAN +/* This is the m68k variant of 80-bit long double, and this definition only works + * on archs where the alignment requirement of uint64_t is <= 4. */ +union ldshape { + long double f; + struct { + uint16_t se; + uint16_t pad; + uint64_t m; + } i; +}; +#elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __LITTLE_ENDIAN +union ldshape { + long double f; + struct { + uint64_t lo; + uint32_t mid; + uint16_t top; + uint16_t se; + } i; + struct { + uint64_t lo; + uint64_t hi; + } i2; +}; +#elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384 && __BYTE_ORDER == __BIG_ENDIAN +union ldshape { + long double f; + struct { + uint16_t se; + uint16_t top; + uint32_t mid; + uint64_t lo; + } i; + struct { + uint64_t hi; + uint64_t lo; + } i2; +}; +#else +#error Unsupported long double representation +#endif + +/* Support non-nearest rounding mode. */ +#define WANT_ROUNDING 1 +/* Support signaling NaNs. */ +#define WANT_SNAN 0 + +#if WANT_SNAN +#error SNaN is unsupported +#else +#define issignalingf_inline(x) 0 +#define issignaling_inline(x) 0 +#endif + +#ifndef TOINT_INTRINSICS +#define TOINT_INTRINSICS 0 +#endif + +#if TOINT_INTRINSICS +/* Round x to nearest int in all rounding modes, ties have to be rounded + consistently with converttoint so the results match. If the result + would be outside of [-2^31, 2^31-1] then the semantics is unspecified. */ +static double_t roundtoint(double_t); + +/* Convert x to nearest int in all rounding modes, ties have to be rounded + consistently with roundtoint. If the result is not representible in an + int32_t then the semantics is unspecified. */ +static int32_t converttoint(double_t); +#endif + +/* Helps static branch prediction so hot path can be better optimized. */ +#ifdef __GNUC__ +#define predict_true(x) __builtin_expect(!!(x), 1) +#define predict_false(x) __builtin_expect(x, 0) +#else +#define predict_true(x) (x) +#define predict_false(x) (x) +#endif + +/* Evaluate an expression as the specified type. With standard excess + precision handling a type cast or assignment is enough (with + -ffloat-store an assignment is required, in old compilers argument + passing and return statement may not drop excess precision). */ + +static inline float eval_as_float(float x) +{ + float y = x; + return y; +} + +static inline double eval_as_double(double x) +{ + double y = x; + return y; +} + +/* fp_barrier returns its input, but limits code transformations + as if it had a side-effect (e.g. observable io) and returned + an arbitrary value. */ + +#ifndef fp_barrierf +#define fp_barrierf fp_barrierf +static inline float fp_barrierf(float x) +{ + volatile float y = x; + return y; +} +#endif + +#ifndef fp_barrier +#define fp_barrier fp_barrier +static inline double fp_barrier(double x) +{ + volatile double y = x; + return y; +} +#endif + +#ifndef fp_barrierl +#define fp_barrierl fp_barrierl +static inline long double fp_barrierl(long double x) +{ + volatile long double y = x; + return y; +} +#endif + +/* fp_force_eval ensures that the input value is computed when that's + otherwise unused. To prevent the constant folding of the input + expression, an additional fp_barrier may be needed or a compilation + mode that does so (e.g. -frounding-math in gcc). Then it can be + used to evaluate an expression for its fenv side-effects only. */ + +#ifndef fp_force_evalf +#define fp_force_evalf fp_force_evalf +static inline void fp_force_evalf(float x) +{ + volatile float y; + y = x; +} +#endif + +#ifndef fp_force_eval +#define fp_force_eval fp_force_eval +static inline void fp_force_eval(double x) +{ + volatile double y; + y = x; +} +#endif + +#ifndef fp_force_evall +#define fp_force_evall fp_force_evall +static inline void fp_force_evall(long double x) +{ + volatile long double y; + y = x; +} +#endif + +#define FORCE_EVAL(x) \ + do { \ + if (sizeof(x) == sizeof(float)) { \ + fp_force_evalf(x); \ + } else if (sizeof(x) == sizeof(double)) { \ + fp_force_eval(x); \ + } else { \ + fp_force_evall(x); \ + } \ + } while (0) + +#define asuint(f) \ + ((union { \ + float _f; \ + uint32_t _i; \ + }){f}) \ + ._i +#define asfloat(i) \ + ((union { \ + uint32_t _i; \ + float _f; \ + }){i}) \ + ._f +#define asuint64(f) \ + ((union { \ + double _f; \ + uint64_t _i; \ + }){f}) \ + ._i +#define asdouble(i) \ + ((union { \ + uint64_t _i; \ + double _f; \ + }){i}) \ + ._f + +/* error handling functions */ +float __math_xflowf(uint32_t, float); +float __math_uflowf(uint32_t); +float __math_oflowf(uint32_t); +float __math_divzerof(uint32_t); +float __math_invalidf(float); +double __math_xflow(uint32_t, double); +double __math_uflow(uint32_t); +double __math_oflow(uint32_t); +double __math_divzero(uint32_t); +double __math_invalid(double); +#if LDBL_MANT_DIG != DBL_MANT_DIG +long double __math_invalidl(long double); +#endif + +#endif // AX_CONFIG_FP_SIMD + +#endif // _LIBM_H diff --git a/ulib/axlibc/c/locale.c b/ulib/axlibc/c/locale.c new file mode 100644 index 000000000..9c535ac3d --- /dev/null +++ b/ulib/axlibc/c/locale.c @@ -0,0 +1,42 @@ +#include +#include +#include + +// TODO +char *setlocale(int __category, const char *__locale) +{ + unimplemented(); + return NULL; +} + +static const struct lconv posix_lconv = { + .decimal_point = ".", + .thousands_sep = "", + .grouping = "", + .int_curr_symbol = "", + .currency_symbol = "", + .mon_decimal_point = "", + .mon_thousands_sep = "", + .mon_grouping = "", + .positive_sign = "", + .negative_sign = "", + .int_frac_digits = CHAR_MAX, + .frac_digits = CHAR_MAX, + .p_cs_precedes = CHAR_MAX, + .p_sep_by_space = CHAR_MAX, + .n_cs_precedes = CHAR_MAX, + .n_sep_by_space = CHAR_MAX, + .p_sign_posn = CHAR_MAX, + .n_sign_posn = CHAR_MAX, + .int_p_cs_precedes = CHAR_MAX, + .int_p_sep_by_space = CHAR_MAX, + .int_n_cs_precedes = CHAR_MAX, + .int_n_sep_by_space = CHAR_MAX, + .int_p_sign_posn = CHAR_MAX, + .int_n_sign_posn = CHAR_MAX, +}; + +struct lconv *localeconv(void) +{ + return (void *)&posix_lconv; +} diff --git a/ulib/axlibc/c/log.c b/ulib/axlibc/c/log.c new file mode 100644 index 000000000..4e34aa0ac --- /dev/null +++ b/ulib/axlibc/c/log.c @@ -0,0 +1,395 @@ +#ifdef AX_CONFIG_FP_SIMD + +#include +#include +#include +#include + +#include "libm.h" + +struct log_data { + double ln2hi; + double ln2lo; + double poly[LOG_POLY_ORDER - 1]; + double poly1[LOG_POLY1_ORDER - 1]; + struct { + double invc, logc; + } tab[1 << LOG_TABLE_BITS]; +#if !__FP_FAST_FMA + struct { + double chi, clo; + } tab2[1 << LOG_TABLE_BITS]; +#endif +}; + +const struct log_data __log_data = { + .ln2hi = 0x1.62e42fefa3800p-1, + .ln2lo = 0x1.ef35793c76730p-45, + .poly1 = + { + -0x1p-1, + 0x1.5555555555577p-2, + -0x1.ffffffffffdcbp-3, + 0x1.999999995dd0cp-3, + -0x1.55555556745a7p-3, + 0x1.24924a344de3p-3, + -0x1.fffffa4423d65p-4, + 0x1.c7184282ad6cap-4, + -0x1.999eb43b068ffp-4, + 0x1.78182f7afd085p-4, + -0x1.5521375d145cdp-4, + }, + .poly = + { + -0x1.0000000000001p-1, + 0x1.555555551305bp-2, + -0x1.fffffffeb459p-3, + 0x1.999b324f10111p-3, + -0x1.55575e506c89fp-3, + }, + .tab = + { + {0x1.734f0c3e0de9fp+0, -0x1.7cc7f79e69000p-2}, + {0x1.713786a2ce91fp+0, -0x1.76feec20d0000p-2}, + {0x1.6f26008fab5a0p+0, -0x1.713e31351e000p-2}, + {0x1.6d1a61f138c7dp+0, -0x1.6b85b38287800p-2}, + {0x1.6b1490bc5b4d1p+0, -0x1.65d5590807800p-2}, + {0x1.69147332f0cbap+0, -0x1.602d076180000p-2}, + {0x1.6719f18224223p+0, -0x1.5a8ca86909000p-2}, + {0x1.6524f99a51ed9p+0, -0x1.54f4356035000p-2}, + {0x1.63356aa8f24c4p+0, -0x1.4f637c36b4000p-2}, + {0x1.614b36b9ddc14p+0, -0x1.49da7fda85000p-2}, + {0x1.5f66452c65c4cp+0, -0x1.445923989a800p-2}, + {0x1.5d867b5912c4fp+0, -0x1.3edf439b0b800p-2}, + {0x1.5babccb5b90dep+0, -0x1.396ce448f7000p-2}, + {0x1.59d61f2d91a78p+0, -0x1.3401e17bda000p-2}, + {0x1.5805612465687p+0, -0x1.2e9e2ef468000p-2}, + {0x1.56397cee76bd3p+0, -0x1.2941b3830e000p-2}, + {0x1.54725e2a77f93p+0, -0x1.23ec58cda8800p-2}, + {0x1.52aff42064583p+0, -0x1.1e9e129279000p-2}, + {0x1.50f22dbb2bddfp+0, -0x1.1956d2b48f800p-2}, + {0x1.4f38f4734ded7p+0, -0x1.141679ab9f800p-2}, + {0x1.4d843cfde2840p+0, -0x1.0edd094ef9800p-2}, + {0x1.4bd3ec078a3c8p+0, -0x1.09aa518db1000p-2}, + {0x1.4a27fc3e0258ap+0, -0x1.047e65263b800p-2}, + {0x1.4880524d48434p+0, -0x1.feb224586f000p-3}, + {0x1.46dce1b192d0bp+0, -0x1.f474a7517b000p-3}, + {0x1.453d9d3391854p+0, -0x1.ea4443d103000p-3}, + {0x1.43a2744b4845ap+0, -0x1.e020d44e9b000p-3}, + {0x1.420b54115f8fbp+0, -0x1.d60a22977f000p-3}, + {0x1.40782da3ef4b1p+0, -0x1.cc00104959000p-3}, + {0x1.3ee8f5d57fe8fp+0, -0x1.c202956891000p-3}, + {0x1.3d5d9a00b4ce9p+0, -0x1.b81178d811000p-3}, + {0x1.3bd60c010c12bp+0, -0x1.ae2c9ccd3d000p-3}, + {0x1.3a5242b75dab8p+0, -0x1.a45402e129000p-3}, + {0x1.38d22cd9fd002p+0, -0x1.9a877681df000p-3}, + {0x1.3755bc5847a1cp+0, -0x1.90c6d69483000p-3}, + {0x1.35dce49ad36e2p+0, -0x1.87120a645c000p-3}, + {0x1.34679984dd440p+0, -0x1.7d68fb4143000p-3}, + {0x1.32f5cceffcb24p+0, -0x1.73cb83c627000p-3}, + {0x1.3187775a10d49p+0, -0x1.6a39a9b376000p-3}, + {0x1.301c8373e3990p+0, -0x1.60b3154b7a000p-3}, + {0x1.2eb4ebb95f841p+0, -0x1.5737d76243000p-3}, + {0x1.2d50a0219a9d1p+0, -0x1.4dc7b8fc23000p-3}, + {0x1.2bef9a8b7fd2ap+0, -0x1.4462c51d20000p-3}, + {0x1.2a91c7a0c1babp+0, -0x1.3b08abc830000p-3}, + {0x1.293726014b530p+0, -0x1.31b996b490000p-3}, + {0x1.27dfa5757a1f5p+0, -0x1.2875490a44000p-3}, + {0x1.268b39b1d3bbfp+0, -0x1.1f3b9f879a000p-3}, + {0x1.2539d838ff5bdp+0, -0x1.160c8252ca000p-3}, + {0x1.23eb7aac9083bp+0, -0x1.0ce7f57f72000p-3}, + {0x1.22a012ba940b6p+0, -0x1.03cdc49fea000p-3}, + {0x1.2157996cc4132p+0, -0x1.f57bdbc4b8000p-4}, + {0x1.201201dd2fc9bp+0, -0x1.e370896404000p-4}, + {0x1.1ecf4494d480bp+0, -0x1.d17983ef94000p-4}, + {0x1.1d8f5528f6569p+0, -0x1.bf9674ed8a000p-4}, + {0x1.1c52311577e7cp+0, -0x1.adc79202f6000p-4}, + {0x1.1b17c74cb26e9p+0, -0x1.9c0c3e7288000p-4}, + {0x1.19e010c2c1ab6p+0, -0x1.8a646b372c000p-4}, + {0x1.18ab07bb670bdp+0, -0x1.78d01b3ac0000p-4}, + {0x1.1778a25efbcb6p+0, -0x1.674f145380000p-4}, + {0x1.1648d354c31dap+0, -0x1.55e0e6d878000p-4}, + {0x1.151b990275fddp+0, -0x1.4485cdea1e000p-4}, + {0x1.13f0ea432d24cp+0, -0x1.333d94d6aa000p-4}, + {0x1.12c8b7210f9dap+0, -0x1.22079f8c56000p-4}, + {0x1.11a3028ecb531p+0, -0x1.10e4698622000p-4}, + {0x1.107fbda8434afp+0, -0x1.ffa6c6ad20000p-5}, + {0x1.0f5ee0f4e6bb3p+0, -0x1.dda8d4a774000p-5}, + {0x1.0e4065d2a9fcep+0, -0x1.bbcece4850000p-5}, + {0x1.0d244632ca521p+0, -0x1.9a1894012c000p-5}, + {0x1.0c0a77ce2981ap+0, -0x1.788583302c000p-5}, + {0x1.0af2f83c636d1p+0, -0x1.5715e67d68000p-5}, + {0x1.09ddb98a01339p+0, -0x1.35c8a49658000p-5}, + {0x1.08cabaf52e7dfp+0, -0x1.149e364154000p-5}, + {0x1.07b9f2f4e28fbp+0, -0x1.e72c082eb8000p-6}, + {0x1.06ab58c358f19p+0, -0x1.a55f152528000p-6}, + {0x1.059eea5ecf92cp+0, -0x1.63d62cf818000p-6}, + {0x1.04949cdd12c90p+0, -0x1.228fb8caa0000p-6}, + {0x1.038c6c6f0ada9p+0, -0x1.c317b20f90000p-7}, + {0x1.02865137932a9p+0, -0x1.419355daa0000p-7}, + {0x1.0182427ea7348p+0, -0x1.81203c2ec0000p-8}, + {0x1.008040614b195p+0, -0x1.0040979240000p-9}, + {0x1.fe01ff726fa1ap-1, 0x1.feff384900000p-9}, + {0x1.fa11cc261ea74p-1, 0x1.7dc41353d0000p-7}, + {0x1.f6310b081992ep-1, 0x1.3cea3c4c28000p-6}, + {0x1.f25f63ceeadcdp-1, 0x1.b9fc114890000p-6}, + {0x1.ee9c8039113e7p-1, 0x1.1b0d8ce110000p-5}, + {0x1.eae8078cbb1abp-1, 0x1.58a5bd001c000p-5}, + {0x1.e741aa29d0c9bp-1, 0x1.95c8340d88000p-5}, + {0x1.e3a91830a99b5p-1, 0x1.d276aef578000p-5}, + {0x1.e01e009609a56p-1, 0x1.07598e598c000p-4}, + {0x1.dca01e577bb98p-1, 0x1.253f5e30d2000p-4}, + {0x1.d92f20b7c9103p-1, 0x1.42edd8b380000p-4}, + {0x1.d5cac66fb5ccep-1, 0x1.606598757c000p-4}, + {0x1.d272caa5ede9dp-1, 0x1.7da76356a0000p-4}, + {0x1.cf26e3e6b2ccdp-1, 0x1.9ab434e1c6000p-4}, + {0x1.cbe6da2a77902p-1, 0x1.b78c7bb0d6000p-4}, + {0x1.c8b266d37086dp-1, 0x1.d431332e72000p-4}, + {0x1.c5894bd5d5804p-1, 0x1.f0a3171de6000p-4}, + {0x1.c26b533bb9f8cp-1, 0x1.067152b914000p-3}, + {0x1.bf583eeece73fp-1, 0x1.147858292b000p-3}, + {0x1.bc4fd75db96c1p-1, 0x1.2266ecdca3000p-3}, + {0x1.b951e0c864a28p-1, 0x1.303d7a6c55000p-3}, + {0x1.b65e2c5ef3e2cp-1, 0x1.3dfc33c331000p-3}, + {0x1.b374867c9888bp-1, 0x1.4ba366b7a8000p-3}, + {0x1.b094b211d304ap-1, 0x1.5933928d1f000p-3}, + {0x1.adbe885f2ef7ep-1, 0x1.66acd2418f000p-3}, + {0x1.aaf1d31603da2p-1, 0x1.740f8ec669000p-3}, + {0x1.a82e63fd358a7p-1, 0x1.815c0f51af000p-3}, + {0x1.a5740ef09738bp-1, 0x1.8e92954f68000p-3}, + {0x1.a2c2a90ab4b27p-1, 0x1.9bb3602f84000p-3}, + {0x1.a01a01393f2d1p-1, 0x1.a8bed1c2c0000p-3}, + {0x1.9d79f24db3c1bp-1, 0x1.b5b515c01d000p-3}, + {0x1.9ae2505c7b190p-1, 0x1.c2967ccbcc000p-3}, + {0x1.9852ef297ce2fp-1, 0x1.cf635d5486000p-3}, + {0x1.95cbaeea44b75p-1, 0x1.dc1bd3446c000p-3}, + {0x1.934c69de74838p-1, 0x1.e8c01b8cfe000p-3}, + {0x1.90d4f2f6752e6p-1, 0x1.f5509c0179000p-3}, + {0x1.8e6528effd79dp-1, 0x1.00e6c121fb800p-2}, + {0x1.8bfce9fcc007cp-1, 0x1.071b80e93d000p-2}, + {0x1.899c0dabec30ep-1, 0x1.0d46b9e867000p-2}, + {0x1.87427aa2317fbp-1, 0x1.13687334bd000p-2}, + {0x1.84f00acb39a08p-1, 0x1.1980d67234800p-2}, + {0x1.82a49e8653e55p-1, 0x1.1f8ffe0cc8000p-2}, + {0x1.8060195f40260p-1, 0x1.2595fd7636800p-2}, + {0x1.7e22563e0a329p-1, 0x1.2b9300914a800p-2}, + {0x1.7beb377dcb5adp-1, 0x1.3187210436000p-2}, + {0x1.79baa679725c2p-1, 0x1.377266dec1800p-2}, + {0x1.77907f2170657p-1, 0x1.3d54ffbaf3000p-2}, + {0x1.756cadbd6130cp-1, 0x1.432eee32fe000p-2}, + }, +#if !__FP_FAST_FMA + .tab2 = + { + {0x1.61000014fb66bp-1, 0x1.e026c91425b3cp-56}, + {0x1.63000034db495p-1, 0x1.dbfea48005d41p-55}, + {0x1.650000d94d478p-1, 0x1.e7fa786d6a5b7p-55}, + {0x1.67000074e6fadp-1, 0x1.1fcea6b54254cp-57}, + {0x1.68ffffedf0faep-1, -0x1.c7e274c590efdp-56}, + {0x1.6b0000763c5bcp-1, -0x1.ac16848dcda01p-55}, + {0x1.6d0001e5cc1f6p-1, 0x1.33f1c9d499311p-55}, + {0x1.6efffeb05f63ep-1, -0x1.e80041ae22d53p-56}, + {0x1.710000e86978p-1, 0x1.bff6671097952p-56}, + {0x1.72ffffc67e912p-1, 0x1.c00e226bd8724p-55}, + {0x1.74fffdf81116ap-1, -0x1.e02916ef101d2p-57}, + {0x1.770000f679c9p-1, -0x1.7fc71cd549c74p-57}, + {0x1.78ffffa7ec835p-1, 0x1.1bec19ef50483p-55}, + {0x1.7affffe20c2e6p-1, -0x1.07e1729cc6465p-56}, + {0x1.7cfffed3fc9p-1, -0x1.08072087b8b1cp-55}, + {0x1.7efffe9261a76p-1, 0x1.dc0286d9df9aep-55}, + {0x1.81000049ca3e8p-1, 0x1.97fd251e54c33p-55}, + {0x1.8300017932c8fp-1, -0x1.afee9b630f381p-55}, + {0x1.850000633739cp-1, 0x1.9bfbf6b6535bcp-55}, + {0x1.87000204289c6p-1, -0x1.bbf65f3117b75p-55}, + {0x1.88fffebf57904p-1, -0x1.9006ea23dcb57p-55}, + {0x1.8b00022bc04dfp-1, -0x1.d00df38e04b0ap-56}, + {0x1.8cfffe50c1b8ap-1, -0x1.8007146ff9f05p-55}, + {0x1.8effffc918e43p-1, 0x1.3817bd07a7038p-55}, + {0x1.910001efa5fc7p-1, 0x1.93e9176dfb403p-55}, + {0x1.9300013467bb9p-1, 0x1.f804e4b980276p-56}, + {0x1.94fffe6ee076fp-1, -0x1.f7ef0d9ff622ep-55}, + {0x1.96fffde3c12d1p-1, -0x1.082aa962638bap-56}, + {0x1.98ffff4458a0dp-1, -0x1.7801b9164a8efp-55}, + {0x1.9afffdd982e3ep-1, -0x1.740e08a5a9337p-55}, + {0x1.9cfffed49fb66p-1, 0x1.fce08c19bep-60}, + {0x1.9f00020f19c51p-1, -0x1.a3faa27885b0ap-55}, + {0x1.a10001145b006p-1, 0x1.4ff489958da56p-56}, + {0x1.a300007bbf6fap-1, 0x1.cbeab8a2b6d18p-55}, + {0x1.a500010971d79p-1, 0x1.8fecadd78793p-55}, + {0x1.a70001df52e48p-1, -0x1.f41763dd8abdbp-55}, + {0x1.a90001c593352p-1, -0x1.ebf0284c27612p-55}, + {0x1.ab0002a4f3e4bp-1, -0x1.9fd043cff3f5fp-57}, + {0x1.acfffd7ae1ed1p-1, -0x1.23ee7129070b4p-55}, + {0x1.aefffee510478p-1, 0x1.a063ee00edea3p-57}, + {0x1.b0fffdb650d5bp-1, 0x1.a06c8381f0ab9p-58}, + {0x1.b2ffffeaaca57p-1, -0x1.9011e74233c1dp-56}, + {0x1.b4fffd995badcp-1, -0x1.9ff1068862a9fp-56}, + {0x1.b7000249e659cp-1, 0x1.aff45d0864f3ep-55}, + {0x1.b8ffff987164p-1, 0x1.cfe7796c2c3f9p-56}, + {0x1.bafffd204cb4fp-1, -0x1.3ff27eef22bc4p-57}, + {0x1.bcfffd2415c45p-1, -0x1.cffb7ee3bea21p-57}, + {0x1.beffff86309dfp-1, -0x1.14103972e0b5cp-55}, + {0x1.c0fffe1b57653p-1, 0x1.bc16494b76a19p-55}, + {0x1.c2ffff1fa57e3p-1, -0x1.4feef8d30c6edp-57}, + {0x1.c4fffdcbfe424p-1, -0x1.43f68bcec4775p-55}, + {0x1.c6fffed54b9f7p-1, 0x1.47ea3f053e0ecp-55}, + {0x1.c8fffeb998fd5p-1, 0x1.383068df992f1p-56}, + {0x1.cb0002125219ap-1, -0x1.8fd8e64180e04p-57}, + {0x1.ccfffdd94469cp-1, 0x1.e7ebe1cc7ea72p-55}, + {0x1.cefffeafdc476p-1, 0x1.ebe39ad9f88fep-55}, + {0x1.d1000169af82bp-1, 0x1.57d91a8b95a71p-56}, + {0x1.d30000d0ff71dp-1, 0x1.9c1906970c7dap-55}, + {0x1.d4fffea790fc4p-1, -0x1.80e37c558fe0cp-58}, + {0x1.d70002edc87e5p-1, -0x1.f80d64dc10f44p-56}, + {0x1.d900021dc82aap-1, -0x1.47c8f94fd5c5cp-56}, + {0x1.dafffd86b0283p-1, 0x1.c7f1dc521617ep-55}, + {0x1.dd000296c4739p-1, 0x1.8019eb2ffb153p-55}, + {0x1.defffe54490f5p-1, 0x1.e00d2c652cc89p-57}, + {0x1.e0fffcdabf694p-1, -0x1.f8340202d69d2p-56}, + {0x1.e2fffdb52c8ddp-1, 0x1.b00c1ca1b0864p-56}, + {0x1.e4ffff24216efp-1, 0x1.2ffa8b094ab51p-56}, + {0x1.e6fffe88a5e11p-1, -0x1.7f673b1efbe59p-58}, + {0x1.e9000119eff0dp-1, -0x1.4808d5e0bc801p-55}, + {0x1.eafffdfa51744p-1, 0x1.80006d54320b5p-56}, + {0x1.ed0001a127fa1p-1, -0x1.002f860565c92p-58}, + {0x1.ef00007babcc4p-1, -0x1.540445d35e611p-55}, + {0x1.f0ffff57a8d02p-1, -0x1.ffb3139ef9105p-59}, + {0x1.f30001ee58ac7p-1, 0x1.a81acf2731155p-55}, + {0x1.f4ffff5823494p-1, 0x1.a3f41d4d7c743p-55}, + {0x1.f6ffffca94c6bp-1, -0x1.202f41c987875p-57}, + {0x1.f8fffe1f9c441p-1, 0x1.77dd1f477e74bp-56}, + {0x1.fafffd2e0e37ep-1, -0x1.f01199a7ca331p-57}, + {0x1.fd0001c77e49ep-1, 0x1.181ee4bceacb1p-56}, + {0x1.feffff7e0c331p-1, -0x1.e05370170875ap-57}, + {0x1.00ffff465606ep+0, -0x1.a7ead491c0adap-55}, + {0x1.02ffff3867a58p+0, -0x1.77f69c3fcb2ep-54}, + {0x1.04ffffdfc0d17p+0, 0x1.7bffe34cb945bp-54}, + {0x1.0700003cd4d82p+0, 0x1.20083c0e456cbp-55}, + {0x1.08ffff9f2cbe8p+0, -0x1.dffdfbe37751ap-57}, + {0x1.0b000010cda65p+0, -0x1.13f7faee626ebp-54}, + {0x1.0d00001a4d338p+0, 0x1.07dfa79489ff7p-55}, + {0x1.0effffadafdfdp+0, -0x1.7040570d66bcp-56}, + {0x1.110000bbafd96p+0, 0x1.e80d4846d0b62p-55}, + {0x1.12ffffae5f45dp+0, 0x1.dbffa64fd36efp-54}, + {0x1.150000dd59ad9p+0, 0x1.a0077701250aep-54}, + {0x1.170000f21559ap+0, 0x1.dfdf9e2e3deeep-55}, + {0x1.18ffffc275426p+0, 0x1.10030dc3b7273p-54}, + {0x1.1b000123d3c59p+0, 0x1.97f7980030188p-54}, + {0x1.1cffff8299eb7p+0, -0x1.5f932ab9f8c67p-57}, + {0x1.1effff48ad4p+0, 0x1.37fbf9da75bebp-54}, + {0x1.210000c8b86a4p+0, 0x1.f806b91fd5b22p-54}, + {0x1.2300003854303p+0, 0x1.3ffc2eb9fbf33p-54}, + {0x1.24fffffbcf684p+0, 0x1.601e77e2e2e72p-56}, + {0x1.26ffff52921d9p+0, 0x1.ffcbb767f0c61p-56}, + {0x1.2900014933a3cp+0, -0x1.202ca3c02412bp-56}, + {0x1.2b00014556313p+0, -0x1.2808233f21f02p-54}, + {0x1.2cfffebfe523bp+0, -0x1.8ff7e384fdcf2p-55}, + {0x1.2f0000bb8ad96p+0, -0x1.5ff51503041c5p-55}, + {0x1.30ffffb7ae2afp+0, -0x1.10071885e289dp-55}, + {0x1.32ffffeac5f7fp+0, -0x1.1ff5d3fb7b715p-54}, + {0x1.350000ca66756p+0, 0x1.57f82228b82bdp-54}, + {0x1.3700011fbf721p+0, 0x1.000bac40dd5ccp-55}, + {0x1.38ffff9592fb9p+0, -0x1.43f9d2db2a751p-54}, + {0x1.3b00004ddd242p+0, 0x1.57f6b707638e1p-55}, + {0x1.3cffff5b2c957p+0, 0x1.a023a10bf1231p-56}, + {0x1.3efffeab0b418p+0, 0x1.87f6d66b152bp-54}, + {0x1.410001532aff4p+0, 0x1.7f8375f198524p-57}, + {0x1.4300017478b29p+0, 0x1.301e672dc5143p-55}, + {0x1.44fffe795b463p+0, 0x1.9ff69b8b2895ap-55}, + {0x1.46fffe80475ep+0, -0x1.5c0b19bc2f254p-54}, + {0x1.48fffef6fc1e7p+0, 0x1.b4009f23a2a72p-54}, + {0x1.4afffe5bea704p+0, -0x1.4ffb7bf0d7d45p-54}, + {0x1.4d000171027dep+0, -0x1.9c06471dc6a3dp-54}, + {0x1.4f0000ff03ee2p+0, 0x1.77f890b85531cp-54}, + {0x1.5100012dc4bd1p+0, 0x1.004657166a436p-57}, + {0x1.530001605277ap+0, -0x1.6bfcece233209p-54}, + {0x1.54fffecdb704cp+0, -0x1.902720505a1d7p-55}, + {0x1.56fffef5f54a9p+0, 0x1.bbfe60ec96412p-54}, + {0x1.5900017e61012p+0, 0x1.87ec581afef9p-55}, + {0x1.5b00003c93e92p+0, -0x1.f41080abf0ccp-54}, + {0x1.5d0001d4919bcp+0, -0x1.8812afb254729p-54}, + {0x1.5efffe7b87a89p+0, -0x1.47eb780ed6904p-54}, + }, +#endif +}; + +#define T __log_data.tab +#define T2 __log_data.tab2 +#define B __log_data.poly1 +#define A __log_data.poly +#define Ln2hi __log_data.ln2hi +#define Ln2lo __log_data.ln2lo +#define N (1 << LOG_TABLE_BITS) +#define OFF 0x3fe6000000000000 + +static inline uint32_t top16(double x) +{ + return asuint64(x) >> 48; +} + +double log(double x) +{ + double_t w, z, r, r2, r3, y, invc, logc, kd, hi, lo; + uint64_t ix, iz, tmp; + uint32_t top; + int k, i; + + ix = asuint64(x); + top = top16(x); +#define LO asuint64(1.0 - 0x1p-4) +#define HI asuint64(1.0 + 0x1.09p-4) + if (predict_false(ix - LO < HI - LO)) { + if (WANT_ROUNDING && predict_false(ix == asuint64(1.0))) + return 0; + r = x - 1.0; + r2 = r * r; + r3 = r * r2; + y = r3 * + (B[1] + r * B[2] + r2 * B[3] + + r3 * (B[4] + r * B[5] + r2 * B[6] + r3 * (B[7] + r * B[8] + r2 * B[9] + r3 * B[10]))); + w = r * 0x1p27; + double_t rhi = r + w - w; + double_t rlo = r - rhi; + w = rhi * rhi * B[0]; + hi = r + w; + lo = r - hi + w; + lo += B[0] * rlo * (rhi + r); + y += lo; + y += hi; + return eval_as_double(y); + } + if (predict_false(top - 0x0010 >= 0x7ff0 - 0x0010)) { + if (ix * 2 == 0) + return __math_divzero(1); + if (ix == asuint64(INFINITY)) + return x; + if ((top & 0x8000) || (top & 0x7ff0) == 0x7ff0) + return __math_invalid(x); + ix = asuint64(x * 0x1p52); + ix -= 52ULL << 52; + } + + tmp = ix - OFF; + i = (tmp >> (52 - LOG_TABLE_BITS)) % N; + k = (int64_t)tmp >> 52; + iz = ix - (tmp & 0xfffULL << 52); + invc = T[i].invc; + logc = T[i].logc; + z = asdouble(iz); + +#if __FP_FAST_FMA + r = __builtin_fma(z, invc, -1.0); +#else + r = (z - T2[i].chi - T2[i].clo) * invc; +#endif + + kd = (double_t)k; + w = kd * Ln2hi + logc; + hi = w + r; + lo = w - hi + r + kd * Ln2lo; + r2 = r * r; + y = lo + r2 * A[0] + r * r2 * (A[1] + r * A[2] + r2 * (A[3] + r * A[4])) + hi; + return eval_as_double(y); +} + +#endif // AX_CONFIG_FP_SIMD diff --git a/ulib/axlibc/c/math.c b/ulib/axlibc/c/math.c new file mode 100644 index 000000000..04ee1e7c3 --- /dev/null +++ b/ulib/axlibc/c/math.c @@ -0,0 +1,571 @@ +#ifdef AX_CONFIG_FP_SIMD + +#include +#include +#include +#include + +#include "libm.h" + +int __fpclassify(double x) +{ + union { + double f; + uint64_t i; + } u = {x}; + int e = u.i >> 52 & 0x7ff; + if (!e) + return u.i << 1 ? FP_SUBNORMAL : FP_ZERO; + if (e == 0x7ff) + return u.i << 12 ? FP_NAN : FP_INFINITE; + return FP_NORMAL; +} + +int __fpclassifyf(float x) +{ + union { + float f; + uint32_t i; + } u = {x}; + int e = u.i >> 23 & 0xff; + if (!e) + return u.i << 1 ? FP_SUBNORMAL : FP_ZERO; + if (e == 0xff) + return u.i << 9 ? FP_NAN : FP_INFINITE; + return FP_NORMAL; +} + +#if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 +int __fpclassifyl(long double x) +{ + return __fpclassify(x); +} +#elif LDBL_MANT_DIG == 64 && LDBL_MAX_EXP == 16384 +int __fpclassifyl(long double x) +{ + union ldshape u = {x}; + int e = u.i.se & 0x7fff; + int msb = u.i.m >> 63; + if (!e && !msb) + return u.i.m ? FP_SUBNORMAL : FP_ZERO; + if (e == 0x7fff) { + /* The x86 variant of 80-bit extended precision only admits + * one representation of each infinity, with the mantissa msb + * necessarily set. The version with it clear is invalid/nan. + * The m68k variant, however, allows either, and tooling uses + * the version with it clear. */ + if (__BYTE_ORDER == __LITTLE_ENDIAN && !msb) + return FP_NAN; + return u.i.m << 1 ? FP_NAN : FP_INFINITE; + } + if (!msb) + return FP_NAN; + return FP_NORMAL; +} +#elif LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384 +int __fpclassifyl(long double x) +{ + union ldshape u = {x}; + int e = u.i.se & 0x7fff; + u.i.se = 0; + if (!e) + return u.i2.lo | u.i2.hi ? FP_SUBNORMAL : FP_ZERO; + if (e == 0x7fff) + return u.i2.lo | u.i2.hi ? FP_NAN : FP_INFINITE; + return FP_NORMAL; +} +#endif + +double fabs(double x) +{ + union { + double f; + uint64_t i; + } u = {x}; + u.i &= -1ULL / 2; + return u.f; +} + +static const double toint = 1 / DBL_EPSILON; + +double floor(double x) +{ + union { + double f; + uint64_t i; + } u = {x}; + int e = u.i >> 52 & 0x7ff; + double y; + + if (e >= 0x3ff + 52 || x == 0) + return x; + /* y = int(x) - x, where int(x) is an integer neighbor of x */ + if (u.i >> 63) + y = x - toint + toint - x; + else + y = x + toint - toint - x; + /* special case because of non-nearest rounding modes */ + if (e <= 0x3ff - 1) { + FORCE_EVAL(y); + return u.i >> 63 ? -1 : 0; + } + if (y > 0) + return x + y - 1; + return x + y; +} + +double rint(double x) +{ + unimplemented(); + return 0; +} + +long long llrint(double x) +{ + return rint(x); +} + +double sqrt(double x) +{ + unimplemented(); + return 0; +} + +double round(double x) +{ + unimplemented(); + return x; +} + +long double roundl(long double x) +{ + unimplemented(); + return x; +} + +long long llroundl(long double x) +{ + unimplemented(); + return x; +} + +double cos(double __x) +{ + unimplemented(); + return 0; +} + +double ceil(double x) +{ + union { + double f; + uint64_t i; + } u = {x}; + int e = u.i >> 52 & 0x7ff; + double_t y; + + if (e >= 0x3ff + 52 || x == 0) + return x; + if (u.i >> 63) + y = x - toint + toint - x; + else + y = x + toint - toint - x; + if (e <= 0x3ff - 1) { + FORCE_EVAL(y); + return u.i >> 63 ? -0.0 : 1; + } + if (y < 0) + return x + y + 1; + return x + y; +} + +// TODO +double sin(double __x) +{ + unimplemented(); + return 0; +} + +// TODO +double asin(double __x) +{ + unimplemented(); + return 0; +} + +long double ceill(long double x) +{ + unimplemented(); + return x; +} + +double acos(double x) +{ + unimplemented(); + return 0; +} + +// TODO +double atan(double x) +{ + unimplemented(); + return 0; +} + +// TODO +double atan2(double y, double x) +{ + unimplemented(); + return 0; +} + +double cosh(double x) +{ + unimplemented(); + return 0; +} + +// TODO +double exp(double x) +{ + unimplemented(); + return 0; +} + +// TODO +double frexp(double x, int *e) +{ + unimplemented(); + return 0; +} + +double ldexp(double x, int n) +{ + unimplemented(); + return 0; +} + +// TODO +double log10(double x) +{ + unimplemented(); + return 0; +} + +// TODO +double modf(double x, double *iptr) +{ + unimplemented(); + return 0; +} + +double sinh(double x) +{ + unimplemented(); + return 0; +} + +// TODO +double tan(double x) +{ + unimplemented(); + return 0; +} + +// TODO +double tanh(double x) +{ + unimplemented(); + return 0; +} + +double copysign(double x, double y) +{ + union { + double f; + uint64_t i; + } ux = {x}, uy = {y}; + ux.i &= -1ULL / 2; + ux.i |= uy.i & 1ULL << 63; + return ux.f; +} + +#if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 +long double copysignl(long double x, long double y) +{ + return copysign(x, y); +} +#elif (LDBL_MANT_DIG == 64 || LDBL_MANT_DIG == 113) && LDBL_MAX_EXP == 16384 +long double copysignl(long double x, long double y) +{ + union ldshape ux = {x}, uy = {y}; + ux.i.se &= 0x7fff; + ux.i.se |= uy.i.se & 0x8000; + return ux.f; +} +#endif + +double scalbn(double x, int n) +{ + union { + double f; + uint64_t i; + } u; + double_t y = x; + + if (n > 1023) { + y *= 0x1p1023; + n -= 1023; + if (n > 1023) { + y *= 0x1p1023; + n -= 1023; + if (n > 1023) + n = 1023; + } + } else if (n < -1022) { + /* make sure final n < -53 to avoid double + rounding in the subnormal range */ + y *= 0x1p-1022 * 0x1p53; + n += 1022 - 53; + if (n < -1022) { + y *= 0x1p-1022 * 0x1p53; + n += 1022 - 53; + if (n < -1022) + n = -1022; + } + } + u.i = (uint64_t)(0x3ff + n) << 52; + x = y * u.f; + return x; +} + +#if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 +long double scalbnl(long double x, int n) +{ + return scalbn(x, n); +} +#elif (LDBL_MANT_DIG == 64 || LDBL_MANT_DIG == 113) && LDBL_MAX_EXP == 16384 +long double scalbnl(long double x, int n) +{ + union ldshape u; + + if (n > 16383) { + x *= 0x1p16383L; + n -= 16383; + if (n > 16383) { + x *= 0x1p16383L; + n -= 16383; + if (n > 16383) + n = 16383; + } + } else if (n < -16382) { + x *= 0x1p-16382L * 0x1p113L; + n += 16382 - 113; + if (n < -16382) { + x *= 0x1p-16382L * 0x1p113L; + n += 16382 - 113; + if (n < -16382) + n = -16382; + } + } + u.f = 1.0; + u.i.se = 0x3fff + n; + return x * u.f; +} +#endif + +double fmod(double x, double y) +{ + union { + double f; + uint64_t i; + } ux = {x}, uy = {y}; + int ex = ux.i >> 52 & 0x7ff; + int ey = uy.i >> 52 & 0x7ff; + int sx = ux.i >> 63; + uint64_t i; + + /* in the followings uxi should be ux.i, but then gcc wrongly adds */ + /* float load/store to inner loops ruining performance and code size */ + uint64_t uxi = ux.i; + + if (uy.i << 1 == 0 || isnan(y) || ex == 0x7ff) + return (x * y) / (x * y); + if (uxi << 1 <= uy.i << 1) { + if (uxi << 1 == uy.i << 1) + return 0 * x; + return x; + } + + /* normalize x and y */ + if (!ex) { + for (i = uxi << 12; i >> 63 == 0; ex--, i <<= 1) + ; + uxi <<= -ex + 1; + } else { + uxi &= -1ULL >> 12; + uxi |= 1ULL << 52; + } + if (!ey) { + for (i = uy.i << 12; i >> 63 == 0; ey--, i <<= 1) + ; + uy.i <<= -ey + 1; + } else { + uy.i &= -1ULL >> 12; + uy.i |= 1ULL << 52; + } + + /* x mod y */ + for (; ex > ey; ex--) { + i = uxi - uy.i; + if (i >> 63 == 0) { + if (i == 0) + return 0 * x; + uxi = i; + } + uxi <<= 1; + } + i = uxi - uy.i; + if (i >> 63 == 0) { + if (i == 0) + return 0 * x; + uxi = i; + } + for (; uxi >> 52 == 0; uxi <<= 1, ex--) + ; + + /* scale result */ + if (ex > 0) { + uxi -= 1ULL << 52; + uxi |= (uint64_t)ex << 52; + } else { + uxi >>= -ex + 1; + } + uxi |= (uint64_t)sx << 63; + ux.i = uxi; + return ux.f; +} + +// x86_64 has specific implementation +#if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 +long double fmodl(long double x, long double y) +{ + return fmod(x, y); +} +#elif (LDBL_MANT_DIG == 64 || LDBL_MANT_DIG == 113) && LDBL_MAX_EXP == 16384 +long double fmodl(long double x, long double y) +{ + union ldshape ux = {x}, uy = {y}; + int ex = ux.i.se & 0x7fff; + int ey = uy.i.se & 0x7fff; + int sx = ux.i.se & 0x8000; + + if (y == 0 || isnan(y) || ex == 0x7fff) + return (x * y) / (x * y); + ux.i.se = ex; + uy.i.se = ey; + if (ux.f <= uy.f) { + if (ux.f == uy.f) + return 0 * x; + return x; + } + + /* normalize x and y */ + if (!ex) { + ux.f *= 0x1p120f; + ex = ux.i.se - 120; + } + if (!ey) { + uy.f *= 0x1p120f; + ey = uy.i.se - 120; + } + + /* x mod y */ +#if LDBL_MANT_DIG == 64 + uint64_t i, mx, my; + mx = ux.i.m; + my = uy.i.m; + for (; ex > ey; ex--) { + i = mx - my; + if (mx >= my) { + if (i == 0) + return 0 * x; + mx = 2 * i; + } else if (2 * mx < mx) { + mx = 2 * mx - my; + } else { + mx = 2 * mx; + } + } + i = mx - my; + if (mx >= my) { + if (i == 0) + return 0 * x; + mx = i; + } + for (; mx >> 63 == 0; mx *= 2, ex--) + ; + ux.i.m = mx; +#elif LDBL_MANT_DIG == 113 + uint64_t hi, lo, xhi, xlo, yhi, ylo; + xhi = (ux.i2.hi & -1ULL >> 16) | 1ULL << 48; + yhi = (uy.i2.hi & -1ULL >> 16) | 1ULL << 48; + xlo = ux.i2.lo; + ylo = uy.i2.lo; + for (; ex > ey; ex--) { + hi = xhi - yhi; + lo = xlo - ylo; + if (xlo < ylo) + hi -= 1; + if (hi >> 63 == 0) { + if ((hi | lo) == 0) + return 0 * x; + xhi = 2 * hi + (lo >> 63); + xlo = 2 * lo; + } else { + xhi = 2 * xhi + (xlo >> 63); + xlo = 2 * xlo; + } + } + hi = xhi - yhi; + lo = xlo - ylo; + if (xlo < ylo) + hi -= 1; + if (hi >> 63 == 0) { + if ((hi | lo) == 0) + return 0 * x; + xhi = hi; + xlo = lo; + } + for (; xhi >> 48 == 0; xhi = 2 * xhi + (xlo >> 63), xlo = 2 * xlo, ex--) + ; + ux.i2.hi = xhi; + ux.i2.lo = xlo; +#endif + + /* scale result */ + if (ex <= 0) { + ux.i.se = (ex + 120) | sx; + ux.f *= 0x1p-120f; + } else + ux.i.se = ex | sx; + return ux.f; +} +#endif + +#if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 +long double fabsl(long double x) +{ + return fabs(x); +} +#elif (LDBL_MANT_DIG == 64 || LDBL_MANT_DIG == 113) && LDBL_MAX_EXP == 16384 +long double fabsl(long double x) +{ + union ldshape u = {x}; + + u.i.se &= 0x7fff; + return u.f; +} +#endif + +#endif // AX_CONFIG_FP_SIMD diff --git a/ulib/axlibc/c/mmap.c b/ulib/axlibc/c/mmap.c new file mode 100644 index 000000000..e3204d714 --- /dev/null +++ b/ulib/axlibc/c/mmap.c @@ -0,0 +1,39 @@ +#include +#include +#include + +// TODO: +void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off) +{ + unimplemented(); + return MAP_FAILED; +} + +// TODO: +int munmap(void *addr, size_t length) +{ + unimplemented(); + return 0; +} + +// TODO: +void *mremap(void *old_address, size_t old_size, size_t new_size, int flags, + ... /* void *new_address */) +{ + unimplemented(); + return NULL; +} + +// TODO +int mprotect(void *addr, size_t len, int prot) +{ + unimplemented(); + return 0; +} + +// TODO +int madvise(void *addr, size_t len, int advice) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/network.c b/ulib/axlibc/c/network.c new file mode 100644 index 000000000..4c6beb845 --- /dev/null +++ b/ulib/axlibc/c/network.c @@ -0,0 +1,226 @@ +#ifdef AX_CONFIG_NET + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +int h_errno; + +static const char gai_msgs[] = "Invalid flags\0" + "Name does not resolve\0" + "Try again\0" + "Non-recoverable error\0" + "Unknown error\0" + "Unrecognized address family or invalid length\0" + "Unrecognized socket type\0" + "Unrecognized service\0" + "Unknown error\0" + "Out of memory\0" + "System error\0" + "Overflow\0" + "\0Unknown error"; + +const char *gai_strerror(int ecode) +{ + const char *s; + for (s = gai_msgs, ecode++; ecode && *s; ecode++, s++) + for (; *s; s++) + ; + if (!*s) + s++; + return s; +} + +static const char msgs[] = "Host not found\0" + "Try again\0" + "Non-recoverable error\0" + "Address not available\0" + "\0Unknown error"; + +const char *hstrerror(int ecode) +{ + const char *s; + for (s = msgs, ecode--; ecode && *s; ecode--, s++) + for (; *s; s++) + ; + if (!*s) + s++; + return s; +} + +static __inline uint16_t __bswap_16(uint16_t __x) +{ + return __x << 8 | __x >> 8; +} + +static __inline uint32_t __bswap_32(uint32_t __x) +{ + return __x >> 24 | (__x >> 8 & 0xff00) | (__x << 8 & 0xff0000) | __x << 24; +} + +uint32_t htonl(uint32_t n) +{ + union { + int i; + char c; + } u = {1}; + return u.c ? __bswap_32(n) : n; +} + +uint16_t htons(uint16_t n) +{ + union { + int i; + char c; + } u = {1}; + return u.c ? __bswap_16(n) : n; +} + +uint32_t ntohl(uint32_t n) +{ + union { + int i; + char c; + } u = {1}; + return u.c ? __bswap_32(n) : n; +} + +uint16_t ntohs(uint16_t n) +{ + union { + int i; + char c; + } u = {1}; + return u.c ? __bswap_16(n) : n; +} + +static int hexval(unsigned c) +{ + if (c - '0' < 10) + return c - '0'; + c |= 32; + if (c - 'a' < 6) + return c - 'a' + 10; + return -1; +} + +int inet_pton(int af, const char *__restrict s, void *__restrict a0) +{ + uint16_t ip[8]; + unsigned char *a = a0; + int i, j, v, d, brk = -1, need_v4 = 0; + + if (af == AF_INET) { + for (i = 0; i < 4; i++) { + for (v = j = 0; j < 3 && isdigit(s[j]); j++) v = 10 * v + s[j] - '0'; + if (j == 0 || (j > 1 && s[0] == '0') || v > 255) + return 0; + a[i] = v; + if (s[j] == 0 && i == 3) + return 1; + if (s[j] != '.') + return 0; + s += j + 1; + } + return 0; + } else if (af != AF_INET6) { + errno = EAFNOSUPPORT; + return -1; + } + + if (*s == ':' && *++s != ':') + return 0; + + for (i = 0;; i++) { + if (s[0] == ':' && brk < 0) { + brk = i; + ip[i & 7] = 0; + if (!*++s) + break; + if (i == 7) + return 0; + continue; + } + for (v = j = 0; j < 4 && (d = hexval(s[j])) >= 0; j++) v = 16 * v + d; + if (j == 0) + return 0; + ip[i & 7] = v; + if (!s[j] && (brk >= 0 || i == 7)) + break; + if (i == 7) + return 0; + if (s[j] != ':') { + if (s[j] != '.' || (i < 6 && brk < 0)) + return 0; + need_v4 = 1; + i++; + break; + } + s += j + 1; + } + if (brk >= 0) { + for (j = 0; j < 7 - i; j++) ip[brk + j] = 0; + memmove(ip + brk + 7 - i, ip + brk, 2 * (i + 1 - brk)); + } + for (j = 0; j < 8; j++) { + *a++ = ip[j] >> 8; + *a++ = ip[j]; + } + if (need_v4 && inet_pton(AF_INET, (void *)s, a - 4) <= 0) + return 0; + return 1; +} + +const char *inet_ntop(int af, const void *__restrict a0, char *__restrict s, socklen_t l) +{ + const unsigned char *a = a0; + int i, j, max, best; + char buf[100]; + + switch (af) { + case AF_INET: + if (snprintf(s, l, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]) < l) + return s; + break; + case AF_INET6: + if (memcmp(a, "\0\0\0\0\0\0\0\0\0\0\377\377", 12)) + snprintf(buf, sizeof buf, "%x:%x:%x:%x:%x:%x:%x:%x", 256 * a[0] + a[1], + 256 * a[2] + a[3], 256 * a[4] + a[5], 256 * a[6] + a[7], 256 * a[8] + a[9], + 256 * a[10] + a[11], 256 * a[12] + a[13], 256 * a[14] + a[15]); + else + snprintf(buf, sizeof buf, "%x:%x:%x:%x:%x:%x:%d.%d.%d.%d", 256 * a[0] + a[1], + 256 * a[2] + a[3], 256 * a[4] + a[5], 256 * a[6] + a[7], 256 * a[8] + a[9], + 256 * a[10] + a[11], a[12], a[13], a[14], a[15]); + /* Replace longest /(^0|:)[:0]{2,}/ with "::" */ + for (i = best = 0, max = 2; buf[i]; i++) { + if (i && buf[i] != ':') + continue; + j = strspn(buf + i, ":0"); + if (j > max) + best = i, max = j; + } + if (max > 3) { + buf[best] = buf[best + 1] = ':'; + memmove(buf + best + 2, buf + best + max, i - best - max + 1); + } + if (strlen(buf) < l) { + strcpy(s, buf); + return s; + } + break; + default: + errno = EAFNOSUPPORT; + return 0; + } + errno = ENOSPC; + return 0; +} + +#endif // AX_CONFIG_NET diff --git a/ulib/axlibc/c/poll.c b/ulib/axlibc/c/poll.c new file mode 100644 index 000000000..5474246f3 --- /dev/null +++ b/ulib/axlibc/c/poll.c @@ -0,0 +1,9 @@ +#include +#include + +// TODO +int poll(struct pollfd *__fds, nfds_t __nfds, int __timeout) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/pow.c b/ulib/axlibc/c/pow.c new file mode 100644 index 000000000..217201176 --- /dev/null +++ b/ulib/axlibc/c/pow.c @@ -0,0 +1,815 @@ +#if defined(AX_CONFIG_FP_SIMD) + +#include +#include +#include +#include +#include + +#include "libm.h" + +#define OFF 0x3fe6955500000000 + +#define POW_LOG_TABLE_BITS 7 +#define POW_LOG_POLY_ORDER 8 +#define N (1 << POW_LOG_TABLE_BITS) +struct pow_log_data { + double ln2hi; + double ln2lo; + double poly[POW_LOG_POLY_ORDER - 1]; /* First coefficient is 1. */ + /* Note: the pad field is unused, but allows slightly faster indexing. */ + struct { + double invc, pad, logc, logctail; + } tab[1 << POW_LOG_TABLE_BITS]; +}; + +const struct + pow_log_data + __pow_log_data = + { + .ln2hi = 0x1.62e42fefa3800p-1, + .ln2lo = 0x1.ef35793c76730p-45, + .poly = + { + -0x1p-1, + 0x1.555555555556p-2 * -2, + -0x1.0000000000006p-2 * -2, + 0x1.999999959554ep-3 * 4, + -0x1.555555529a47ap-3 * 4, + 0x1.2495b9b4845e9p-3 * -8, + -0x1.0002b8b263fc3p-3 * -8, + }, + .tab = + { +#define A(a, b, c) {a, 0, b, c}, + A(0x1.6a00000000000p+0, -0x1.62c82f2b9c800p-2, 0x1.ab42428375680p-48) + A(0x1.6800000000000p+0, -0x1.5d1bdbf580800p-2, -0x1.ca508d8e0f720p-46) + A(0x1.6600000000000p+0, -0x1.5767717455800p-2, + -0x1.362a4d5b6506dp-45) + A(0x1.6400000000000p+0, -0x1.51aad872df800p-2, + -0x1.684e49eb067d5p-49) A(0x1.6200000000000p+0, + -0x1.4be5f95777800p-2, + -0x1.41b6993293ee0p-47) A(0x1.6000000000000p+0, -0x1.4618bc21c6000p-2, 0x1.3d82f484c84ccp-46) A(0x1.5e00000000000p+0, -0x1.404308686a800p-2, 0x1.c42f3ed820b3ap-50) A(0x1.5c00000000000p+0, -0x1.3a64c55694800p-2, 0x1.0b1c686519460p-45) A(0x1.5a00000000000p+0, -0x1.347dd9a988000p-2, 0x1.5594dd4c58092p-45) A(0x1.5800000000000p+0, -0x1.2e8e2bae12000p-2, 0x1.67b1e99b72bd8p-45) A(0x1.5600000000000p+0, + -0x1.2895a13de8800p-2, 0x1.5ca14b6cfb03fp-46) A(0x1.5600000000000p+0, + -0x1.2895a13de8800p-2, 0x1.5ca14b6cfb03fp-46) A(0x1.5400000000000p+0, + -0x1.22941fbcf7800p-2, + -0x1.65a242853da76p-46) A(0x1.5200000000000p+0, + -0x1.1c898c1699800p-2, + -0x1.fafbc68e75404p-46) A(0x1.5000000000000p+0, + -0x1.1675cababa800p-2, 0x1.f1fc63382a8f0p-46) A(0x1.4e00000000000p+0, + -0x1.1058bf9ae4800p-2, + -0x1.6a8c4fd055a66p-45) A(0x1.4c00000000000p+0, + -0x1.0a324e2739000p-2, -0x1.c6bee7ef4030ep-47) A(0x1.4a00000000000p+0, + -0x1.0402594b4d000p-2, -0x1.036b89ef42d7fp-48) A(0x1.4a00000000000p+0, + -0x1.0402594b4d000p-2, -0x1.036b89ef42d7fp-48) A(0x1.4800000000000p+0, + -0x1.fb9186d5e4000p-3, + 0x1.d572aab993c87p-47) A(0x1.4600000000000p+0, + -0x1.ef0adcbdc6000p-3, + 0x1.b26b79c86af24p-45) A(0x1.4400000000000p+0, + -0x1.e27076e2af000p-3, -0x1.72f4f543fff10p-46) A(0x1.4200000000000p+0, + -0x1.d5c216b4fc000p-3, 0x1.1ba91bbca681bp-45) A(0x1.4000000000000p+0, -0x1.c8ff7c79aa000p-3, 0x1.7794f689f8434p-45) A(0x1.4000000000000p+0, -0x1.c8ff7c79aa000p-3, 0x1.7794f689f8434p-45) A(0x1.3e00000000000p+0, + -0x1.bc286742d9000p-3, 0x1.94eb0318bb78fp-46) A(0x1.3c00000000000p+0, + -0x1.af3c94e80c000p-3, 0x1.a4e633fcd9066p-52) A(0x1.3a00000000000p+0, + -0x1.a23bc1fe2b000p-3, + -0x1.58c64dc46c1eap-45) A(0x1.3a00000000000p+0, + -0x1.a23bc1fe2b000p-3, -0x1.58c64dc46c1eap-45) A(0x1.3800000000000p+0, + -0x1.9525a9cf45000p-3, -0x1.ad1d904c1d4e3p-45) A(0x1.3600000000000p+0, + -0x1.87fa06520d000p-3, 0x1.bbdbf7fdbfa09p-45) A(0x1.3400000000000p+0, + -0x1.7ab890210e000p-3, 0x1.bdb9072534a58p-45) A(0x1.3400000000000p+0, + -0x1.7ab890210e000p-3, 0x1.bdb9072534a58p-45) A(0x1.3200000000000p+0, -0x1.6d60fe719d000p-3, -0x1.0e46aa3b2e266p-46) A(0x1.3000000000000p+0, + -0x1.5ff3070a79000p-3, -0x1.e9e439f105039p-46) A(0x1.3000000000000p+0, + -0x1.5ff3070a79000p-3, -0x1.e9e439f105039p-46) A(0x1.2e00000000000p+0, + -0x1.526e5e3a1b000p-3, -0x1.0de8b90075b8fp-45) A(0x1.2c00000000000p+0, + -0x1.44d2b6ccb8000p-3, 0x1.70cc16135783cp-46) A(0x1.2c00000000000p+0, -0x1.44d2b6ccb8000p-3, 0x1.70cc16135783cp-46) A(0x1.2a00000000000p+0, + -0x1.371fc201e9000p-3, 0x1.178864d27543ap-48) A(0x1.2800000000000p+0, + -0x1.29552f81ff000p-3, -0x1.48d301771c408p-45) A(0x1.2600000000000p+0, -0x1.1b72ad52f6000p-3, -0x1.e80a41811a396p-45) A(0x1.2600000000000p+0, -0x1.1b72ad52f6000p-3, -0x1.e80a41811a396p-45) A(0x1.2400000000000p+0, + -0x1.0d77e7cd09000p-3, + 0x1.a699688e85bf4p-47) A(0x1.2400000000000p+0, -0x1.0d77e7cd09000p-3, 0x1.a699688e85bf4p-47) A(0x1.2200000000000p+0, -0x1.fec9131dbe000p-4, -0x1.575545ca333f2p-45) A(0x1.2000000000000p+0, + -0x1.e27076e2b0000p-4, 0x1.a342c2af0003cp-45) A(0x1.2000000000000p+0, -0x1.e27076e2b0000p-4, 0x1.a342c2af0003cp-45) A(0x1.1e00000000000p+0, -0x1.c5e548f5bc000p-4, -0x1.d0c57585fbe06p-46) A(0x1.1c00000000000p+0, + -0x1.a926d3a4ae000p-4, 0x1.53935e85baac8p-45) A(0x1.1c00000000000p+0, + -0x1.a926d3a4ae000p-4, 0x1.53935e85baac8p-45) A(0x1.1a00000000000p+0, -0x1.8c345d631a000p-4, 0x1.37c294d2f5668p-46) A(0x1.1a00000000000p+0, + -0x1.8c345d631a000p-4, + 0x1.37c294d2f5668p-46) A(0x1.1800000000000p+0, -0x1.6f0d28ae56000p-4, + -0x1.69737c93373dap-45) A(0x1.1600000000000p+0, + -0x1.51b073f062000p-4, 0x1.f025b61c65e57p-46) A(0x1.1600000000000p+0, + -0x1.51b073f062000p-4, 0x1.f025b61c65e57p-46) A(0x1.1400000000000p+0, -0x1.341d7961be000p-4, 0x1.c5edaccf913dfp-45) A(0x1.1400000000000p+0, + -0x1.341d7961be000p-4, 0x1.c5edaccf913dfp-45) A(0x1.1200000000000p+0, -0x1.16536eea38000p-4, 0x1.47c5e768fa309p-46) A(0x1.1000000000000p+0, + -0x1.f0a30c0118000p-5, 0x1.d599e83368e91p-45) A(0x1.1000000000000p+0, -0x1.f0a30c0118000p-5, + 0x1.d599e83368e91p-45) A(0x1.0e00000000000p+0, + -0x1.b42dd71198000p-5, 0x1.c827ae5d6704cp-46) A(0x1.0e00000000000p+0, + -0x1.b42dd71198000p-5, 0x1.c827ae5d6704cp-46) A(0x1.0c00000000000p+0, + -0x1.77458f632c000p-5, -0x1.cfc4634f2a1eep-45) A(0x1.0c00000000000p+0, -0x1.77458f632c000p-5, -0x1.cfc4634f2a1eep-45) A(0x1.0a00000000000p+0, -0x1.39e87b9fec000p-5, 0x1.502b7f526feaap-48) A(0x1.0a00000000000p+0, + -0x1.39e87b9fec000p-5, 0x1.502b7f526feaap-48) A(0x1.0800000000000p+0, -0x1.f829b0e780000p-6, -0x1.980267c7e09e4p-45) A(0x1.0800000000000p+0, -0x1.f829b0e780000p-6, -0x1.980267c7e09e4p-45) A(0x1.0600000000000p+0, + -0x1.7b91b07d58000p-6, -0x1.88d5493faa639p-45) A(0x1.0400000000000p+0, + -0x1.fc0a8b0fc0000p-7, -0x1.f1e7cf6d3a69cp-50) A(0x1.0400000000000p+0, -0x1.fc0a8b0fc0000p-7, -0x1.f1e7cf6d3a69cp-50) A(0x1.0200000000000p+0, -0x1.fe02a6b100000p-8, -0x1.9e23f0dda40e4p-46) A(0x1.0200000000000p+0, + -0x1.fe02a6b100000p-8, + -0x1.9e23f0dda40e4p-46) A(0x1.0000000000000p+0, + 0x0.0000000000000p+0, 0x0.0000000000000p+0) A(0x1.0000000000000p+0, + 0x0.0000000000000p+0, + 0x0.0000000000000p+0) A(0x1.fc00000000000p-1, + 0x1.0101575890000p-7, -0x1.0c76b999d2be8p-46) A(0x1.f800000000000p-1, + 0x1.0205658938000p-6, -0x1.3dc5b06e2f7d2p-45) A(0x1.f400000000000p-1, + 0x1.8492528c90000p-6, + -0x1.aa0ba325a0c34p-45) A(0x1.f000000000000p-1, + 0x1.0415d89e74000p-5, + 0x1.111c05cf1d753p-47) A(0x1.ec00000000000p-1, 0x1.466aed42e0000p-5, -0x1.c167375bdfd28p-45) A(0x1.e800000000000p-1, + 0x1.894aa149fc000p-5, + -0x1.97995d05a267dp-46) A(0x1.e400000000000p-1, + 0x1.ccb73cdddc000p-5, -0x1.a68f247d82807p-46) A(0x1.e200000000000p-1, + 0x1.eea31c006c000p-5, + -0x1.e113e4fc93b7bp-47) A(0x1.de00000000000p-1, + 0x1.1973bd1466000p-4, + -0x1.5325d560d9e9bp-45) A(0x1.da00000000000p-1, 0x1.3bdf5a7d1e000p-4, 0x1.cc85ea5db4ed7p-45) A(0x1.d600000000000p-1, 0x1.5e95a4d97a000p-4, -0x1.c69063c5d1d1ep-45) A(0x1.d400000000000p-1, 0x1.700d30aeac000p-4, 0x1.c1e8da99ded32p-49) A(0x1.d000000000000p-1, + 0x1.9335e5d594000p-4, + 0x1.3115c3abd47dap-45) A(0x1.cc00000000000p-1, + 0x1.b6ac88dad6000p-4, + -0x1.390802bf768e5p-46) A(0x1.ca00000000000p-1, + 0x1.c885801bc4000p-4, + 0x1.646d1c65aacd3p-45) A(0x1.c600000000000p-1, + 0x1.ec739830a2000p-4, + -0x1.dc068afe645e0p-45) A(0x1.c400000000000p-1, + 0x1.fe89139dbe000p-4, + -0x1.534d64fa10afdp-45) A(0x1.c000000000000p-1, 0x1.1178e8227e000p-3, 0x1.1ef78ce2d07f2p-45) A(0x1.be00000000000p-1, 0x1.1aa2b7e23f000p-3, 0x1.ca78e44389934p-45) A(0x1.ba00000000000p-1, 0x1.2d1610c868000p-3, 0x1.39d6ccb81b4a1p-47) A(0x1.b800000000000p-1, 0x1.365fcb0159000p-3, 0x1.62fa8234b7289p-51) A(0x1.b400000000000p-1, 0x1.4913d8333b000p-3, 0x1.5837954fdb678p-45) A(0x1.b200000000000p-1, 0x1.527e5e4a1b000p-3, + 0x1.633e8e5697dc7p-45) A(0x1.ae00000000000p-1, + 0x1.6574ebe8c1000p-3, + 0x1.9cf8b2c3c2e78p-46) A(0x1.ac00000000000p-1, + 0x1.6f0128b757000p-3, -0x1.5118de59c21e1p-45) A(0x1.aa00000000000p-1, 0x1.7898d85445000p-3, -0x1.c661070914305p-46) A(0x1.a600000000000p-1, + 0x1.8beafeb390000p-3, -0x1.73d54aae92cd1p-47) A(0x1.a400000000000p-1, 0x1.95a5adcf70000p-3, 0x1.7f22858a0ff6fp-47) A(0x1.a000000000000p-1, 0x1.a93ed3c8ae000p-3, -0x1.8724350562169p-45) A(0x1.9e00000000000p-1, 0x1.b31d8575bd000p-3, -0x1.c358d4eace1aap-47) A(0x1.9c00000000000p-1, 0x1.bd087383be000p-3, -0x1.d4bc4595412b6p-45) A(0x1.9a00000000000p-1, 0x1.c6ffbc6f01000p-3, -0x1.1ec72c5962bd2p-48) A(0x1.9600000000000p-1, 0x1.db13db0d49000p-3, -0x1.aff2af715b035p-45) A(0x1.9400000000000p-1, + 0x1.e530effe71000p-3, + 0x1.212276041f430p-51) A(0x1.9200000000000p-1, 0x1.ef5ade4dd0000p-3, -0x1.a211565bb8e11p-51) A(0x1.9000000000000p-1, 0x1.f991c6cb3b000p-3, 0x1.bcbecca0cdf30p-46) A(0x1.8c00000000000p-1, 0x1.07138604d5800p-2, 0x1.89cdb16ed4e91p-48) A(0x1.8a00000000000p-1, + 0x1.0c42d67616000p-2, + 0x1.7188b163ceae9p-45) A(0x1.8800000000000p-1, 0x1.1178e8227e800p-2, -0x1.c210e63a5f01cp-45) A(0x1.8600000000000p-1, + 0x1.16b5ccbacf800p-2, + 0x1.b9acdf7a51681p-45) A(0x1.8400000000000p-1, + 0x1.1bf99635a6800p-2, + 0x1.ca6ed5147bdb7p-45) A(0x1.8200000000000p-1, + 0x1.214456d0eb800p-2, + 0x1.a87deba46baeap-47) A(0x1.7e00000000000p-1, + 0x1.2bef07cdc9000p-2, 0x1.a9cfa4a5004f4p-45) A(0x1.7c00000000000p-1, 0x1.314f1e1d36000p-2, -0x1.8e27ad3213cb8p-45) A(0x1.7a00000000000p-1, 0x1.36b6776be1000p-2, 0x1.16ecdb0f177c8p-46) A(0x1.7800000000000p-1, + 0x1.3c25277333000p-2, + 0x1.83b54b606bd5cp-46) A(0x1.7600000000000p-1, + 0x1.419b423d5e800p-2, + 0x1.8e436ec90e09dp-47) A(0x1.7400000000000p-1, 0x1.4718dc271c800p-2, -0x1.f27ce0967d675p-45) A(0x1.7200000000000p-1, 0x1.4c9e09e173000p-2, -0x1.e20891b0ad8a4p-45) A(0x1.7000000000000p-1, + 0x1.522ae0738a000p-2, + 0x1.ebe708164c759p-45) A(0x1.6e00000000000p-1, 0x1.57bf753c8d000p-2, 0x1.fadedee5d40efp-46) A(0x1.6c00000000000p-1, + 0x1.5d5bddf596000p-2, + -0x1.a0b2a08a465dcp-47)}, +}; + +#define T __pow_log_data.tab +#undef A +#define A __pow_log_data.poly +#define Ln2hi __pow_log_data.ln2hi +#define Ln2lo __pow_log_data.ln2lo + +/* Top 12 bits of a double (sign and exponent bits). */ +static inline uint32_t top12(double x) +{ + return asuint64(x) >> 52; +} + +/* Compute y+TAIL = log(x) where the rounded result is y and TAIL has about + additional 15 bits precision. IX is the bit representation of x, but + normalized in the subnormal range using the sign bit for the exponent. */ +static inline double_t log_inline(uint64_t ix, double_t *tail) +{ + /* double_t for better performance on targets with FLT_EVAL_METHOD==2. */ + double_t z, r, y, invc, logc, logctail, kd, hi, t1, t2, lo, lo1, lo2, p; + uint64_t iz, tmp; + int k, i; + + /* x = 2^k z; where z is in range [OFF,2*OFF) and exact. + The range is split into N subintervals. + The ith subinterval contains z and c is near its center. */ + tmp = ix - OFF; + i = (tmp >> (52 - POW_LOG_TABLE_BITS)) % N; + k = (int64_t)tmp >> 52; /* arithmetic shift */ + iz = ix - (tmp & 0xfffULL << 52); + z = asdouble(iz); + kd = (double_t)k; + + /* log(x) = k*Ln2 + log(c) + log1p(z/c-1). */ + invc = T[i].invc; + logc = T[i].logc; + logctail = T[i].logctail; + + /* Note: 1/c is j/N or j/N/2 where j is an integer in [N,2N) and + |z/c - 1| < 1/N, so r = z/c - 1 is exactly representible. */ +#if __FP_FAST_FMA + r = __builtin_fma(z, invc, -1.0); +#else + /* Split z such that rhi, rlo and rhi*rhi are exact and |rlo| <= |r|. */ + double_t zhi = asdouble((iz + (1ULL << 31)) & (-1ULL << 32)); + double_t zlo = z - zhi; + double_t rhi = zhi * invc - 1.0; + double_t rlo = zlo * invc; + r = rhi + rlo; +#endif + + /* k*Ln2 + log(c) + r. */ + t1 = kd * Ln2hi + logc; + t2 = t1 + r; + lo1 = kd * Ln2lo + logctail; + lo2 = t1 - t2 + r; + + /* Evaluation is optimized assuming superscalar pipelined execution. */ + double_t ar, ar2, ar3, lo3, lo4; + ar = A[0] * r; /* A[0] = -0.5. */ + ar2 = r * ar; + ar3 = r * ar2; + /* k*Ln2 + log(c) + r + A[0]*r*r. */ +#if __FP_FAST_FMA + hi = t2 + ar2; + lo3 = __builtin_fma(ar, r, -ar2); + lo4 = t2 - hi + ar2; +#else + double_t arhi = A[0] * rhi; + double_t arhi2 = rhi * arhi; + hi = t2 + arhi2; + lo3 = rlo * (ar + arhi); + lo4 = t2 - hi + arhi2; +#endif + /* p = log1p(r) - r - A[0]*r*r. */ + p = (ar3 * (A[1] + r * A[2] + ar2 * (A[3] + r * A[4] + ar2 * (A[5] + r * A[6])))); + lo = lo1 + lo2 + lo3 + lo4 + p; + y = hi + lo; + *tail = hi - y + lo; + return y; +} + +#undef N +#undef T +#define EXP_TABLE_BITS 7 +#define EXP_POLY_ORDER 5 +#define EXP_USE_TOINT_NARROW 0 +#define EXP2_POLY_ORDER 5 +struct exp_data { + double invln2N; + double shift; + double negln2hiN; + double negln2loN; + double poly[4]; /* Last four coefficients. */ + double exp2_shift; + double exp2_poly[EXP2_POLY_ORDER]; + uint64_t tab[2 * (1 << EXP_TABLE_BITS)]; +}; +#define N (1 << EXP_TABLE_BITS) + +const struct exp_data __exp_data = { + // N/ln2 + .invln2N = 0x1.71547652b82fep0 * N, + // -ln2/N + .negln2hiN = -0x1.62e42fefa0000p-8, + .negln2loN = -0x1.cf79abc9e3b3ap-47, +// Used for rounding when !TOINT_INTRINSICS +#if EXP_USE_TOINT_NARROW + .shift = 0x1800000000.8p0, +#else + .shift = 0x1.8p52, +#endif + // exp polynomial coefficients. + .poly = + { + // abs error: 1.555*2^-66 + // ulp error: 0.509 (0.511 without fma) + // if |x| < ln2/256+eps + // abs error if |x| < ln2/256+0x1p-15: 1.09*2^-65 + // abs error if |x| < ln2/128: 1.7145*2^-56 + 0x1.ffffffffffdbdp-2, + 0x1.555555555543cp-3, + 0x1.55555cf172b91p-5, + 0x1.1111167a4d017p-7, + }, + .exp2_shift = 0x1.8p52 / N, + // exp2 polynomial coefficients. + .exp2_poly = + { + // abs error: 1.2195*2^-65 + // ulp error: 0.507 (0.511 without fma) + // if |x| < 1/256 + // abs error if |x| < 1/128: 1.9941*2^-56 + 0x1.62e42fefa39efp-1, + 0x1.ebfbdff82c424p-3, + 0x1.c6b08d70cf4b5p-5, + 0x1.3b2abd24650ccp-7, + 0x1.5d7e09b4e3a84p-10, + }, + // 2^(k/N) ~= H[k]*(1 + T[k]) for int k in [0,N) + // tab[2*k] = asuint64(T[k]) + // tab[2*k+1] = asuint64(H[k]) - (k << 52)/N + .tab = + { + 0x0, + 0x3ff0000000000000, + 0x3c9b3b4f1a88bf6e, + 0x3feff63da9fb3335, + 0xbc7160139cd8dc5d, + 0x3fefec9a3e778061, + 0xbc905e7a108766d1, + 0x3fefe315e86e7f85, + 0x3c8cd2523567f613, + 0x3fefd9b0d3158574, + 0xbc8bce8023f98efa, + 0x3fefd06b29ddf6de, + 0x3c60f74e61e6c861, + 0x3fefc74518759bc8, + 0x3c90a3e45b33d399, + 0x3fefbe3ecac6f383, + 0x3c979aa65d837b6d, + 0x3fefb5586cf9890f, + 0x3c8eb51a92fdeffc, + 0x3fefac922b7247f7, + 0x3c3ebe3d702f9cd1, + 0x3fefa3ec32d3d1a2, + 0xbc6a033489906e0b, + 0x3fef9b66affed31b, + 0xbc9556522a2fbd0e, + 0x3fef9301d0125b51, + 0xbc5080ef8c4eea55, + 0x3fef8abdc06c31cc, + 0xbc91c923b9d5f416, + 0x3fef829aaea92de0, + 0x3c80d3e3e95c55af, + 0x3fef7a98c8a58e51, + 0xbc801b15eaa59348, + 0x3fef72b83c7d517b, + 0xbc8f1ff055de323d, + 0x3fef6af9388c8dea, + 0x3c8b898c3f1353bf, + 0x3fef635beb6fcb75, + 0xbc96d99c7611eb26, + 0x3fef5be084045cd4, + 0x3c9aecf73e3a2f60, + 0x3fef54873168b9aa, + 0xbc8fe782cb86389d, + 0x3fef4d5022fcd91d, + 0x3c8a6f4144a6c38d, + 0x3fef463b88628cd6, + 0x3c807a05b0e4047d, + 0x3fef3f49917ddc96, + 0x3c968efde3a8a894, + 0x3fef387a6e756238, + 0x3c875e18f274487d, + 0x3fef31ce4fb2a63f, + 0x3c80472b981fe7f2, + 0x3fef2b4565e27cdd, + 0xbc96b87b3f71085e, + 0x3fef24dfe1f56381, + 0x3c82f7e16d09ab31, + 0x3fef1e9df51fdee1, + 0xbc3d219b1a6fbffa, + 0x3fef187fd0dad990, + 0x3c8b3782720c0ab4, + 0x3fef1285a6e4030b, + 0x3c6e149289cecb8f, + 0x3fef0cafa93e2f56, + 0x3c834d754db0abb6, + 0x3fef06fe0a31b715, + 0x3c864201e2ac744c, + 0x3fef0170fc4cd831, + 0x3c8fdd395dd3f84a, + 0x3feefc08b26416ff, + 0xbc86a3803b8e5b04, + 0x3feef6c55f929ff1, + 0xbc924aedcc4b5068, + 0x3feef1a7373aa9cb, + 0xbc9907f81b512d8e, + 0x3feeecae6d05d866, + 0xbc71d1e83e9436d2, + 0x3feee7db34e59ff7, + 0xbc991919b3ce1b15, + 0x3feee32dc313a8e5, + 0x3c859f48a72a4c6d, + 0x3feedea64c123422, + 0xbc9312607a28698a, + 0x3feeda4504ac801c, + 0xbc58a78f4817895b, + 0x3feed60a21f72e2a, + 0xbc7c2c9b67499a1b, + 0x3feed1f5d950a897, + 0x3c4363ed60c2ac11, + 0x3feece086061892d, + 0x3c9666093b0664ef, + 0x3feeca41ed1d0057, + 0x3c6ecce1daa10379, + 0x3feec6a2b5c13cd0, + 0x3c93ff8e3f0f1230, + 0x3feec32af0d7d3de, + 0x3c7690cebb7aafb0, + 0x3feebfdad5362a27, + 0x3c931dbdeb54e077, + 0x3feebcb299fddd0d, + 0xbc8f94340071a38e, + 0x3feeb9b2769d2ca7, + 0xbc87deccdc93a349, + 0x3feeb6daa2cf6642, + 0xbc78dec6bd0f385f, + 0x3feeb42b569d4f82, + 0xbc861246ec7b5cf6, + 0x3feeb1a4ca5d920f, + 0x3c93350518fdd78e, + 0x3feeaf4736b527da, + 0x3c7b98b72f8a9b05, + 0x3feead12d497c7fd, + 0x3c9063e1e21c5409, + 0x3feeab07dd485429, + 0x3c34c7855019c6ea, + 0x3feea9268a5946b7, + 0x3c9432e62b64c035, + 0x3feea76f15ad2148, + 0xbc8ce44a6199769f, + 0x3feea5e1b976dc09, + 0xbc8c33c53bef4da8, + 0x3feea47eb03a5585, + 0xbc845378892be9ae, + 0x3feea34634ccc320, + 0xbc93cedd78565858, + 0x3feea23882552225, + 0x3c5710aa807e1964, + 0x3feea155d44ca973, + 0xbc93b3efbf5e2228, + 0x3feea09e667f3bcd, + 0xbc6a12ad8734b982, + 0x3feea012750bdabf, + 0xbc6367efb86da9ee, + 0x3fee9fb23c651a2f, + 0xbc80dc3d54e08851, + 0x3fee9f7df9519484, + 0xbc781f647e5a3ecf, + 0x3fee9f75e8ec5f74, + 0xbc86ee4ac08b7db0, + 0x3fee9f9a48a58174, + 0xbc8619321e55e68a, + 0x3fee9feb564267c9, + 0x3c909ccb5e09d4d3, + 0x3feea0694fde5d3f, + 0xbc7b32dcb94da51d, + 0x3feea11473eb0187, + 0x3c94ecfd5467c06b, + 0x3feea1ed0130c132, + 0x3c65ebe1abd66c55, + 0x3feea2f336cf4e62, + 0xbc88a1c52fb3cf42, + 0x3feea427543e1a12, + 0xbc9369b6f13b3734, + 0x3feea589994cce13, + 0xbc805e843a19ff1e, + 0x3feea71a4623c7ad, + 0xbc94d450d872576e, + 0x3feea8d99b4492ed, + 0x3c90ad675b0e8a00, + 0x3feeaac7d98a6699, + 0x3c8db72fc1f0eab4, + 0x3feeace5422aa0db, + 0xbc65b6609cc5e7ff, + 0x3feeaf3216b5448c, + 0x3c7bf68359f35f44, + 0x3feeb1ae99157736, + 0xbc93091fa71e3d83, + 0x3feeb45b0b91ffc6, + 0xbc5da9b88b6c1e29, + 0x3feeb737b0cdc5e5, + 0xbc6c23f97c90b959, + 0x3feeba44cbc8520f, + 0xbc92434322f4f9aa, + 0x3feebd829fde4e50, + 0xbc85ca6cd7668e4b, + 0x3feec0f170ca07ba, + 0x3c71affc2b91ce27, + 0x3feec49182a3f090, + 0x3c6dd235e10a73bb, + 0x3feec86319e32323, + 0xbc87c50422622263, + 0x3feecc667b5de565, + 0x3c8b1c86e3e231d5, + 0x3feed09bec4a2d33, + 0xbc91bbd1d3bcbb15, + 0x3feed503b23e255d, + 0x3c90cc319cee31d2, + 0x3feed99e1330b358, + 0x3c8469846e735ab3, + 0x3feede6b5579fdbf, + 0xbc82dfcd978e9db4, + 0x3feee36bbfd3f37a, + 0x3c8c1a7792cb3387, + 0x3feee89f995ad3ad, + 0xbc907b8f4ad1d9fa, + 0x3feeee07298db666, + 0xbc55c3d956dcaeba, + 0x3feef3a2b84f15fb, + 0xbc90a40e3da6f640, + 0x3feef9728de5593a, + 0xbc68d6f438ad9334, + 0x3feeff76f2fb5e47, + 0xbc91eee26b588a35, + 0x3fef05b030a1064a, + 0x3c74ffd70a5fddcd, + 0x3fef0c1e904bc1d2, + 0xbc91bdfbfa9298ac, + 0x3fef12c25bd71e09, + 0x3c736eae30af0cb3, + 0x3fef199bdd85529c, + 0x3c8ee3325c9ffd94, + 0x3fef20ab5fffd07a, + 0x3c84e08fd10959ac, + 0x3fef27f12e57d14b, + 0x3c63cdaf384e1a67, + 0x3fef2f6d9406e7b5, + 0x3c676b2c6c921968, + 0x3fef3720dcef9069, + 0xbc808a1883ccb5d2, + 0x3fef3f0b555dc3fa, + 0xbc8fad5d3ffffa6f, + 0x3fef472d4a07897c, + 0xbc900dae3875a949, + 0x3fef4f87080d89f2, + 0x3c74a385a63d07a7, + 0x3fef5818dcfba487, + 0xbc82919e2040220f, + 0x3fef60e316c98398, + 0x3c8e5a50d5c192ac, + 0x3fef69e603db3285, + 0x3c843a59ac016b4b, + 0x3fef7321f301b460, + 0xbc82d52107b43e1f, + 0x3fef7c97337b9b5f, + 0xbc892ab93b470dc9, + 0x3fef864614f5a129, + 0x3c74b604603a88d3, + 0x3fef902ee78b3ff6, + 0x3c83c5ec519d7271, + 0x3fef9a51fbc74c83, + 0xbc8ff7128fd391f0, + 0x3fefa4afa2a490da, + 0xbc8dae98e223747d, + 0x3fefaf482d8e67f1, + 0x3c8ec3bc41aa2008, + 0x3fefba1bee615a27, + 0x3c842b94c3a9eb32, + 0x3fefc52b376bba97, + 0x3c8a64a931d185ee, + 0x3fefd0765b6e4540, + 0xbc8e37bae43be3ed, + 0x3fefdbfdad9cbe14, + 0x3c77893b4d91cd9d, + 0x3fefe7c1819e90d8, + 0x3c5305c14160cc89, + 0x3feff3c22b8f71f1, + }, +}; + +#define InvLn2N __exp_data.invln2N +#define NegLn2hiN __exp_data.negln2hiN +#define NegLn2loN __exp_data.negln2loN +#define Shift __exp_data.shift +#define T __exp_data.tab +#define C2 __exp_data.poly[5 - EXP_POLY_ORDER] +#define C3 __exp_data.poly[6 - EXP_POLY_ORDER] +#define C4 __exp_data.poly[7 - EXP_POLY_ORDER] +#define C5 __exp_data.poly[8 - EXP_POLY_ORDER] +#define C6 __exp_data.poly[9 - EXP_POLY_ORDER] + +static inline double specialcase(double_t tmp, uint64_t sbits, uint64_t ki) +{ + double_t scale, y; + + if ((ki & 0x80000000) == 0) { + /* k > 0, the exponent of scale might have overflowed by <= 460. */ + sbits -= 1009ull << 52; + scale = asdouble(sbits); + y = 0x1p1009 * (scale + scale * tmp); + return eval_as_double(y); + } + /* k < 0, need special care in the subnormal range. */ + sbits += 1022ull << 52; + /* Note: sbits is signed scale. */ + scale = asdouble(sbits); + y = scale + scale * tmp; + if (fabs(y) < 1.0) { + /* Round y to the right precision before scaling it into the subnormal + range to avoid double rounding that can cause 0.5+E/2 ulp error where + E is the worst-case ulp error outside the subnormal range. So this + is only useful if the goal is better than 1 ulp worst-case error. */ + double_t hi, lo, one = 1.0; + if (y < 0.0) + one = -1.0; + lo = scale - y + scale * tmp; + hi = one + y; + lo = one - hi + y + lo; + y = eval_as_double(hi + lo) - one; + /* Fix the sign of 0. */ + if (y == 0.0) + y = asdouble(sbits & 0x8000000000000000); + /* The underflow exception needs to be signaled explicitly. */ + fp_force_eval(fp_barrier(0x1p-1022) * 0x1p-1022); + } + y = 0x1p-1022 * y; + return eval_as_double(y); +} + +#define SIGN_BIAS (0x800 << EXP_TABLE_BITS) + +double __math_xflow(uint32_t sign, double y) +{ + return eval_as_double(fp_barrier(sign ? -y : y) * y); +} + +double __math_uflow(uint32_t sign) +{ + return __math_xflow(sign, 0x1p-767); +} + +double __math_oflow(uint32_t sign) +{ + return __math_xflow(sign, 0x1p769); +} + +/* Computes sign*exp(x+xtail) where |xtail| < 2^-8/N and |xtail| <= |x|. + The sign_bias argument is SIGN_BIAS or 0 and sets the sign to -1 or 1. */ +static inline double exp_inline(double_t x, double_t xtail, uint32_t sign_bias) +{ + uint32_t abstop; + uint64_t ki, idx, top, sbits; + /* double_t for better performance on targets with FLT_EVAL_METHOD==2. */ + double_t kd, z, r, r2, scale, tail, tmp; + + abstop = top12(x) & 0x7ff; + if (predict_false(abstop - top12(0x1p-54) >= top12(512.0) - top12(0x1p-54))) { + if (abstop - top12(0x1p-54) >= 0x80000000) { + /* Avoid spurious underflow for tiny x. */ + /* Note: 0 is common input. */ + double_t one = WANT_ROUNDING ? 1.0 + x : 1.0; + return sign_bias ? -one : one; + } + if (abstop >= top12(1024.0)) { + /* Note: inf and nan are already handled. */ + if (asuint64(x) >> 63) + return __math_uflow(sign_bias); + else + return __math_oflow(sign_bias); + } + /* Large x is special cased below. */ + abstop = 0; + } + + /* exp(x) = 2^(k/N) * exp(r), with exp(r) in [2^(-1/2N),2^(1/2N)]. */ + /* x = ln2/N*k + r, with int k and r in [-ln2/2N, ln2/2N]. */ + z = InvLn2N * x; +#if TOINT_INTRINSICS + kd = roundtoint(z); + ki = converttoint(z); +#elif EXP_USE_TOINT_NARROW + /* z - kd is in [-0.5-2^-16, 0.5] in all rounding modes. */ + kd = eval_as_double(z + Shift); + ki = asuint64(kd) >> 16; + kd = (double_t)(int32_t)ki; +#else + /* z - kd is in [-1, 1] in non-nearest rounding modes. */ + kd = eval_as_double(z + Shift); + ki = asuint64(kd); + kd -= Shift; +#endif + r = x + kd * NegLn2hiN + kd * NegLn2loN; + /* The code assumes 2^-200 < |xtail| < 2^-8/N. */ + r += xtail; + /* 2^(k/N) ~= scale * (1 + tail). */ + idx = 2 * (ki % N); + top = (ki + sign_bias) << (52 - EXP_TABLE_BITS); + tail = asdouble(T[idx]); + /* This is only a valid scale when -1023*N < k < 1024*N. */ + sbits = T[idx + 1] + top; + /* exp(x) = 2^(k/N) * exp(r) ~= scale + scale * (tail + exp(r) - 1). */ + /* Evaluation is optimized assuming superscalar pipelined execution. */ + r2 = r * r; + /* Without fma the worst case error is 0.25/N ulp larger. */ + /* Worst case error is less than 0.5+1.11/N+(abs poly error * 2^53) ulp. */ + tmp = tail + r + r2 * (C2 + r * C3) + r2 * r2 * (C4 + r * C5); + if (predict_false(abstop == 0)) + return specialcase(tmp, sbits, ki); + scale = asdouble(sbits); + /* Note: tmp == 0 or |tmp| > 2^-200 and scale > 2^-739, so there + is no spurious underflow here even without fma. */ + return eval_as_double(scale + scale * tmp); +} + +/* Returns 0 if not int, 1 if odd int, 2 if even int. The argument is + the bit representation of a non-zero finite floating-point value. */ +static inline int checkint(uint64_t iy) +{ + int e = iy >> 52 & 0x7ff; + if (e < 0x3ff) + return 0; + if (e > 0x3ff + 52) + return 2; + if (iy & ((1ULL << (0x3ff + 52 - e)) - 1)) + return 0; + if (iy & (1ULL << (0x3ff + 52 - e))) + return 1; + return 2; +} + +#if 100 * __GNUC__ + __GNUC_MINOR__ >= 303 +#define NAN __builtin_nanf("") +#define INFINITY __builtin_inff() +#else +#define NAN (0.0f / 0.0f) +#define INFINITY 1e5000f +#endif + +static inline int zeroinfnan(uint64_t i) +{ + return 2 * i - 1 >= 2 * asuint64(INFINITY) - 1; +} + +#if WANT_SNAN +#error SNaN is unsupported +#else +#define issignalingf_inline(x) 0 +#define issignaling_inline(x) 0 +#endif + +double pow(double x, double y) +{ + uint32_t sign_bias = 0; + uint64_t ix, iy; + uint32_t topx, topy; + + ix = asuint64(x); + iy = asuint64(y); + topx = top12(x); + topy = top12(y); + if (predict_false(topx - 0x001 >= 0x7ff - 0x001 || (topy & 0x7ff) - 0x3be >= 0x43e - 0x3be)) { + /* Note: if |y| > 1075 * ln2 * 2^53 ~= 0x1.749p62 then pow(x,y) = inf/0 + and if |y| < 2^-54 / 1075 ~= 0x1.e7b6p-65 then pow(x,y) = +-1. */ + /* Special cases: (x < 0x1p-126 or inf or nan) or + (|y| < 0x1p-65 or |y| >= 0x1p63 or nan). */ + if (predict_false(zeroinfnan(iy))) { + if (2 * iy == 0) + return issignaling_inline(x) ? x + y : 1.0; + if (ix == asuint64(1.0)) + return issignaling_inline(y) ? x + y : 1.0; + if (2 * ix > 2 * asuint64(INFINITY) || 2 * iy > 2 * asuint64(INFINITY)) + return x + y; + if (2 * ix == 2 * asuint64(1.0)) + return 1.0; + if ((2 * ix < 2 * asuint64(1.0)) == !(iy >> 63)) + return 0.0; /* |x|<1 && y==inf or |x|>1 && y==-inf. */ + return y * y; + } + if (predict_false(zeroinfnan(ix))) { + double_t x2 = x * x; + if (ix >> 63 && checkint(iy) == 1) + x2 = -x2; + /* Without the barrier some versions of clang hoist the 1/x2 and + thus division by zero exception can be signaled spuriously. */ + return iy >> 63 ? fp_barrier(1 / x2) : x2; + } + /* Here x and y are non-zero finite. */ + if (ix >> 63) { + /* Finite x < 0. */ + int yint = checkint(iy); + if (yint == 0) + return __math_invalid(x); + if (yint == 1) + sign_bias = SIGN_BIAS; + ix &= 0x7fffffffffffffff; + topx &= 0x7ff; + } + if ((topy & 0x7ff) - 0x3be >= 0x43e - 0x3be) { + /* Note: sign_bias == 0 here because y is not odd. */ + if (ix == asuint64(1.0)) + return 1.0; + if ((topy & 0x7ff) < 0x3be) { + /* |y| < 2^-65, x^y ~= 1 + y*log(x). */ + if (WANT_ROUNDING) + return ix > asuint64(1.0) ? 1.0 + y : 1.0 - y; + else + return 1.0; + } + return (ix > asuint64(1.0)) == (topy < 0x800) ? __math_oflow(0) : __math_uflow(0); + } + if (topx == 0) { + /* Normalize subnormal x so exponent becomes negative. */ + ix = asuint64(x * 0x1p52); + ix &= 0x7fffffffffffffff; + ix -= 52ULL << 52; + } + } + + double_t lo; + double_t hi = log_inline(ix, &lo); + double_t ehi, elo; +#if __FP_FAST_FMA + ehi = y * hi; + elo = y * lo + __builtin_fma(y, hi, -ehi); +#else + double_t yhi = asdouble(iy & -1ULL << 27); + double_t ylo = y - yhi; + double_t lhi = asdouble(asuint64(hi) & -1ULL << 27); + double_t llo = hi - lhi + lo; + ehi = yhi * lhi; + elo = ylo * lhi + y * llo; /* |elo| < |ehi| * 2^-25. */ +#endif + return exp_inline(ehi, elo, sign_bias); +} +#endif diff --git a/ulib/axlibc/c/printf.c b/ulib/axlibc/c/printf.c new file mode 100644 index 000000000..3657376bd --- /dev/null +++ b/ulib/axlibc/c/printf.c @@ -0,0 +1,1482 @@ +/** + * @author (c) Eyal Rozenberg + * 2021-2022, Haifa, Palestine/Israel + * @author (c) Marco Paland (info@paland.com) + * 2014-2019, PALANDesign Hannover, Germany + * + * @note Others have made smaller contributions to this file: see the + * contributors page at https://github.com/eyalroz/printf/graphs/contributors + * or ask one of the authors. The original code for exponential specifiers was + * contributed by Martijn Jasperse . + * + * @brief Small stand-alone implementation of the printf family of functions + * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with + * a very limited resources. + * + * @note the implementations are thread-safe; re-entrant; use no functions from + * the standard library; and do not dynamically allocate any memory. + * + * @license The MIT License (MIT) + * + * 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 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. + */ + +// Define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H=1 ...) to include the +// printf_config.h header file +#define PRINTF_INCLUDE_CONFIG_H 1 + +#if PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + +#include "printf.h" + +#ifdef __cplusplus +#include +#include +#else +#include +#include +#include +#include +#endif // __cplusplus + +#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES +#define printf_ printf +#define sprintf_ sprintf +#define vsprintf_ vsprintf +#define snprintf_ snprintf +#define vsnprintf_ vsnprintf +#define vprintf_ vprintf +#endif + +// 'ntoa' conversion buffer size, this must be big enough to hold one converted +// numeric number including padded zeros (dynamically created on stack) +#ifndef PRINTF_INTEGER_BUFFER_SIZE +#define PRINTF_INTEGER_BUFFER_SIZE 32 +#endif + +// size of the fixed (on-stack) buffer for printing individual decimal numbers. +// this must be big enough to hold one converted floating-point value including +// padded zeros. +#ifndef PRINTF_DECIMAL_BUFFER_SIZE +#define PRINTF_DECIMAL_BUFFER_SIZE 32 +#endif + +// Support for the decimal notation floating point conversion specifiers (%f, %F) +#ifndef PRINTF_SUPPORT_DECIMAL_SPECIFIERS +#define PRINTF_SUPPORT_DECIMAL_SPECIFIERS 1 +#endif + +// Support for the exponential notation floating point conversion specifiers (%e, %g, %E, %G) +#ifndef PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS +#define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 1 +#endif + +// Support for the length write-back specifier (%n) +#ifndef PRINTF_SUPPORT_WRITEBACK_SPECIFIER +#define PRINTF_SUPPORT_WRITEBACK_SPECIFIER 1 +#endif + +// Default precision for the floating point conversion specifiers (the C standard sets this at 6) +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6 +#endif + +// According to the C languages standard, printf() and related functions must be able to print any +// integral number in floating-point notation, regardless of length, when using the %f specifier - +// possibly hundreds of characters, potentially overflowing your buffers. In this implementation, +// all values beyond this threshold are switched to exponential notation. +#ifndef PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL +#define PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL 9 +#endif + +// Support for the long long integral types (with the ll, z and t length modifiers for specifiers +// %d,%i,%o,%x,%X,%u, and with the %p specifier). Note: 'L' (long double) is not supported. +#ifndef PRINTF_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG 1 +#endif + +// The number of terms in a Taylor series expansion of log_10(x) to +// use for approximation - including the power-zero term (i.e. the +// value at the point of expansion). +#ifndef PRINTF_LOG10_TAYLOR_TERMS +#define PRINTF_LOG10_TAYLOR_TERMS 4 +#endif + +#if PRINTF_LOG10_TAYLOR_TERMS <= 1 +#error "At least one non-constant Taylor expansion is necessary for the log10() calculation" +#endif + +// Be extra-safe, and don't assume format specifiers are completed correctly +// before the format string end. +#ifndef PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER +#define PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER 1 +#endif + +#define PRINTF_PREFER_DECIMAL false +#define PRINTF_PREFER_EXPONENTIAL true + +/////////////////////////////////////////////////////////////////////////////// + +// The following will convert the number-of-digits into an exponential-notation literal +#define PRINTF_CONCATENATE(s1, s2) s1##s2 +#define PRINTF_EXPAND_THEN_CONCATENATE(s1, s2) PRINTF_CONCATENATE(s1, s2) +#define PRINTF_FLOAT_NOTATION_THRESHOLD \ + PRINTF_EXPAND_THEN_CONCATENATE(1e, PRINTF_MAX_INTEGRAL_DIGITS_FOR_DECIMAL) + +// internal flag definitions +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_INT (1U << 8U) +// Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS +#define FLAGS_LONG (1U << 9U) +#define FLAGS_LONG_LONG (1U << 10U) +#define FLAGS_PRECISION (1U << 11U) +#define FLAGS_ADAPT_EXP (1U << 12U) +#define FLAGS_POINTER (1U << 13U) +// Note: Similar, but not identical, effect as FLAGS_HASH +#define FLAGS_SIGNED (1U << 14U) +// Only used with PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS + +#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS + +#define FLAGS_INT8 FLAGS_CHAR + +#if (SHRT_MAX == 32767LL) +#define FLAGS_INT16 FLAGS_SHORT +#elif (INT_MAX == 32767LL) +#define FLAGS_INT16 FLAGS_INT +#elif (LONG_MAX == 32767LL) +#define FLAGS_INT16 FLAGS_LONG +#elif (LLONG_MAX == 32767LL) +#define FLAGS_INT16 FLAGS_LONG_LONG +#else +#error "No basic integer type has a size of 16 bits exactly" +#endif + +#if (SHRT_MAX == 2147483647LL) +#define FLAGS_INT32 FLAGS_SHORT +#elif (INT_MAX == 2147483647LL) +#define FLAGS_INT32 FLAGS_INT +#elif (LONG_MAX == 2147483647LL) +#define FLAGS_INT32 FLAGS_LONG +#elif (LLONG_MAX == 2147483647LL) +#define FLAGS_INT32 FLAGS_LONG_LONG +#else +#error "No basic integer type has a size of 32 bits exactly" +#endif + +#if (SHRT_MAX == 9223372036854775807LL) +#define FLAGS_INT64 FLAGS_SHORT +#elif (INT_MAX == 9223372036854775807LL) +#define FLAGS_INT64 FLAGS_INT +#elif (LONG_MAX == 9223372036854775807LL) +#define FLAGS_INT64 FLAGS_LONG +#elif (LLONG_MAX == 9223372036854775807LL) +#define FLAGS_INT64 FLAGS_LONG_LONG +#else +#error "No basic integer type has a size of 64 bits exactly" +#endif + +#endif // PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS + +typedef unsigned int printf_flags_t; + +#define BASE_BINARY 2 +#define BASE_OCTAL 8 +#define BASE_DECIMAL 10 +#define BASE_HEX 16 + +typedef uint8_t numeric_base_t; + +#if PRINTF_SUPPORT_LONG_LONG +typedef unsigned long long printf_unsigned_value_t; +typedef long long printf_signed_value_t; +#else +typedef unsigned long printf_unsigned_value_t; +typedef long printf_signed_value_t; +#endif + +// The printf()-family functions return an `int`; it is therefore +// unnecessary/inappropriate to use size_t - often larger than int +// in practice - for non-negative related values, such as widths, +// precisions, offsets into buffers used for printing and the sizes +// of these buffers. instead, we use: +typedef unsigned int printf_size_t; +#define PRINTF_MAX_POSSIBLE_BUFFER_SIZE INT_MAX +// If we were to nitpick, this would actually be INT_MAX + 1, +// since INT_MAX is the maximum return value, which excludes the +// trailing '\0'. + +#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) +#include +#if FLT_RADIX != 2 +#error "Non-binary-radix floating-point types are unsupported." +#endif + +#if DBL_MANT_DIG == 24 + +#define DOUBLE_SIZE_IN_BITS 32 +typedef uint32_t double_uint_t; +#define DOUBLE_EXPONENT_MASK 0xFFU +#define DOUBLE_BASE_EXPONENT 127 +#define DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10 -38 +#define DOUBLE_MAX_SUBNORMAL_POWER_OF_10 1e-38 + +#elif DBL_MANT_DIG == 53 + +#define DOUBLE_SIZE_IN_BITS 64 +typedef uint64_t double_uint_t; +#define DOUBLE_EXPONENT_MASK 0x7FFU +#define DOUBLE_BASE_EXPONENT 1023 +#define DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10 -308 +#define DOUBLE_MAX_SUBNORMAL_POWER_OF_10 1e-308 + +#else +#error "Unsupported double type configuration" +#endif +#define DOUBLE_STORED_MANTISSA_BITS (DBL_MANT_DIG - 1) + +typedef union { + double_uint_t U; + double F; +} double_with_bit_access; + +// This is unnecessary in C99, since compound initializers can be used, +// but: +// 1. Some compilers are finicky about this; +// 2. Some people may want to convert this to C89; +// 3. If you try to use it as C++, only C++20 supports compound literals +static inline double_with_bit_access get_bit_access(double x) +{ + double_with_bit_access dwba; + dwba.F = x; + return dwba; +} + +static inline int get_sign_bit(double x) +{ + // The sign is stored in the highest bit + return (int)(get_bit_access(x).U >> (DOUBLE_SIZE_IN_BITS - 1)); +} + +static inline int get_exp2(double_with_bit_access x) +{ + // The exponent in an IEEE-754 floating-point number occupies a contiguous + // sequence of bits (e.g. 52..62 for 64-bit doubles), but with a non-trivial representation: An + // unsigned offset from some negative value (with the extremal offset values reserved for + // special use). + return (int)((x.U >> DOUBLE_STORED_MANTISSA_BITS) & DOUBLE_EXPONENT_MASK) - + DOUBLE_BASE_EXPONENT; +} +#define PRINTF_ABS(_x) ((_x) > 0 ? (_x) : -(_x)) + +#endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) + +// Note in particular the behavior here on LONG_MIN or LLONG_MIN; it is valid +// and well-defined, but if you're not careful you can easily trigger undefined +// behavior with -LONG_MIN or -LLONG_MIN +#define ABS_FOR_PRINTING(_x) \ + ((printf_unsigned_value_t)((_x) > 0 ? (_x) : -((printf_signed_value_t)_x))) + +// wrapper (used as buffer) for output function type +// +// One of the following must hold: +// 1. max_chars is 0 +// 2. buffer is non-null +// 3. function is non-null +// +// ... otherwise bad things will happen. +typedef struct { + void (*function)(char c, void *extra_arg); + void *extra_function_arg; + char *buffer; + printf_size_t pos; + printf_size_t max_chars; +} output_gadget_t; + +// Note: This function currently assumes it is not passed a '\0' c, +// or alternatively, that '\0' can be passed to the function in the output +// gadget. The former assumption holds within the printf library. It also +// assumes that the output gadget has been properly initialized. +static inline void putchar_via_gadget(output_gadget_t *gadget, char c) +{ + printf_size_t write_pos = gadget->pos++; + // We're _always_ increasing pos, so as to count how may characters + // _would_ have been written if not for the max_chars limitation + if (write_pos >= gadget->max_chars) { + return; + } + if (gadget->function != NULL) { + // No check for c == '\0' . + gadget->function(c, gadget->extra_function_arg); + } else { + // it must be the case that gadget->buffer != NULL , due to the constraint + // on output_gadget_t ; and note we're relying on write_pos being non-negative. + gadget->buffer[write_pos] = c; + } +} + +// Possibly-write the string-terminating '\0' character +static inline void append_termination_with_gadget(output_gadget_t *gadget) +{ + if (gadget->function != NULL || gadget->max_chars == 0) { + return; + } + if (gadget->buffer == NULL) { + return; + } + printf_size_t null_char_pos = + gadget->pos < gadget->max_chars ? gadget->pos : gadget->max_chars - 1; + gadget->buffer[null_char_pos] = '\0'; +} + +// We can't use putchar_ as is, since our output gadget +// only takes pointers to functions with an extra argument +// static inline void putchar_wrapper(char c, void *unused) +// { +// (void)unused; +// putchar_(c); +// } + +static inline output_gadget_t discarding_gadget(void) +{ + output_gadget_t gadget; + gadget.function = NULL; + gadget.extra_function_arg = NULL; + gadget.buffer = NULL; + gadget.pos = 0; + gadget.max_chars = 0; + return gadget; +} + +static inline output_gadget_t buffer_gadget(char *buffer, size_t buffer_size) +{ + printf_size_t usable_buffer_size = (buffer_size > PRINTF_MAX_POSSIBLE_BUFFER_SIZE) + ? PRINTF_MAX_POSSIBLE_BUFFER_SIZE + : (printf_size_t)buffer_size; + output_gadget_t result = discarding_gadget(); + if (buffer != NULL) { + result.buffer = buffer; + result.max_chars = usable_buffer_size; + } + return result; +} + +static inline output_gadget_t function_gadget(void (*function)(char, void *), void *extra_arg) +{ + output_gadget_t result = discarding_gadget(); + result.function = function; + result.extra_function_arg = extra_arg; + result.max_chars = PRINTF_MAX_POSSIBLE_BUFFER_SIZE; + return result; +} + +// static inline output_gadget_t extern_putchar_gadget(void) +// { +// return function_gadget(putchar_wrapper, NULL); +// } + +// internal secure strlen +// @return The length of the string (excluding the terminating 0) limited by 'maxsize' +// @note strlen uses size_t, but wes only use this function with printf_size_t +// variables - hence the signature. +static inline printf_size_t strnlen_s_(const char *str, printf_size_t maxsize) +{ + const char *s; + for (s = str; *s && maxsize--; ++s) + ; + return (printf_size_t)(s - str); +} + +// internal test if char is a digit (0-9) +// @return true if char is a digit +static inline bool is_digit_(char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + +// internal ASCII string to printf_size_t conversion +static printf_size_t atou_(const char **str) +{ + printf_size_t i = 0U; + while (is_digit_(**str)) { + i = i * 10U + (printf_size_t)(*((*str)++) - '0'); + } + return i; +} + +// output the specified string in reverse, taking care of any zero-padding +static void out_rev_(output_gadget_t *output, const char *buf, printf_size_t len, + printf_size_t width, printf_flags_t flags) +{ + const printf_size_t start_pos = output->pos; + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for (printf_size_t i = len; i < width; i++) { + putchar_via_gadget(output, ' '); + } + } + + // reverse string + while (len) { + putchar_via_gadget(output, buf[--len]); + } + + // append pad spaces up to given width + if (flags & FLAGS_LEFT) { + while (output->pos - start_pos < width) { + putchar_via_gadget(output, ' '); + } + } +} + +// Invoked by print_integer after the actual number has been printed, performing necessary +// work on the number's prefix (as the number is initially printed in reverse order) +static void print_integer_finalization(output_gadget_t *output, char *buf, printf_size_t len, + bool negative, numeric_base_t base, printf_size_t precision, + printf_size_t width, printf_flags_t flags) +{ + printf_size_t unpadded_len = len; + + // pad with leading zeros + { + if (!(flags & FLAGS_LEFT)) { + if (width && (flags & FLAGS_ZEROPAD) && + (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + while ((len < precision) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { + buf[len++] = '0'; + } + + if (base == BASE_OCTAL && (len > unpadded_len)) { + // Since we've written some zeros, we've satisfied the alternative format leading space + // requirement + flags &= ~FLAGS_HASH; + } + } + + // handle hash + if (flags & (FLAGS_HASH | FLAGS_POINTER)) { + if (!(flags & FLAGS_PRECISION) && len && ((len == precision) || (len == width))) { + // Let's take back some padding digits to fit in what will eventually + // be the format-specific prefix + if (unpadded_len < len) { + len--; // This should suffice for BASE_OCTAL + } + if (len && (base == BASE_HEX || base == BASE_BINARY) && (unpadded_len < len)) { + len--; // ... and an extra one for 0x or 0b + } + } + if ((base == BASE_HEX) && !(flags & FLAGS_UPPERCASE) && + (len < PRINTF_INTEGER_BUFFER_SIZE)) { + buf[len++] = 'x'; + } else if ((base == BASE_HEX) && (flags & FLAGS_UPPERCASE) && + (len < PRINTF_INTEGER_BUFFER_SIZE)) { + buf[len++] = 'X'; + } else if ((base == BASE_BINARY) && (len < PRINTF_INTEGER_BUFFER_SIZE)) { + buf[len++] = 'b'; + } + if (len < PRINTF_INTEGER_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_INTEGER_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + out_rev_(output, buf, len, width, flags); +} + +// An internal itoa-like function +static void print_integer(output_gadget_t *output, printf_unsigned_value_t value, bool negative, + numeric_base_t base, printf_size_t precision, printf_size_t width, + printf_flags_t flags) +{ + char buf[PRINTF_INTEGER_BUFFER_SIZE]; + printf_size_t len = 0U; + + if (!value) { + if (!(flags & FLAGS_PRECISION)) { + buf[len++] = '0'; + flags &= ~FLAGS_HASH; + // We drop this flag this since either the alternative and regular modes of the + // specifier don't differ on 0 values, or (in the case of octal) we've already provided + // the special handling for this mode. + } else if (base == BASE_HEX) { + flags &= ~FLAGS_HASH; + // We drop this flag this since either the alternative and regular modes of the + // specifier don't differ on 0 values + } + } else { + do { + const char digit = (char)(value % base); + buf[len++] = (char)(digit < 10 ? '0' + digit + : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10); + value /= base; + } while (value && (len < PRINTF_INTEGER_BUFFER_SIZE)); + } + + print_integer_finalization(output, buf, len, negative, base, precision, width, flags); +} + +#if (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) + +// Stores a fixed-precision representation of a double relative +// to a fixed precision (which cannot be determined by examining this structure) +struct double_components { + int_fast64_t integral; + int_fast64_t fractional; + // ... truncation of the actual fractional part of the double value, scaled + // by the precision value + bool is_negative; +}; + +#define NUM_DECIMAL_DIGITS_IN_INT64_T 18 +#define PRINTF_MAX_PRECOMPUTED_POWER_OF_10 NUM_DECIMAL_DIGITS_IN_INT64_T +static const double powers_of_10[NUM_DECIMAL_DIGITS_IN_INT64_T] = { + 1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07, 1e08, + 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17}; + +#define PRINTF_MAX_SUPPORTED_PRECISION NUM_DECIMAL_DIGITS_IN_INT64_T - 1 + +// Break up a double number - which is known to be a finite non-negative number - +// into its base-10 parts: integral - before the decimal point, and fractional - after it. +// Taken the precision into account, but does not change it even internally. +static struct double_components get_components(double number, printf_size_t precision) +{ + struct double_components number_; + number_.is_negative = get_sign_bit(number); + double abs_number = (number_.is_negative) ? -number : number; + number_.integral = (int_fast64_t)abs_number; + double remainder = (abs_number - (double)number_.integral) * powers_of_10[precision]; + number_.fractional = (int_fast64_t)remainder; + + remainder -= (double)number_.fractional; + + if (remainder > 0.5) { + ++number_.fractional; + // handle rollover, e.g. case 0.99 with precision 1 is 1.0 + if ((double)number_.fractional >= powers_of_10[precision]) { + number_.fractional = 0; + ++number_.integral; + } + } else if ((remainder == 0.5) && ((number_.fractional == 0U) || (number_.fractional & 1U))) { + // if halfway, round up if odd OR if last digit is 0 + ++number_.fractional; + } + + if (precision == 0U) { + remainder = abs_number - (double)number_.integral; + if ((!(remainder < 0.5) || (remainder > 0.5)) && (number_.integral & 1)) { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++number_.integral; + } + } + return number_; +} + +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS +struct scaling_factor { + double raw_factor; + bool multiply; // if true, need to multiply by raw_factor; otherwise need to divide by it +}; + +static double apply_scaling(double num, struct scaling_factor normalization) +{ + return normalization.multiply ? num * normalization.raw_factor : num / normalization.raw_factor; +} + +static double unapply_scaling(double normalized, struct scaling_factor normalization) +{ +#ifdef __GNUC__ +// accounting for a static analysis bug in GCC 6.x and earlier +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + return normalization.multiply ? normalized / normalization.raw_factor + : normalized * normalization.raw_factor; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +} + +static struct scaling_factor update_normalization(struct scaling_factor sf, + double extra_multiplicative_factor) +{ + struct scaling_factor result; + if (sf.multiply) { + result.multiply = true; + result.raw_factor = sf.raw_factor * extra_multiplicative_factor; + } else { + int factor_exp2 = get_exp2(get_bit_access(sf.raw_factor)); + int extra_factor_exp2 = get_exp2(get_bit_access(extra_multiplicative_factor)); + + // Divide the larger-exponent raw raw_factor by the smaller + if (PRINTF_ABS(factor_exp2) > PRINTF_ABS(extra_factor_exp2)) { + result.multiply = false; + result.raw_factor = sf.raw_factor / extra_multiplicative_factor; + } else { + result.multiply = true; + result.raw_factor = extra_multiplicative_factor / sf.raw_factor; + } + } + return result; +} + +static struct double_components get_normalized_components(bool negative, printf_size_t precision, + double non_normalized, + struct scaling_factor normalization, + int floored_exp10) +{ + struct double_components components; + components.is_negative = negative; + double scaled = apply_scaling(non_normalized, normalization); + + bool close_to_representation_extremum = + ((-floored_exp10 + (int)precision) >= DBL_MAX_10_EXP - 1); + if (close_to_representation_extremum) { + // We can't have a normalization factor which also accounts for the precision, i.e. moves + // some decimal digits into the mantissa, since it's unrepresentable, or nearly + // unrepresentable. So, we'll give up early on getting extra precision... + return get_components(negative ? -scaled : scaled, precision); + } + components.integral = (int_fast64_t)scaled; + double remainder = non_normalized - unapply_scaling((double)components.integral, normalization); + double prec_power_of_10 = powers_of_10[precision]; + struct scaling_factor account_for_precision = + update_normalization(normalization, prec_power_of_10); + double scaled_remainder = apply_scaling(remainder, account_for_precision); + double rounding_threshold = 0.5; + + components.fractional = + (int_fast64_t)scaled_remainder; // when precision == 0, the assigned value should be 0 + scaled_remainder -= + (double)components.fractional; // when precision == 0, this will not change scaled_remainder + + components.fractional += (scaled_remainder >= rounding_threshold); + if (scaled_remainder == rounding_threshold) { + // banker's rounding: Round towards the even number (making the mean error 0) + components.fractional &= ~((int_fast64_t)0x1); + } + // handle rollover, e.g. the case of 0.99 with precision 1 becoming (0,100), + // and must then be corrected into (1, 0). + // Note: for precision = 0, this will "translate" the rounding effect from + // the fractional part to the integral part where it should actually be + // felt (as prec_power_of_10 is 1) + if ((double)components.fractional >= prec_power_of_10) { + components.fractional = 0; + ++components.integral; + } + return components; +} +#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + +static void print_broken_up_decimal(struct double_components number_, output_gadget_t *output, + printf_size_t precision, printf_size_t width, + printf_flags_t flags, char *buf, printf_size_t len) +{ + if (precision != 0U) { + // do fractional part, as an unsigned number + + printf_size_t count = precision; + + // %g/%G mandates we skip the trailing 0 digits... + if ((flags & FLAGS_ADAPT_EXP) && !(flags & FLAGS_HASH) && (number_.fractional > 0)) { + while (true) { + int_fast64_t digit = number_.fractional % 10U; + if (digit != 0) { + break; + } + --count; + number_.fractional /= 10U; + } + // ... and even the decimal point if there are no + // non-zero fractional part digits (see below) + } + + if (number_.fractional > 0 || !(flags & FLAGS_ADAPT_EXP) || (flags & FLAGS_HASH)) { + while (len < PRINTF_DECIMAL_BUFFER_SIZE) { + --count; + buf[len++] = (char)('0' + number_.fractional % 10U); + if (!(number_.fractional /= 10U)) { + break; + } + } + // add extra 0s + while ((len < PRINTF_DECIMAL_BUFFER_SIZE) && (count > 0U)) { + buf[len++] = '0'; + --count; + } + if (len < PRINTF_DECIMAL_BUFFER_SIZE) { + buf[len++] = '.'; + } + } + } else { + if ((flags & FLAGS_HASH) && (len < PRINTF_DECIMAL_BUFFER_SIZE)) { + buf[len++] = '.'; + } + } + + // Write the integer part of the number (it comes after the fractional + // since the character order is reversed) + while (len < PRINTF_DECIMAL_BUFFER_SIZE) { + buf[len++] = (char)('0' + (number_.integral % 10)); + if (!(number_.integral /= 10)) { + break; + } + } + + // pad leading zeros + if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { + if (width && (number_.is_negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < width) && (len < PRINTF_DECIMAL_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_DECIMAL_BUFFER_SIZE) { + if (number_.is_negative) { + buf[len++] = '-'; + } else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + out_rev_(output, buf, len, width, flags); +} + +// internal ftoa for fixed decimal floating point +static void print_decimal_number(output_gadget_t *output, double number, printf_size_t precision, + printf_size_t width, printf_flags_t flags, char *buf, + printf_size_t len) +{ + struct double_components value_ = get_components(number, precision); + print_broken_up_decimal(value_, output, precision, width, flags, buf, len); +} + +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + +// A floor function - but one which only works for numbers whose +// floor value is representable by an int. +static int bastardized_floor(double x) +{ + if (x >= 0) { + return (int)x; + } + int n = (int)x; + return (((double)n) == x) ? n : n - 1; +} + +// Computes the base-10 logarithm of the input number - which must be an actual +// positive number (not infinity or NaN, nor a sub-normal) +static double log10_of_positive(double positive_number) +{ + // The implementation follows David Gay (https://www.ampl.com/netlib/fp/dtoa.c). + // + // Since log_10 ( M * 2^x ) = log_10(M) + x , we can separate the components of + // our input number, and need only solve log_10(M) for M between 1 and 2 (as + // the base-2 mantissa is always 1-point-something). In that limited range, a + // Taylor series expansion of log10(x) should serve us well enough; and we'll + // take the mid-point, 1.5, as the point of expansion. + + double_with_bit_access dwba = get_bit_access(positive_number); + // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) + int exp2 = get_exp2(dwba); + // drop the exponent, so dwba.F comes into the range [1,2) + dwba.U = (dwba.U & (((double_uint_t)(1) << DOUBLE_STORED_MANTISSA_BITS) - 1U)) | + ((double_uint_t)DOUBLE_BASE_EXPONENT << DOUBLE_STORED_MANTISSA_BITS); + double z = (dwba.F - 1.5); + return ( + // Taylor expansion around 1.5: + 0.1760912590556812420 // Expansion term 0: ln(1.5) / ln(10) + + z * 0.2895296546021678851 // Expansion term 1: (M - 1.5) * 2/3 / ln(10) +#if PRINTF_LOG10_TAYLOR_TERMS > 2 + - z * z * 0.0965098848673892950 // Expansion term 2: (M - 1.5)^2 * 2/9 / ln(10) +#if PRINTF_LOG10_TAYLOR_TERMS > 3 + + z * z * z * 0.0428932821632841311 // Expansion term 2: (M - 1.5)^3 * 8/81 / ln(10) +#endif +#endif + // exact log_2 of the exponent x, with logarithm base change + + exp2 * 0.30102999566398119521 // = exp2 * log_10(2) = exp2 * ln(2)/ln(10) + ); +} + +static double pow10_of_int(int floored_exp10) +{ + // A crude hack for avoiding undesired behavior with barely-normal or slightly-subnormal values. + if (floored_exp10 == DOUBLE_MAX_SUBNORMAL_EXPONENT_OF_10) { + return DOUBLE_MAX_SUBNORMAL_POWER_OF_10; + } + // Compute 10^(floored_exp10) but (try to) make sure that doesn't overflow + double_with_bit_access dwba; + int exp2 = bastardized_floor(floored_exp10 * 3.321928094887362 + 0.5); + const double z = floored_exp10 * 2.302585092994046 - exp2 * 0.6931471805599453; + const double z2 = z * z; + dwba.U = ((double_uint_t)(exp2) + DOUBLE_BASE_EXPONENT) << DOUBLE_STORED_MANTISSA_BITS; + // compute exp(z) using continued fractions, + // see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + dwba.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); + return dwba.F; +} + +static void print_exponential_number(output_gadget_t *output, double number, + printf_size_t precision, printf_size_t width, + printf_flags_t flags, char *buf, printf_size_t len) +{ + const bool negative = get_sign_bit(number); + // This number will decrease gradually (by factors of 10) as we "extract" the exponent out of it + double abs_number = negative ? -number : number; + + int floored_exp10; + bool abs_exp10_covered_by_powers_table; + struct scaling_factor normalization; + + // Determine the decimal exponent + if (abs_number == 0.0) { + // TODO: This is a special-case for 0.0 (and -0.0); but proper handling is required for + // denormals more generally. + floored_exp10 = + 0; // ... and no need to set a normalization factor or check the powers table + } else { + double exp10 = log10_of_positive(abs_number); + floored_exp10 = bastardized_floor(exp10); + double p10 = pow10_of_int(floored_exp10); + // correct for rounding errors + if (abs_number < p10) { + floored_exp10--; + p10 /= 10; + } + abs_exp10_covered_by_powers_table = + PRINTF_ABS(floored_exp10) < PRINTF_MAX_PRECOMPUTED_POWER_OF_10; + normalization.raw_factor = + abs_exp10_covered_by_powers_table ? powers_of_10[PRINTF_ABS(floored_exp10)] : p10; + } + + // We now begin accounting for the widths of the two parts of our printed field: + // the decimal part after decimal exponent extraction, and the base-10 exponent part. + // For both of these, the value of 0 has a special meaning, but not the same one: + // a 0 exponent-part width means "don't print the exponent"; a 0 decimal-part width + // means "use as many characters as necessary". + + bool fall_back_to_decimal_only_mode = false; + if (flags & FLAGS_ADAPT_EXP) { + int required_significant_digits = (precision == 0) ? 1 : (int)precision; + // Should we want to fall-back to "%f" mode, and only print the decimal part? + fall_back_to_decimal_only_mode = + (floored_exp10 >= -4 && floored_exp10 < required_significant_digits); + // Now, let's adjust the precision + // This also decided how we adjust the precision value - as in "%g" mode, + // "precision" is the number of _significant digits_, and this is when we "translate" + // the precision value to an actual number of decimal digits. + int precision_ = + fall_back_to_decimal_only_mode + ? (int)precision - 1 - floored_exp10 + : (int)precision - 1; // the presence of the exponent ensures only one significant + // digit comes before the decimal point + precision = (precision_ > 0 ? (unsigned)precision_ : 0U); + flags |= FLAGS_PRECISION; // make sure print_broken_up_decimal respects our choice above + } + + normalization.multiply = (floored_exp10 < 0 && abs_exp10_covered_by_powers_table); + bool should_skip_normalization = (fall_back_to_decimal_only_mode || floored_exp10 == 0); + struct double_components decimal_part_components = + should_skip_normalization ? get_components(negative ? -abs_number : abs_number, precision) + : get_normalized_components(negative, precision, abs_number, + normalization, floored_exp10); + + // Account for roll-over, e.g. rounding from 9.99 to 100.0 - which effects + // the exponent and may require additional tweaking of the parts + if (fall_back_to_decimal_only_mode) { + if ((flags & FLAGS_ADAPT_EXP) && floored_exp10 >= -1 && + decimal_part_components.integral == powers_of_10[floored_exp10 + 1]) { + floored_exp10++; // Not strictly necessary, since floored_exp10 is no longer really used + precision--; + // ... and it should already be the case that decimal_part_components.fractional == 0 + } + // TODO: What about rollover strictly within the fractional part? + } else { + if (decimal_part_components.integral >= 10) { + floored_exp10++; + decimal_part_components.integral = 1; + decimal_part_components.fractional = 0; + } + } + + // the floored_exp10 format is "E%+03d" and largest possible floored_exp10 value for a 64-bit + // double is "307" (for 2^1023), so we set aside 4-5 characters overall + printf_size_t exp10_part_width = fall_back_to_decimal_only_mode ? 0U + : (PRINTF_ABS(floored_exp10) < 100) ? 4U + : 5U; + + printf_size_t decimal_part_width = + ((flags & FLAGS_LEFT) && exp10_part_width) + ? + // We're padding on the right, so the width constraint is the exponent part's + // problem, not the decimal part's, so we'll use as many characters as we need: + 0U + : + // We're padding on the left; so the width constraint is the decimal part's + // problem. Well, can both the decimal part and the exponent part fit within our overall + // width? + ((width > exp10_part_width) + ? + // Yes, so we limit our decimal part's width. + // (Note this is trivially valid even if we've fallen back to "%f" mode) + width - exp10_part_width + : + // No; we just give up on any restriction on the decimal part and use as many + // characters as we need + 0U); + + const printf_size_t printed_exponential_start_pos = output->pos; + print_broken_up_decimal(decimal_part_components, output, precision, decimal_part_width, flags, + buf, len); + + if (!fall_back_to_decimal_only_mode) { + putchar_via_gadget(output, (flags & FLAGS_UPPERCASE) ? 'E' : 'e'); + print_integer(output, ABS_FOR_PRINTING(floored_exp10), floored_exp10 < 0, 10, 0, + exp10_part_width - 1, FLAGS_ZEROPAD | FLAGS_PLUS); + if (flags & FLAGS_LEFT) { + // We need to right-pad with spaces to meet the width requirement + while (output->pos - printed_exponential_start_pos < width) { + putchar_via_gadget(output, ' '); + } + } + } +} +#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + +static void print_floating_point(output_gadget_t *output, double value, printf_size_t precision, + printf_size_t width, printf_flags_t flags, bool prefer_exponential) +{ + char buf[PRINTF_DECIMAL_BUFFER_SIZE]; + printf_size_t len = 0U; + + // test for special values + if (value != value) { + out_rev_(output, "nan", 3, width, flags); + return; + } + if (value < -DBL_MAX) { + out_rev_(output, "fni-", 4, width, flags); + return; + } + if (value > DBL_MAX) { + out_rev_(output, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, + width, flags); + return; + } + + if (!prefer_exponential && + ((value > PRINTF_FLOAT_NOTATION_THRESHOLD) || (value < -PRINTF_FLOAT_NOTATION_THRESHOLD))) { + // The required behavior of standard printf is to print _every_ integral-part digit -- which + // could mean printing hundreds of characters, overflowing any fixed internal buffer and + // necessitating a more complicated implementation. +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + print_exponential_number(output, value, precision, width, flags, buf, len); +#endif + return; + } + + // set default precision, if not set explicitly + if (!(flags & FLAGS_PRECISION)) { + precision = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // limit precision so that our integer holding the fractional part does not overflow + while ((len < PRINTF_DECIMAL_BUFFER_SIZE) && (precision > PRINTF_MAX_SUPPORTED_PRECISION)) { + buf[len++] = '0'; // This respects the precision in terms of result length only + precision--; + } + +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + if (prefer_exponential) + print_exponential_number(output, value, precision, width, flags, buf, len); + else +#endif + print_decimal_number(output, value, precision, width, flags, buf, len); +} + +#endif // (PRINTF_SUPPORT_DECIMAL_SPECIFIERS || PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS) + +// Advances the format pointer past the flags, and returns the parsed flags +// due to the characters passed +static printf_flags_t parse_flags(const char **format) +{ + printf_flags_t flags = 0U; + do { + switch (**format) { + case '0': + flags |= FLAGS_ZEROPAD; + (*format)++; + break; + case '-': + flags |= FLAGS_LEFT; + (*format)++; + break; + case '+': + flags |= FLAGS_PLUS; + (*format)++; + break; + case ' ': + flags |= FLAGS_SPACE; + (*format)++; + break; + case '#': + flags |= FLAGS_HASH; + (*format)++; + break; + default: + return flags; + } + } while (true); +} + +static inline void format_string_loop(output_gadget_t *output, const char *format, va_list args) +{ +#if PRINTF_CHECK_FOR_NUL_IN_FORMAT_SPECIFIER +#define ADVANCE_IN_FORMAT_STRING(cptr_) \ + do { \ + (cptr_)++; \ + if (!*(cptr_)) \ + return; \ + } while (0) +#else +#define ADVANCE_IN_FORMAT_STRING(cptr_) (cptr_)++ +#endif + + while (*format) { + if (*format != '%') { + // A regular content character + putchar_via_gadget(output, *format); + format++; + continue; + } + // We're parsing a format specifier: %[flags][width][.precision][length] + ADVANCE_IN_FORMAT_STRING(format); + + printf_flags_t flags = parse_flags(&format); + + // evaluate width field + printf_size_t width = 0U; + if (is_digit_(*format)) { + width = (printf_size_t)atou_(&format); + } else if (*format == '*') { + const int w = va_arg(args, int); + if (w < 0) { + flags |= FLAGS_LEFT; // reverse padding + width = (printf_size_t)-w; + } else { + width = (printf_size_t)w; + } + ADVANCE_IN_FORMAT_STRING(format); + } + + // evaluate precision field + printf_size_t precision = 0U; + if (*format == '.') { + flags |= FLAGS_PRECISION; + ADVANCE_IN_FORMAT_STRING(format); + if (is_digit_(*format)) { + precision = (printf_size_t)atou_(&format); + } else if (*format == '*') { + const int precision_ = va_arg(args, int); + precision = precision_ > 0 ? (printf_size_t)precision_ : 0U; + ADVANCE_IN_FORMAT_STRING(format); + } + } + + // evaluate length field + switch (*format) { +#ifdef PRINTF_SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS + case 'I': { + ADVANCE_IN_FORMAT_STRING(format); + // Greedily parse for size in bits: 8, 16, 32 or 64 + switch (*format) { + case '8': + flags |= FLAGS_INT8; + ADVANCE_IN_FORMAT_STRING(format); + break; + case '1': + ADVANCE_IN_FORMAT_STRING(format); + if (*format == '6') { + format++; + flags |= FLAGS_INT16; + } + break; + case '3': + ADVANCE_IN_FORMAT_STRING(format); + if (*format == '2') { + ADVANCE_IN_FORMAT_STRING(format); + flags |= FLAGS_INT32; + } + break; + case '6': + ADVANCE_IN_FORMAT_STRING(format); + if (*format == '4') { + ADVANCE_IN_FORMAT_STRING(format); + flags |= FLAGS_INT64; + } + break; + default: + break; + } + break; + } +#endif + case 'l': + flags |= FLAGS_LONG; + ADVANCE_IN_FORMAT_STRING(format); + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + ADVANCE_IN_FORMAT_STRING(format); + } + break; + case 'h': + flags |= FLAGS_SHORT; + ADVANCE_IN_FORMAT_STRING(format); + if (*format == 'h') { + flags |= FLAGS_CHAR; + ADVANCE_IN_FORMAT_STRING(format); + } + break; + case 't': + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + ADVANCE_IN_FORMAT_STRING(format); + break; + case 'j': + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + ADVANCE_IN_FORMAT_STRING(format); + break; + case 'z': + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + ADVANCE_IN_FORMAT_STRING(format); + break; + default: + break; + } + + // evaluate specifier + switch (*format) { + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + case 'b': { + + if (*format == 'd' || *format == 'i') { + flags |= FLAGS_SIGNED; + } + + numeric_base_t base; + if (*format == 'x' || *format == 'X') { + base = BASE_HEX; + } else if (*format == 'o') { + base = BASE_OCTAL; + } else if (*format == 'b') { + base = BASE_BINARY; + } else { + base = BASE_DECIMAL; + flags &= ~FLAGS_HASH; // decimal integers have no alternative presentation + } + + if (*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + format++; + // ignore '0' flag when precision is given + if (flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + if (flags & FLAGS_SIGNED) { + // A signed specifier: d, i or possibly I + bit size if enabled + + if (flags & FLAGS_LONG_LONG) { +#if PRINTF_SUPPORT_LONG_LONG + const long long value = va_arg(args, long long); + print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, + width, flags); +#endif + } else if (flags & FLAGS_LONG) { + const long value = va_arg(args, long); + print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, + width, flags); + } else { + // We never try to interpret the argument as something potentially-smaller than + // int, due to integer promotion rules: Even if the user passed a short int, + // short unsigned etc. - these will come in after promotion, as int's (or + // unsigned for the case of short unsigned when it has the same size as int) + const int value = (flags & FLAGS_CHAR) ? (signed char)va_arg(args, int) + : (flags & FLAGS_SHORT) ? (short int)va_arg(args, int) + : va_arg(args, int); + print_integer(output, ABS_FOR_PRINTING(value), value < 0, base, precision, + width, flags); + } + } else { + // An unsigned specifier: u, x, X, o, b + + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + + if (flags & FLAGS_LONG_LONG) { +#if PRINTF_SUPPORT_LONG_LONG + print_integer(output, (printf_unsigned_value_t)va_arg(args, unsigned long long), + false, base, precision, width, flags); +#endif + } else if (flags & FLAGS_LONG) { + print_integer(output, (printf_unsigned_value_t)va_arg(args, unsigned long), + false, base, precision, width, flags); + } else { + const unsigned int value = + (flags & FLAGS_CHAR) ? (unsigned char)va_arg(args, unsigned int) + : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(args, unsigned int) + : va_arg(args, unsigned int); + print_integer(output, (printf_unsigned_value_t)value, false, base, precision, + width, flags); + } + } + break; + } +#if PRINTF_SUPPORT_DECIMAL_SPECIFIERS + case 'f': + case 'F': + if (*format == 'F') + flags |= FLAGS_UPPERCASE; + print_floating_point(output, va_arg(args, double), precision, width, flags, + PRINTF_PREFER_DECIMAL); + format++; + break; +#endif +#if PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + case 'e': + case 'E': + case 'g': + case 'G': + if ((*format == 'g') || (*format == 'G')) + flags |= FLAGS_ADAPT_EXP; + if ((*format == 'E') || (*format == 'G')) + flags |= FLAGS_UPPERCASE; + print_floating_point(output, va_arg(args, double), precision, width, flags, + PRINTF_PREFER_EXPONENTIAL); + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS + case 'c': { + printf_size_t l = 1U; + // pre padding + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + putchar_via_gadget(output, ' '); + } + } + // char output + putchar_via_gadget(output, (char)va_arg(args, int)); + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + putchar_via_gadget(output, ' '); + } + } + format++; + break; + } + + case 's': { + const char *p = va_arg(args, char *); + if (p == NULL) { + out_rev_(output, ")llun(", 6, width, flags); + } else { + printf_size_t l = + strnlen_s_(p, precision ? precision : PRINTF_MAX_POSSIBLE_BUFFER_SIZE); + // pre padding + if (flags & FLAGS_PRECISION) { + l = (l < precision ? l : precision); + } + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + putchar_via_gadget(output, ' '); + } + } + // string output + while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision)) { + putchar_via_gadget(output, *(p++)); + --precision; + } + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + putchar_via_gadget(output, ' '); + } + } + } + format++; + break; + } + + case 'p': { + width = sizeof(void *) * 2U + 2; // 2 hex chars per byte + the "0x" prefix + flags |= FLAGS_ZEROPAD | FLAGS_POINTER; + uintptr_t value = (uintptr_t)va_arg(args, void *); + (value == (uintptr_t)NULL) ? out_rev_(output, ")lin(", 5, width, flags) + : print_integer(output, (printf_unsigned_value_t)value, + false, BASE_HEX, precision, width, flags); + format++; + break; + } + + case '%': + putchar_via_gadget(output, '%'); + format++; + break; + + // Many people prefer to disable support for %n, as it lets the caller + // engineer a write to an arbitrary location, of a value the caller + // effectively controls - which could be a security concern in some cases. +#if PRINTF_SUPPORT_WRITEBACK_SPECIFIER + case 'n': { + if (flags & FLAGS_CHAR) + *(va_arg(args, char *)) = (char)output->pos; + else if (flags & FLAGS_SHORT) + *(va_arg(args, short *)) = (short)output->pos; + else if (flags & FLAGS_LONG) + *(va_arg(args, long *)) = (long)output->pos; +#if PRINTF_SUPPORT_LONG_LONG + else if (flags & FLAGS_LONG_LONG) + *(va_arg(args, long long *)) = (long long int)output->pos; +#endif // PRINTF_SUPPORT_LONG_LONG + else + *(va_arg(args, int *)) = (int)output->pos; + format++; + break; + } +#endif // PRINTF_SUPPORT_WRITEBACK_SPECIFIER + + default: + putchar_via_gadget(output, *format); + format++; + break; + } + } +} + +// internal vsnprintf - used for implementing _all library functions +static int vsnprintf_impl(output_gadget_t *output, const char *format, va_list args) +{ + // Note: The library only calls vsnprintf_impl() with output->pos being 0. However, it is + // possible to call this function with a non-zero pos value for some "remedial printing". + format_string_loop(output, format, args); + + // termination + append_termination_with_gadget(output); + + // return written chars without terminating \0 + return (int)output->pos; +} + +/////////////////////////////////////////////////////////////////////////////// + +// int vprintf_(const char *format, va_list arg) +// { +// output_gadget_t gadget = extern_putchar_gadget(); +// return vsnprintf_impl(&gadget, format, arg); +// } + +int vsnprintf_(char *s, size_t n, const char *format, va_list arg) +{ + output_gadget_t gadget = buffer_gadget(s, n); + return vsnprintf_impl(&gadget, format, arg); +} + +int vsprintf_(char *s, const char *format, va_list arg) +{ + return vsnprintf_(s, PRINTF_MAX_POSSIBLE_BUFFER_SIZE, format, arg); +} + +int vfctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, + va_list arg) +{ + output_gadget_t gadget = function_gadget(out, extra_arg); + return vsnprintf_impl(&gadget, format, arg); +} + +// int printf_(const char *format, ...) +// { +// va_list args; +// va_start(args, format); +// const int ret = vprintf_(format, args); +// va_end(args); +// return ret; +// } + +int sprintf_(char *s, const char *format, ...) +{ + va_list args; + va_start(args, format); + const int ret = vsprintf_(s, format, args); + va_end(args); + return ret; +} + +int snprintf_(char *s, size_t n, const char *format, ...) +{ + va_list args; + va_start(args, format); + const int ret = vsnprintf_(s, n, format, args); + va_end(args); + return ret; +} + +int fctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, ...) +{ + va_list args; + va_start(args, format); + const int ret = vfctprintf(out, extra_arg, format, args); + va_end(args); + return ret; +} diff --git a/ulib/axlibc/c/printf.h b/ulib/axlibc/c/printf.h new file mode 100644 index 000000000..c6484e504 --- /dev/null +++ b/ulib/axlibc/c/printf.h @@ -0,0 +1,215 @@ +/** + * @author (c) Eyal Rozenberg + * 2021-2022, Haifa, Palestine/Israel + * @author (c) Marco Paland (info@paland.com) + * 2014-2019, PALANDesign Hannover, Germany + * + * @note Others have made smaller contributions to this file: see the + * contributors page at https://github.com/eyalroz/printf/graphs/contributors + * or ask one of the authors. + * + * @brief Small stand-alone implementation of the printf family of functions + * (`(v)printf`, `(v)s(n)printf` etc., geared towards use on embedded systems with + * a very limited resources. + * + * @note the implementations are thread-safe; re-entrant; use no functions from + * the standard library; and do not dynamically allocate any memory. + * + * @license The MIT License (MIT) + * + * 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 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. + */ + +#ifndef PRINTF_H_ +#define PRINTF_H_ + +#ifdef __cplusplus +#include +#include +extern "C" { +#else +#include +#include +#endif + +#ifdef __GNUC__ +#if ((__GNUC__ == 4 && __GNUC_MINOR__ >= 4) || __GNUC__ > 4) +#define ATTR_PRINTF(one_based_format_index, first_arg) \ + __attribute__((format(gnu_printf, (one_based_format_index), (first_arg)))) +#else +#define ATTR_PRINTF(one_based_format_index, first_arg) \ + __attribute__((format(printf, (one_based_format_index), (first_arg)))) +#endif +#define ATTR_VPRINTF(one_based_format_index) ATTR_PRINTF((one_based_format_index), 0) +#else +#define ATTR_PRINTF(one_based_format_index, first_arg) +#define ATTR_VPRINTF(one_based_format_index) +#endif + +#ifndef PRINTF_ALIAS_STANDARD_FUNCTION_NAMES +#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 0 +#endif + +#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD +#define printf_ printf +#define sprintf_ sprintf +#define vsprintf_ vsprintf +#define snprintf_ snprintf +#define vsnprintf_ vsnprintf +#define vprintf_ vprintf +#endif + +// If you want to include this implementation file directly rather than +// link against, this will let you control the functions' visibility, +// e.g. make them static so as not to clash with other objects also +// using them. +#ifndef PRINTF_VISIBILITY +#define PRINTF_VISIBILITY +#endif + +/** + * Prints/send a single character to some opaque output entity + * + * @note This function is not implemented by the library, only declared; you must provide an + * implementation if you wish to use the @ref printf / @ref vprintf function (and possibly + * for linking against the library, if your toolchain does not support discarding unused functions) + * + * @note The output could be as simple as a wrapper for the `write()` system call on a Unix-like + * system, or even libc's @ref putchar , for replicating actual functionality of libc's @ref printf + * function; but on an embedded system it may involve interaction with a special output device, + * like a UART, etc. + * + * @note in libc's @ref putchar, the parameter type is an int; this was intended to support the + * representation of either a proper character or EOF in a variable - but this is really not + * meaningful to pass into @ref putchar and is discouraged today. See further discussion in: + * @link https://stackoverflow.com/q/17452847/1593077 + * + * @param c the single character to print + */ +// PRINTF_VISIBILITY +// void putchar_(char c); + +/** + * An implementation of the C standard's printf/vprintf + * + * @note you must implement a @ref putchar_ function for using this function - it invokes @ref + * putchar_ rather than directly performing any I/O (which insulates it from any dependence on the + * operating system and external libraries). + * + * @param format A string specifying the format of the output, with %-marked specifiers of how to + * interpret additional arguments. + * @param arg Additional arguments to the function, one for each %-specifier in @p format string + * @return The number of characters written into @p s, not counting the terminating null character + */ +///@{ +// PRINTF_VISIBILITY +// int printf_(const char* format, ...) ATTR_PRINTF(1, 2); +// PRINTF_VISIBILITY +// int vprintf_(const char* format, va_list arg) ATTR_VPRINTF(1); +///@} + +/** + * An implementation of the C standard's sprintf/vsprintf + * + * @note For security considerations (the potential for exceeding the buffer bounds), please + * consider using the size-constrained variant, @ref snprintf / @ref vsnprintf , instead. + * + * @param s An array in which to store the formatted string. It must be large enough to fit the + * formatted output! + * @param format A string specifying the format of the output, with %-marked specifiers of how to + * interpret additional arguments. + * @param arg Additional arguments to the function, one for each specifier in @p format + * @return The number of characters written into @p s, not counting the terminating null character + */ +///@{ +PRINTF_VISIBILITY +int sprintf_(char *s, const char *format, ...) ATTR_PRINTF(2, 3); +PRINTF_VISIBILITY +int vsprintf_(char *s, const char *format, va_list arg) ATTR_VPRINTF(2); +///@} + +/** + * An implementation of the C standard's snprintf/vsnprintf + * + * @param s An array in which to store the formatted string. It must be large enough to fit either + * the entire formatted output, or at least @p n characters. Alternatively, it can be NULL, in which + * case nothing will be printed, and only the number of characters which _could_ have been printed + * is tallied and returned. + * @param n The maximum number of characters to write to the array, including a terminating null + * character + * @param format A string specifying the format of the output, with %-marked specifiers of how to + * interpret additional arguments. + * @param arg Additional arguments to the function, one for each specifier in @p format + * @return The number of characters that COULD have been written into @p s, not counting the + * terminating null character. A value equal or larger than @p n indicates truncation. Only when the + * returned value is non-negative and less than @p n, the null-terminated string has been fully and + * successfully printed. + */ +///@{ +PRINTF_VISIBILITY +int snprintf_(char *s, size_t count, const char *format, ...) ATTR_PRINTF(3, 4); +PRINTF_VISIBILITY +int vsnprintf_(char *s, size_t count, const char *format, va_list arg) ATTR_VPRINTF(3); +///@} + +/** + * printf/vprintf with user-specified output function + * + * An alternative to @ref printf_, in which the output function is specified dynamically + * (rather than @ref putchar_ being used) + * + * @param out An output function which takes one character and a type-erased additional parameters + * @param extra_arg The type-erased argument to pass to the output function @p out with each call + * @param format A string specifying the format of the output, with %-marked specifiers of how to + * interpret additional arguments. + * @param arg Additional arguments to the function, one for each specifier in @p format + * @return The number of characters for which the output f unction was invoked, not counting the + * terminating null character + * + */ +PRINTF_VISIBILITY +int fctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, ...) + ATTR_PRINTF(3, 4); +PRINTF_VISIBILITY +int vfctprintf(void (*out)(char c, void *extra_arg), void *extra_arg, const char *format, + va_list arg) ATTR_VPRINTF(3); + +#ifdef __cplusplus +} // extern "C" +#endif + +#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_HARD +#undef printf_ +#undef sprintf_ +#undef vsprintf_ +#undef snprintf_ +#undef vsnprintf_ +#undef vprintf_ +#else +#if PRINTF_ALIAS_STANDARD_FUNCTION_NAMES_SOFT +#define printf printf_ +#define sprintf sprintf_ +#define vsprintf vsprintf_ +#define snprintf snprintf_ +#define vsnprintf vsnprintf_ +#define vprintf vprintf_ +#endif +#endif + +#endif // PRINTF_H_ diff --git a/ulib/axlibc/c/printf_config.h b/ulib/axlibc/c/printf_config.h new file mode 100644 index 000000000..bd860a754 --- /dev/null +++ b/ulib/axlibc/c/printf_config.h @@ -0,0 +1,14 @@ +#ifndef PRINTF_CONFIG_H +#define PRINTF_CONFIG_H + +#define PRINTF_ALIAS_STANDARD_FUNCTION_NAMES 1 + +#ifndef AX_CONFIG_FP_SIMD + +#define PRINTF_SUPPORT_DECIMAL_SPECIFIERS 0 + +#define PRINTF_SUPPORT_EXPONENTIAL_SPECIFIERS 0 + +#endif // AX_CONFIG_FP_SIMD + +#endif // PRINTF_CONFIG_H diff --git a/ulib/axlibc/c/pthread.c b/ulib/axlibc/c/pthread.c new file mode 100644 index 000000000..74fb1faf1 --- /dev/null +++ b/ulib/axlibc/c/pthread.c @@ -0,0 +1,110 @@ +#ifdef AX_CONFIG_MULTITASK + +#include +#include +#include +#include +#include + +int pthread_setcancelstate(int new, int *old) +{ + unimplemented(); + return 0; +} + +int pthread_setcanceltype(int new, int *old) +{ + unimplemented(); + return 0; +} + +// TODO +void pthread_testcancel(void) +{ + unimplemented(); + return; +} + +// TODO +int pthread_cancel(pthread_t t) +{ + unimplemented(); + return 0; +} + +// TODO +int pthread_mutex_trylock(pthread_mutex_t *m) +{ + unimplemented(); + return 0; +} + +// TODO +int pthread_setname_np(pthread_t thread, const char *name) +{ + unimplemented(); + return 0; +} + +int pthread_cond_init(pthread_cond_t *restrict c, const pthread_condattr_t *restrict a) +{ + *c = (pthread_cond_t){0}; + if (a) { + c->_c_clock = a->__attr & 0x7fffffff; + if (a->__attr >> 31) + c->_c_shared = (void *)-1; + } + return 0; +} + +// TODO +int pthread_cond_signal(pthread_cond_t *__cond) +{ + unimplemented(); + return 0; +} + +// TODO +int pthread_cond_wait(pthread_cond_t *__restrict__ __cond, pthread_mutex_t *__restrict__ __mutex) +{ + unimplemented(); + return 0; +} + +// TODO +int pthread_cond_broadcast(pthread_cond_t *c) +{ + unimplemented(); + return 0; +} + +#define DEFAULT_STACK_SIZE 131072 +#define DEFAULT_GUARD_SIZE 8192 + +// TODO +int pthread_attr_init(pthread_attr_t *a) +{ + *a = (pthread_attr_t){0}; + // __acquire_ptc(); + a->_a_stacksize = DEFAULT_STACK_SIZE; + a->_a_guardsize = DEFAULT_GUARD_SIZE; + // __release_ptc(); + return 0; +} + +int pthread_attr_getstacksize(const pthread_attr_t *restrict a, size_t *restrict size) +{ + *size = a->_a_stacksize; + return 0; +} + +int pthread_attr_setstacksize(pthread_attr_t *a, size_t size) +{ + if (size - PTHREAD_STACK_MIN > SIZE_MAX / 4) + return EINVAL; + a->_a_stackaddr = 0; + a->_a_stacksize = size; + return 0; +} + +#endif // AX_CONFIG_MULTITASK diff --git a/ulib/axlibc/c/pwd.c b/ulib/axlibc/c/pwd.c new file mode 100644 index 000000000..61f32f4ae --- /dev/null +++ b/ulib/axlibc/c/pwd.c @@ -0,0 +1,14 @@ +#include +#include + +int getpwnam_r(const char *name, struct passwd *pw, char *buf, size_t size, struct passwd **res) +{ + unimplemented(); + return 0; +} + +int getpwuid_r(uid_t uid, struct passwd *pw, char *buf, size_t size, struct passwd **res) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/resource.c b/ulib/axlibc/c/resource.c new file mode 100644 index 000000000..3e34624cb --- /dev/null +++ b/ulib/axlibc/c/resource.c @@ -0,0 +1,10 @@ +#include +#include +#include + +// TODO +int getrusage(int __who, struct rusage *__usage) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/sched.c b/ulib/axlibc/c/sched.c new file mode 100644 index 000000000..21203c465 --- /dev/null +++ b/ulib/axlibc/c/sched.c @@ -0,0 +1,9 @@ +#include +#include + +// TODO +int sched_setaffinity(pid_t __pid, size_t __cpusetsize, const cpu_set_t *__cpuset) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/select.c b/ulib/axlibc/c/select.c new file mode 100644 index 000000000..d20d5f680 --- /dev/null +++ b/ulib/axlibc/c/select.c @@ -0,0 +1,17 @@ +#ifdef AX_CONFIG_SELECT + +#include +#include +#include +#include +#include + +int pselect(int n, fd_set *restrict rfds, fd_set *restrict wfds, fd_set *restrict efds, + const struct timespec *restrict ts, const sigset_t *restrict mask) +{ + struct timeval tv = {ts->tv_sec, ts->tv_nsec / 1000}; + select(n, rfds, wfds, efds, &tv); + return 0; +} + +#endif // AX_CONFIG_SELECT diff --git a/ulib/axlibc/c/signal.c b/ulib/axlibc/c/signal.c new file mode 100644 index 000000000..de2164767 --- /dev/null +++ b/ulib/axlibc/c/signal.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include + +int sigaction_helper(int signum, const struct sigaction *act, struct sigaction *oldact, + size_t sigsetsize) +{ + if (signum == SIGKILL || signum == SIGSTOP) + return -EINVAL; + + if (oldact) + *oldact = (struct sigaction){0}; + + return 0; +} + +void (*signal(int signum, void (*handler)(int)))(int) +{ + struct sigaction old; + struct sigaction act = { + .sa_handler = handler, .sa_flags = SA_RESTART, /* BSD signal semantics */ + }; + + if (sigaction_helper(signum, &act, &old, sizeof(sigset_t)) < 0) + return SIG_ERR; + + return (old.sa_flags & SA_SIGINFO) ? NULL : old.sa_handler; +} + +int sigaction(int sig, const struct sigaction *restrict act, struct sigaction *restrict oact) +{ + return sigaction_helper(sig, act, oact, sizeof(sigset_t)); +} + +// TODO +int kill(pid_t __pid, int __sig) +{ + unimplemented(); + return 0; +} + +int sigemptyset(sigset_t *set) +{ + set->__bits[0] = 0; + if (sizeof(long) == 4 || _NSIG > 65) + set->__bits[1] = 0; + if (sizeof(long) == 4 && _NSIG > 65) { + set->__bits[2] = 0; + set->__bits[3] = 0; + } + return 0; +} + +// TODO +int raise(int __sig) +{ + unimplemented(); + return 0; +} + +int sigaddset(sigset_t *set, int sig) +{ + unsigned s = sig - 1; + if (s >= _NSIG - 1 || sig - 32U < 3) { + errno = EINVAL; + return -1; + } + set->__bits[s / 8 / sizeof *set->__bits] |= 1UL << (s & (8 * sizeof *set->__bits - 1)); + return 0; +} + +// TODO +int pthread_sigmask(int __how, const sigset_t *restrict __newmask, sigset_t *restrict __oldmask) +{ + unimplemented(); + return 0; +} + +#ifdef AX_CONFIG_MULTITASK +// TODO +int pthread_kill(pthread_t t, int sig) +{ + unimplemented(); + return 0; +} +#endif diff --git a/ulib/axlibc/c/socket.c b/ulib/axlibc/c/socket.c new file mode 100644 index 000000000..677e43198 --- /dev/null +++ b/ulib/axlibc/c/socket.c @@ -0,0 +1,47 @@ +#ifdef AX_CONFIG_NET + +#include +#include +#include +#include +#include + +int accept4(int fd, struct sockaddr *restrict addr, socklen_t *restrict len, int flg) +{ + if (!flg) + return accept(fd, addr, len); + if (flg & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) { + errno = EINVAL; + return -1; + } + int ret = accept(fd, addr, len); + if (ret < 0) + return ret; + if (flg & SOCK_CLOEXEC) + fcntl(ret, F_SETFD, FD_CLOEXEC); + if (flg & SOCK_NONBLOCK) + fcntl(ret, F_SETFL, O_NONBLOCK); + return ret; +} + +int getsockopt(int fd, int level, int optname, void *restrict optval, socklen_t *restrict optlen) +{ + unimplemented(); + return -1; +} + +int setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) +{ + unimplemented("fd: %d, level: %d, optname: %d, optval: %d, optlen: %d", fd, level, optname, + *(int *)optval, optlen); + return 0; +} + +// TODO +ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) +{ + unimplemented(); + return 0; +} + +#endif // AX_CONFIG_NET diff --git a/ulib/axlibc/c/stat.c b/ulib/axlibc/c/stat.c new file mode 100644 index 000000000..27ff3d814 --- /dev/null +++ b/ulib/axlibc/c/stat.c @@ -0,0 +1,38 @@ +#include +#include +#include + +// TODO: +int fchmod(int fd, mode_t mode) +{ + unimplemented(); + return 0; +} + +// TODO: +int mkdir(const char *path, mode_t mode) +{ + unimplemented(); + return 0; +} + +// TODO +int chmod(const char *path, mode_t mode) +{ + unimplemented(); + return 0; +} + +// TODO +mode_t umask(mode_t mask) +{ + unimplemented("mask: %d", mask); + return 0; +} + +// TODO +int fstatat(int fd, const char *restrict path, struct stat *restrict st, int flag) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/stdio.c b/ulib/axlibc/c/stdio.c new file mode 100644 index 000000000..03ea75cf3 --- /dev/null +++ b/ulib/axlibc/c/stdio.c @@ -0,0 +1,412 @@ +#include "printf.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// LOCK used by `puts()` +#ifdef AX_CONFIG_MULTITASK +#include +static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +#endif + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +FILE __stdin_FILE = {.fd = 0, .buffer_len = 0}; + +FILE __stdout_FILE = {.fd = 1, .buffer_len = 0}; + +FILE __stderr_FILE = {.fd = 2, .buffer_len = 0}; + +FILE *const stdin = &__stdin_FILE; +FILE *const stdout = &__stdout_FILE; +FILE *const stderr = &__stderr_FILE; + +// Returns: number of chars written, negative for failure +// Warn: buffer_len[f] will not be changed +static int __write_buffer(FILE *f) +{ + int r = 0; + if (f->buffer_len == 0) + return 0; + r = write(f->fd, f->buf, f->buffer_len); + return r; +} + +// Clear buffer_len[f] +static void __clear_buffer(FILE *f) +{ + f->buffer_len = 0; +} + +static int __fflush(FILE *f) +{ + int r = __write_buffer(f); + __clear_buffer(f); + return r >= 0 ? 0 : r; +} + +static int out(FILE *f, const char *s, size_t l) +{ + int ret = 0; + for (size_t i = 0; i < l; i++) { + char c = s[i]; + f->buf[f->buffer_len++] = c; + if (f->buffer_len == FILE_BUF_SIZE || c == '\n') { + int r = __write_buffer(f); + __clear_buffer(f); + if (r < 0) + return r; + if (r < f->buffer_len) + return ret + r; + ret += r; + } + } + return ret; +} + +int getchar(void) +{ + unimplemented(); + return 0; +} + +int fflush(FILE *f) +{ + return __fflush(f); +} + +static inline int do_putc(int c, FILE *f) +{ + char byte = c; + return out(f, &byte, 1); +} + +int fputc(int c, FILE *f) +{ + return do_putc(c, f); +} + +int putc(int c, FILE *f) +{ + return do_putc(c, f); +} + +int putchar(int c) +{ + return do_putc(c, stdout); +} + +int puts(const char *s) +{ +#ifdef AX_CONFIG_MULTITASK + pthread_mutex_lock(&lock); +#endif + + int r = write(1, (const void *)s, strlen(s)); + char brk[1] = {'\n'}; + write(1, (const void *)brk, 1); + +#ifdef AX_CONFIG_MULTITASK + pthread_mutex_unlock(&lock); +#endif + + return r; +} + +void perror(const char *msg) +{ + FILE *f = stderr; + char *errstr = strerror(errno); + + if (msg && *msg) { + out(f, msg, strlen(msg)); + out(f, ": ", 2); + } + out(f, errstr, strlen(errstr)); + out(f, "\n", 1); +} + +static void __out_wrapper(char c, void *arg) +{ + out(arg, &c, 1); +} + +int printf(const char *restrict fmt, ...) +{ + int ret; + va_list ap; + va_start(ap, fmt); + ret = vfprintf(stdout, fmt, ap); + va_end(ap); + return ret; +} + +int fprintf(FILE *restrict f, const char *restrict fmt, ...) +{ + int ret; + va_list ap; + va_start(ap, fmt); + ret = vfprintf(f, fmt, ap); + va_end(ap); + return ret; +} + +int vfprintf(FILE *restrict f, const char *restrict fmt, va_list ap) +{ + return vfctprintf(__out_wrapper, f, fmt, ap); +} + +// TODO +int sscanf(const char *restrict __s, const char *restrict __format, ...) +{ + unimplemented(); + return 0; +} + +#ifdef AX_CONFIG_FS + +int __fmodeflags(const char *mode) +{ + int flags; + if (strchr(mode, '+')) + flags = O_RDWR; + else if (*mode == 'r') + flags = O_RDONLY; + else + flags = O_WRONLY; + if (strchr(mode, 'x')) + flags |= O_EXCL; + if (strchr(mode, 'e')) + flags |= O_CLOEXEC; + if (*mode != 'r') + flags |= O_CREAT; + if (*mode == 'w') + flags |= O_TRUNC; + if (*mode == 'a') + flags |= O_APPEND; + return flags; +} + +FILE *fopen(const char *filename, const char *mode) +{ + FILE *f; + int flags; + + if (!strchr("rwa", *mode)) { + errno = EINVAL; + return 0; + } + + f = (FILE *)malloc(sizeof(FILE)); + + flags = __fmodeflags(mode); + // TODO: currently mode is unused in ax_open + int fd = open(filename, flags, 0666); + if (fd < 0) + return NULL; + f->fd = fd; + + return f; +} + +char *fgets(char *restrict s, int n, FILE *restrict f) +{ + if (n == 0) + return NULL; + if (n == 1) { + *s = '\0'; + return s; + } + + int cnt = 0; + while (cnt < n - 1) { + char c; + if (read(f->fd, (void *)&c, 1) > 0) { + if (c != '\n') + s[cnt++] = c; + else + break; + } else + break; + } + s[cnt] = '\0'; + return s; +} + +size_t fread(void *restrict destv, size_t size, size_t nmemb, FILE *restrict f) +{ + size_t total = size * nmemb; + size_t read_len = 0; + size_t len = 0; + do { + len = read(f->fd, destv + read_len, total - read_len); + if (len < 0) + break; + read_len += len; + } while (len > 0); + return read_len == size * nmemb ? nmemb : read_len / size; +} + +size_t fwrite(const void *restrict src, size_t size, size_t nmemb, FILE *restrict f) +{ + size_t total = size * nmemb; + size_t write_len = 0; + size_t len = 0; + do { + len = write(f->fd, src + write_len, total - write_len); + if (len < 0) + break; + write_len += len; + } while (len > 0); + return write_len == size * nmemb ? nmemb : write_len / size; +} + +int fputs(const char *restrict s, FILE *restrict f) +{ + size_t l = strlen(s); + return (fwrite(s, 1, l, f) == l) - 1; +} + +int fclose(FILE *f) +{ + return close(f->fd); +} + +int fileno(FILE *f) +{ + return f->fd; +} + +int feof(FILE *f) +{ + unimplemented(); + return 0; +} + +// TODO +int fseek(FILE *__stream, long __off, int __whence) +{ + unimplemented(); + return 0; +} + +// TODO +off_t ftello(FILE *__stream) +{ + unimplemented(); + return 0; +} + +// TODO +char *tmpnam(char *buf) +{ + unimplemented(); + return 0; +} + +// TODO +void clearerr(FILE *f) +{ + unimplemented(); +} + +// TODO +int ferror(FILE *f) +{ + unimplemented(); + return 0; +} + +// TODO +FILE *freopen(const char *restrict filename, const char *restrict mode, FILE *restrict f) +{ + unimplemented(); + return 0; +} + +// TODO +int fscanf(FILE *restrict f, const char *restrict fmt, ...) +{ + unimplemented(); + return 0; +} + +// TODO +long ftell(FILE *f) +{ + unimplemented(); + return 0; +} + +int getc(FILE *f) +{ + unimplemented(); + return 0; +} + +// TODO +int remove(const char *path) +{ + unimplemented(); + return 0; +} + +// TODO +int setvbuf(FILE *restrict f, char *restrict buf, int type, size_t size) +{ + unimplemented(); + return 0; +} + +// TODO +FILE *tmpfile(void) +{ + unimplemented(); + return NULL; +} + +int ungetc(int c, FILE *f) +{ + unimplemented(); + return 0; +} + +ssize_t getdelim(char **restrict s, size_t *restrict n, int delim, FILE *restrict f) +{ + unimplemented(); + return 0; +} + +ssize_t getline(char **restrict s, size_t *restrict n, FILE *restrict f) +{ + return getdelim(s, n, '\n', f); +} + +int __uflow(FILE *f) +{ + unimplemented(); + return 0; +} + +int getc_unlocked(FILE *f) +{ + unimplemented(); + return 0; +} + +FILE *fdopen(int fd, const char *mode) +{ + unimplemented(); + return NULL; +} + +#endif // AX_CONFIG_FS diff --git a/ulib/axlibc/c/stdlib.c b/ulib/axlibc/c/stdlib.c new file mode 100644 index 000000000..ef8a82368 --- /dev/null +++ b/ulib/axlibc/c/stdlib.c @@ -0,0 +1,385 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +char *program_invocation_short_name = "dummy"; +char *program_invocation_name = "dummy"; + +#define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) + +void srandom(unsigned int s) +{ + srand(s); +} + +#ifdef AX_CONFIG_ALLOC + +void *calloc(size_t m, size_t n) +{ + void *mem = malloc(m * n); + + return memset(mem, 0, n * m); +} + +void *realloc(void *memblock, size_t size) +{ + if (!memblock) + return malloc(size); + + size_t o_size = *(size_t *)(memblock - 8); + + void *mem = malloc(size); + + for (int i = 0; i < (o_size < size ? o_size : size); i++) + ((char *)mem)[i] = ((char *)memblock)[i]; + + free(memblock); + return mem; +} + +#endif // AX_CONFIG_ALLOC + +long long llabs(long long a) +{ + return a > 0 ? a : -a; +} + +int abs(int a) +{ + return a > 0 ? a : -a; +} + +long long atoll(const char *s) +{ + long long n = 0; + int neg = 0; + while (isspace(*s)) s++; + switch (*s) { + case '-': + neg = 1; + case '+': + s++; + } + /* Compute n as a negative number to avoid overflow on LLONG_MIN */ + while (isdigit(*s)) n = 10 * n - (*s++ - '0'); + return neg ? n : -n; +} + +long strtol(const char *restrict nptr, char **restrict endptr, int base) +{ + const char *s; + unsigned long acc; + unsigned char c; + unsigned long qbase, cutoff; + int neg, any, cutlim; + + s = nptr; + if (base < 0 || base == 1 || base > 36) { + errno = EINVAL; + any = 0; + acc = 0; + goto exit; + } + + do { + c = *s++; + } while (isspace(c)); + if (c == '-') { + neg = 1; + c = *s++; + } else { + neg = 0; + if (c == '+') + c = *s++; + } + if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = c == '0' ? 8 : 10; + + qbase = (unsigned int)base; + cutoff = neg ? (unsigned long)LONG_MAX - (unsigned long)(LONG_MIN + LONG_MAX) : LONG_MAX; + cutlim = cutoff % qbase; + cutoff /= qbase; + for (acc = 0, any = 0;; c = *s++) { + if (!isascii(c)) + break; + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + break; + if (c >= base) + break; + if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) + any = -1; + else { + any = 1; + acc *= qbase; + acc += c; + } + } + + if (any < 0) { + acc = neg ? LONG_MIN : LONG_MAX; + errno = ERANGE; + } else if (neg) + acc = -acc; + +exit: + if (endptr != 0) + *endptr = __DECONST(char *, any ? s - 1 : nptr); + return acc; +} + +unsigned long strtoul(const char *nptr, char **endptr, int base) +{ + const char *s = nptr; + unsigned long acc; + unsigned char c; + unsigned long cutoff; + int neg = 0, any, cutlim; + + if (base < 0 || base == 1 || base > 36) { + errno = EINVAL; + any = 0; + acc = 0; + goto exit; + } + + do { + c = *s++; + } while (isspace(c)); + if (c == '-') { + neg = 1; + c = *s++; + } else if (c == '+') + c = *s++; + if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = c == '0' ? 8 : 10; + cutoff = (unsigned long)ULONG_MAX / (unsigned long)base; + cutlim = (unsigned long)ULONG_MAX % (unsigned long)base; + + for (acc = 0, any = 0;; c = *s++) { + if (!isascii(c)) + break; + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + break; + if (c >= base) + break; + if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) + any = -1; + else { + any = 1; + acc *= base; + acc += c; + } + } + if (any < 0) { + acc = ULONG_MAX; + errno = ERANGE; + } else if (neg) + acc = -acc; +exit: + if (endptr != 0) + *endptr = __DECONST(char *, any ? s - 1 : nptr); + return acc; +} + +long long strtoll(const char *nptr, char **endptr, int base) +{ + const char *s; + unsigned long long acc; + unsigned char c; + unsigned long long qbase, cutoff; + int neg, any, cutlim; + + s = nptr; + if (base < 0 || base == 1 || base > 36) { + errno = EINVAL; + any = 0; + acc = 0; + goto exit; + } + + do { + c = *s++; + } while (isspace(c)); + if (c == '-') { + neg = 1; + c = *s++; + } else { + neg = 0; + if (c == '+') + c = *s++; + } + if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = c == '0' ? 8 : 10; + + qbase = (unsigned int)base; + cutoff = neg ? (unsigned long long)LLONG_MAX - (unsigned long long)(LLONG_MIN + LLONG_MAX) + : LLONG_MAX; + cutlim = cutoff % qbase; + cutoff /= qbase; + for (acc = 0, any = 0;; c = *s++) { + if (!isascii(c)) + break; + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + break; + if (c >= base) + break; + if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) + any = -1; + else { + any = 1; + acc *= qbase; + acc += c; + } + } + + if (any < 0) { + errno = ERANGE; + acc = neg ? LLONG_MIN : LLONG_MAX; + } else if (neg) + acc = -acc; + +exit: + if (endptr != 0) + *endptr = __DECONST(char *, any ? s - 1 : nptr); + return acc; +} + +unsigned long long strtoull(const char *nptr, char **endptr, int base) +{ + const char *s = nptr; + unsigned long long acc; + unsigned char c; + unsigned long long qbase, cutoff; + int neg, any, cutlim; + + if (base < 0 || base == 1 || base > 36) { + errno = EINVAL; + any = 0; + acc = 0; + goto exit; + } + + do { + c = *s++; + } while (isspace(c)); + if (c == '-') { + neg = 1; + c = *s++; + } else { + neg = 0; + if (c == '+') + c = *s++; + } + if ((base == 0 || base == 16) && c == '0' && (*s == 'x' || *s == 'X')) { + c = s[1]; + s += 2; + base = 16; + } + if (base == 0) + base = c == '0' ? 8 : 10; + + qbase = (unsigned int)base; + cutoff = (unsigned long long)ULLONG_MAX / qbase; + cutlim = (unsigned long long)ULLONG_MAX % qbase; + for (acc = 0, any = 0;; c = *s++) { + if (!isascii(c)) + break; + if (isdigit(c)) + c -= '0'; + else if (isalpha(c)) + c -= isupper(c) ? 'A' - 10 : 'a' - 10; + else + break; + if (c >= base) + break; + if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) + any = -1; + else { + any = 1; + acc *= qbase; + acc += c; + } + } + if (any < 0) { + errno = ERANGE; + acc = ULLONG_MAX; + } else if (neg) + acc = -acc; + +exit: + if (endptr != 0) + *endptr = __DECONST(char *, any ? s - 1 : nptr); + return acc; +} + +#ifdef AX_CONFIG_FP_SIMD + +// TODO: precision may not be enough +long double strtold(const char *restrict s, char **restrict p) +{ + return (long double)strtod(s, p); +} + +#endif // AX_CONFIG_FP_SIMD + +typedef int (*cmpfun)(const void *, const void *); + +// TODO +void qsort(void *base, size_t nel, size_t width, cmpfun cmp) +{ + unimplemented(); + return; +} + +// TODO +int mkstemp(char *__template) +{ + unimplemented(); + return 0; +} + +// TODO +int mkostemp(char *__template, int __flags) +{ + unimplemented(); + return 0; +} + +// TODO +int system(const char *cmd) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/string.c b/ulib/axlibc/c/string.c new file mode 100644 index 000000000..2c56d7e92 --- /dev/null +++ b/ulib/axlibc/c/string.c @@ -0,0 +1,478 @@ +#include +#include +#include +#include +#include +#include + +size_t strlen(const char *s) +{ + const char *a = s; + for (; *s; s++) + ; + return s - a; +} + +size_t strnlen(const char *s, size_t n) +{ + const char *p = memchr(s, 0, n); + return p ? p - s : n; +} + +int atoi(const char *s) +{ + int n = 0, neg = 0; + while (isspace(*s)) s++; + switch (*s) { + case '-': + neg = 1; + case '+': + s++; + } + /* Compute n as a negative number to avoid overflow on INT_MIN */ + while (isdigit(*s)) n = 10 * n - (*s++ - '0'); + return neg ? n : -n; +} + +void *memchr(const void *src, int c, size_t n) +{ + const unsigned char *s = src; + c = (unsigned char)c; + for (; n && *s != c; s++, n--) + ; + return n ? (void *)s : 0; +} + +void *memset(void *dest, int c, size_t n) +{ + unsigned char *s = dest; + size_t k; + + /* Fill head and tail with minimal branching. Each + * conditional ensures that all the subsequently used + * offsets are well-defined and in the dest region. */ + + if (!n) + return dest; + s[0] = c; + s[n - 1] = c; + if (n <= 2) + return dest; + s[1] = c; + s[2] = c; + s[n - 2] = c; + s[n - 3] = c; + if (n <= 6) + return dest; + s[3] = c; + s[n - 4] = c; + if (n <= 8) + return dest; + + /* Advance pointer to align it at a 4-byte boundary, + * and truncate n to a multiple of 4. The previous code + * already took care of any head/tail that get cut off + * by the alignment. */ + + k = -(uintptr_t)s & 3; + s += k; + n -= k; + n &= -4; + + /* Pure C fallback with no aliasing violations. */ + for (; n; n--, s++) *s = c; + + return dest; +} + +char *strcpy(char *restrict d, const char *restrict s) +{ + for (; (*d = *s); s++, d++) + ; + return d; +} + +char *strncpy(char *restrict d, const char *restrict s, size_t n) +{ + for (; n && (*d = *s); n--, s++, d++) + ; + return d; +} + +char *strcat(char *restrict d, const char *restrict s) +{ + strcpy(d + strlen(d), s); + return d; +} + +char *strncat(char *restrict d, const char *restrict s, size_t n) +{ + char *a = d; + d += strlen(d); + while (n && *s) n--, *d++ = *s++; + *d++ = 0; + return a; +} + +int strcmp(const char *l, const char *r) +{ + for (; *l == *r && *l; l++, r++) + ; + return *(unsigned char *)l - *(unsigned char *)r; +} + +int strncmp(const char *_l, const char *_r, size_t n) +{ + const unsigned char *l = (void *)_l, *r = (void *)_r; + if (!n--) + return 0; + for (; *l && *r && n && *l == *r; l++, r++, n--) + ; + return *l - *r; +} + +int strcoll(const char *l, const char *r) +{ + return strcmp(l, r); +} + +#define BITOP(a, b, op) \ + ((a)[(size_t)(b) / (8 * sizeof *(a))] op(size_t) 1 << ((size_t)(b) % (8 * sizeof *(a)))) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +size_t strcspn(const char *s1, const char *s2) +{ + const char *a = s1; + size_t byteset[32 / sizeof(size_t)]; + + if (!s2[0] || !s2[1]) { + for (; *s1 != *s2; s1++) return s1 - a; + } + memset(byteset, 0, sizeof byteset); + + for (; *s2 != '\0'; s2++) BITOP(byteset, *(unsigned char *)s2, |=); + for (; *s1 && !(BITOP(byteset, *(unsigned char *)s1, &)); s1++) + ; + + return s1 - a; +} + +size_t strspn(const char *s, const char *c) +{ + const char *a = s; + size_t byteset[32 / sizeof(size_t)] = {0}; + + if (!c[0]) + return 0; + if (!c[1]) { + for (; *s == *c; s++) + ; + return s - a; + } + + for (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++) + ; + for (; *s && BITOP(byteset, *(unsigned char *)s, &); s++) + ; + return s - a; +} + +char *strpbrk(const char *s, const char *b) +{ + s += strcspn(s, b); + return *s ? (char *)s : 0; +} + +char *strchrnul(const char *s, int c) +{ + c = (unsigned char)c; + if (!c) + return (char *)s + strlen(s); + + for (; *s && *(unsigned char *)s != c; s++) + ; + return (char *)s; +} + +char *strchr(const char *s, int c) +{ + while (*s != c && *s != '\0') s++; + + if (*s == c) { + return (char *)s; + } else { + return NULL; + } +} + +char *strrchr(const char *s, int c) +{ + char *isCharFind = NULL; + if (s != NULL) { + do { + if (*s == (char)c) { + isCharFind = (char *)s; + } + } while (*s++); + } + return isCharFind; +} + +int strerror_r(int err, char *buf, size_t buflen) +{ + char *msg = strerror(err); + size_t l = strlen(msg); + if (l >= buflen) { + if (buflen) { + memcpy(buf, msg, buflen - 1); + buf[buflen - 1] = 0; + } + return ERANGE; + } + memcpy(buf, msg, l + 1); + return 0; +} + +void *memcpy(void *restrict dest, const void *restrict src, size_t n) +{ + unsigned char *d = dest; + const unsigned char *s = src; + for (; n; n--) *d++ = *s++; + return dest; +} + +void *memmove(void *dest, const void *src, size_t n) +{ + char *d = dest; + const char *s = src; + + if (d == s) + return d; + if ((uintptr_t)s - (uintptr_t)d - n <= -2 * n) + return memcpy(d, s, n); + + if (d < s) { + for (; n; n--) *d++ = *s++; + } else { + while (n) n--, d[n] = s[n]; + } + + return dest; +} + +int memcmp(const void *vl, const void *vr, size_t n) +{ + const unsigned char *l = vl, *r = vr; + for (; n && *l == *r; n--, l++, r++) + ; + return n ? *l - *r : 0; +} + +int strcasecmp(const char *_l, const char *_r) +{ + const unsigned char *l = (void *)_l, *r = (void *)_r; + for (; *l && *r && (*l == *r || tolower(*l) == tolower(*r)); l++, r++) + ; + return tolower(*l) - tolower(*r); +} + +int strncasecmp(const char *_l, const char *_r, size_t n) +{ + const unsigned char *l = (void *)_l, *r = (void *)_r; + if (!n--) + return 0; + for (; *l && *r && n && (*l == *r || tolower(*l) == tolower(*r)); l++, r++, n--) + ; + return tolower(*l) - tolower(*r); +} + +// `strstr` helper function +static char *twobyte_strstr(const unsigned char *h, const unsigned char *n) +{ + uint16_t nw = n[0] << 8 | n[1], hw = h[0] << 8 | h[1]; + for (h++; *h && hw != nw; hw = hw << 8 | *++h) + ; + return *h ? (char *)h - 1 : 0; +} + +// `strstr` helper function +static char *threebyte_strstr(const unsigned char *h, const unsigned char *n) +{ + uint32_t nw = (uint32_t)n[0] << 24 | n[1] << 16 | n[2] << 8; + uint32_t hw = (uint32_t)h[0] << 24 | h[1] << 16 | h[2] << 8; + for (h += 2; *h && hw != nw; hw = (hw | *++h) << 8) + ; + return *h ? (char *)h - 2 : 0; +} + +// `strstr` helper function +static char *fourbyte_strstr(const unsigned char *h, const unsigned char *n) +{ + uint32_t nw = (uint32_t)n[0] << 24 | n[1] << 16 | n[2] << 8 | n[3]; + uint32_t hw = (uint32_t)h[0] << 24 | h[1] << 16 | h[2] << 8 | h[3]; + for (h += 3; *h && hw != nw; hw = hw << 8 | *++h) + ; + return *h ? (char *)h - 3 : 0; +} + +// `strstr` helper function +static char *twoway_strstr(const unsigned char *h, const unsigned char *n) +{ + const unsigned char *z; + size_t l, ip, jp, k, p, ms, p0, mem, mem0; + size_t byteset[32 / sizeof(size_t)] = {0}; + size_t shift[256]; + + /* Computing length of needle and fill shift table */ + for (l = 0; n[l] && h[l]; l++) BITOP(byteset, n[l], |=), shift[n[l]] = l + 1; + if (n[l]) + return 0; /* hit the end of h */ + + /* Compute maximal suffix */ + ip = -1; + jp = 0; + k = p = 1; + while (jp + k < l) { + if (n[ip + k] == n[jp + k]) { + if (k == p) { + jp += p; + k = 1; + } else + k++; + } else if (n[ip + k] > n[jp + k]) { + jp += k; + k = 1; + p = jp - ip; + } else { + ip = jp++; + k = p = 1; + } + } + ms = ip; + p0 = p; + + /* And with the opposite comparison */ + ip = -1; + jp = 0; + k = p = 1; + while (jp + k < l) { + if (n[ip + k] == n[jp + k]) { + if (k == p) { + jp += p; + k = 1; + } else + k++; + } else if (n[ip + k] < n[jp + k]) { + jp += k; + k = 1; + p = jp - ip; + } else { + ip = jp++; + k = p = 1; + } + } + if (ip + 1 > ms + 1) + ms = ip; + else + p = p0; + + /* Periodic needle? */ + if (memcmp(n, n + p, ms + 1)) { + mem0 = 0; + p = MAX(ms, l - ms - 1) + 1; + } else + mem0 = l - p; + mem = 0; + + /* Initialize incremental end-of-haystack pointer */ + z = h; + + /* Search loop */ + for (;;) { + /* Update incremental end-of-haystack pointer */ + if (z - h < l) { + /* Fast estimate for MAX(l,63) */ + size_t grow = l | 63; + const unsigned char *z2 = memchr(z, 0, grow); + if (z2) { + z = z2; + if (z - h < l) + return 0; + } else + z += grow; + } + + /* Check last byte first; advance by shift on mismatch */ + if (BITOP(byteset, h[l - 1], &)) { + k = l - shift[h[l - 1]]; + if (k) { + if (k < mem) + k = mem; + h += k; + mem = 0; + continue; + } + } else { + h += l; + mem = 0; + continue; + } + + /* Compare right half */ + for (k = MAX(ms + 1, mem); n[k] && n[k] == h[k]; k++) + ; + if (n[k]) { + h += k - ms; + mem = 0; + continue; + } + /* Compare left half */ + for (k = ms + 1; k > mem && n[k - 1] == h[k - 1]; k--) + ; + if (k <= mem) + return (char *)h; + h += p; + mem = mem0; + } +} + +char *strstr(const char *h, const char *n) +{ + /* Return immediately on empty needle */ + if (!n[0]) + return (char *)h; + + /* Use faster algorithms for short needles */ + h = strchr(h, *n); + if (!h || !n[1]) + return (char *)h; + if (!h[1]) + return 0; + if (!n[2]) + return twobyte_strstr((void *)h, (void *)n); + if (!h[2]) + return 0; + if (!n[3]) + return threebyte_strstr((void *)h, (void *)n); + if (!h[3]) + return 0; + if (!n[4]) + return fourbyte_strstr((void *)h, (void *)n); + + return twoway_strstr((void *)h, (void *)n); +} + +#ifdef AX_CONFIG_ALLOC + +#include +char *strdup(const char *s) +{ + size_t l = strlen(s); + char *d = malloc(l + 1); + if (!d) + return NULL; + return memcpy(d, s, l + 1); +} + +#endif // AX_CONFIG_ALLOC diff --git a/ulib/axlibc/c/syslog.c b/ulib/axlibc/c/syslog.c new file mode 100644 index 000000000..eb0ea8a1d --- /dev/null +++ b/ulib/axlibc/c/syslog.c @@ -0,0 +1,16 @@ +#include +#include + +// TODO +void syslog(int __pri, const char *__fmt, ...) +{ + unimplemented(); + return; +} + +// TODO +void openlog(const char *__ident, int __option, int __facility) +{ + unimplemented(); + return; +} diff --git a/ulib/axlibc/c/time.c b/ulib/axlibc/c/time.c new file mode 100644 index 000000000..9a2384ad9 --- /dev/null +++ b/ulib/axlibc/c/time.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include + +long timezone = 0; +const char __utc[] = "UTC"; + +const int SEC_PER_MIN = 60; +const int SEC_PER_HOUR = 3600; +const int MIN_PER_HOUR = 60; +const int HOUR_PER_DAY = 24; + +/* 2000-03-01 (mod 400 year, immediately after feb29 */ +#define LEAPOCH (946684800LL + 86400 * (31 + 29)) +#define DAYS_PER_400Y (365 * 400 + 97) +#define DAYS_PER_100Y (365 * 100 + 24) +#define DAYS_PER_4Y (365 * 4 + 1) + +int __secs_to_tm(long long t, struct tm *tm) +{ + long long days, secs, years; + int remdays, remsecs, remyears; + int qc_cycles, c_cycles, q_cycles; + int months; + int wday, yday, leap; + static const char days_in_month[] = {31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29}; + + /* Reject time_t values whose year would overflow int */ + if (t < INT_MIN * 31622400LL || t > INT_MAX * 31622400LL) + return -1; + + secs = t - LEAPOCH; + days = secs / 86400; + remsecs = secs % 86400; + if (remsecs < 0) { + remsecs += 86400; + days--; + } + + wday = (3 + days) % 7; + if (wday < 0) + wday += 7; + + qc_cycles = days / DAYS_PER_400Y; + remdays = days % DAYS_PER_400Y; + if (remdays < 0) { + remdays += DAYS_PER_400Y; + qc_cycles--; + } + + c_cycles = remdays / DAYS_PER_100Y; + if (c_cycles == 4) + c_cycles--; + remdays -= c_cycles * DAYS_PER_100Y; + + q_cycles = remdays / DAYS_PER_4Y; + if (q_cycles == 25) + q_cycles--; + remdays -= q_cycles * DAYS_PER_4Y; + + remyears = remdays / 365; + if (remyears == 4) + remyears--; + remdays -= remyears * 365; + + leap = !remyears && (q_cycles || !c_cycles); + yday = remdays + 31 + 28 + leap; + if (yday >= 365 + leap) + yday -= 365 + leap; + + years = remyears + 4 * q_cycles + 100 * c_cycles + 400LL * qc_cycles; + + for (months = 0; days_in_month[months] <= remdays; months++) remdays -= days_in_month[months]; + + if (months >= 10) { + months -= 12; + years++; + } + + if (years + 100 > INT_MAX || years + 100 < INT_MIN) + return -1; + + tm->tm_year = years + 100; + tm->tm_mon = months + 2; + tm->tm_mday = remdays + 1; + tm->tm_wday = wday; + tm->tm_yday = yday; + + tm->tm_hour = remsecs / 3600; + tm->tm_min = remsecs / 60 % 60; + tm->tm_sec = remsecs % 60; + + return 0; +} + +struct tm *gmtime_r(const time_t *restrict t, struct tm *restrict tm) +{ + if (__secs_to_tm(*t, tm) < 0) { + errno = EOVERFLOW; + return 0; + } + tm->tm_isdst = 0; + tm->__tm_gmtoff = 0; + tm->__tm_zone = __utc; + return tm; +} + +struct tm *gmtime(const time_t *timer) +{ + static struct tm tm; + return gmtime_r(timer, &tm); +} + +struct tm *localtime_r(const time_t *restrict t, struct tm *restrict tm) +{ + if (*t < INT_MIN * 31622400LL || *t > INT_MAX * 31622400LL) { + errno = EOVERFLOW; + return 0; + } + + if (__secs_to_tm(*t, tm) < 0) { + errno = EOVERFLOW; + return 0; + } + + tm->tm_isdst = 0; + tm->__tm_gmtoff = 0; + tm->__tm_zone = __utc; + + return tm; +} + +struct tm *localtime(const time_t *timep) +{ + static struct tm tm; + return localtime_r(timep, &tm); +} + +time_t time(time_t *t) +{ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + time_t ret = ts.tv_sec; + if (t) + *t = ret; + return ret; +} + +int gettimeofday(struct timeval *tv, struct timezone *tz) +{ + struct timespec ts; + if (!tv) + return 0; + clock_gettime(CLOCK_REALTIME, &ts); + tv->tv_sec = ts.tv_sec; + tv->tv_usec = (int)ts.tv_nsec / 1000; + return 0; +} + +// TODO: +int utimes(const char *filename, const struct timeval times[2]) +{ + unimplemented(); + return 0; +} + +// TODO +void tzset() +{ + unimplemented(); + return; +} + +// TODO +int setitimer(int _which, const struct itimerval *restrict _new, struct itimerval *restrict _old) +{ + unimplemented(); + return 0; +} + +// TODO +char *ctime_r(const time_t *t, char *buf) +{ + unimplemented(); + return NULL; +} + +// TODO +clock_t clock(void) +{ + unimplemented(); + return 0; +} + +#ifdef AX_CONFIG_FP_SIMD +double difftime(time_t t1, time_t t0) +{ + return t1 - t0; +} +#endif diff --git a/ulib/axlibc/c/unistd.c b/ulib/axlibc/c/unistd.c new file mode 100644 index 000000000..eec49bf6b --- /dev/null +++ b/ulib/axlibc/c/unistd.c @@ -0,0 +1,174 @@ +#include +#include +#include +#include +#include +#include +#include + +// TODO: +uid_t geteuid(void) +{ + unimplemented(); + return 0; +} + +// TODO +uid_t getuid(void) +{ + unimplemented(); + return 0; +} + +// TODO +pid_t setsid(void) +{ + unimplemented(); + return 0; +} + +// TODO +int isatty(int fd) +{ + unimplemented(); + return 0; +} + +unsigned int sleep(unsigned int seconds) +{ + struct timespec ts; + + ts.tv_sec = seconds; + ts.tv_nsec = 0; + if (nanosleep(&ts, &ts)) + return ts.tv_sec; + + return 0; +} + +int usleep(unsigned useconds) +{ + struct timespec tv = {.tv_sec = useconds / 1000000, .tv_nsec = (useconds % 1000000) * 1000}; + return nanosleep(&tv, &tv); +} + +#ifdef AX_CONFIG_FS + +// TODO: +int access(const char *pathname, int mode) +{ + unimplemented(); + return 0; +} + +// TODO: +ssize_t readlink(const char *path, char *buf, size_t bufsiz) +{ + unimplemented(); + return 0; +} + +// TODO: +int unlink(const char *pathname) +{ + unimplemented(); + return 0; +} + +// TODO: +int rmdir(const char *pathname) +{ + unimplemented(); + return 0; +} + +// TODO: +int fsync(int fd) +{ + unimplemented(); + return 0; +} + +// TODO +int fdatasync(int __fildes) +{ + unimplemented(); + return 0; +} + +// TODO: +int fchown(int fd, uid_t owner, gid_t group) +{ + unimplemented("owner: %x group: %x", owner, group); + return 0; +} + +// TODO: +int ftruncate(int fd, off_t length) +{ + unimplemented(); + return 0; +} + +// TODO +int chdir(const char *__path) +{ + unimplemented(); + return 0; +} + +// TODO +int truncate(const char *path, off_t length) +{ + unimplemented(); + return 0; +} + +#endif // AX_CONFIG_FS + +#ifdef AX_CONFIG_PIPE + +int pipe2(int fd[2], int flag) +{ + if (!flag) + return pipe(fd); + if (flag & ~(O_CLOEXEC | O_NONBLOCK)) + return -EINVAL; + + int res = pipe(fd); + if (res != 0) + return res; + + if (flag & O_CLOEXEC) { + fcntl(fd[0], F_SETFD, FD_CLOEXEC); + fcntl(fd[1], F_SETFD, FD_CLOEXEC); + } + if (flag & O_NONBLOCK) { + fcntl(fd[0], F_SETFL, O_NONBLOCK); + fcntl(fd[1], F_SETFL, O_NONBLOCK); + } + + return 0; +} + +#endif // AX_CONFIG_PIPE + +// TODO +_Noreturn void _exit(int status) +{ + exit(status); +} + +// TODO +int execve(const char *__path, char *const *__argv, char *const *__envp) +{ + unimplemented(); + return 0; +} + +// TODO +pid_t fork(void) +{ + unimplemented(); + return -1; +} diff --git a/ulib/axlibc/c/utsname.c b/ulib/axlibc/c/utsname.c new file mode 100644 index 000000000..14c35c805 --- /dev/null +++ b/ulib/axlibc/c/utsname.c @@ -0,0 +1,9 @@ +#include +#include + +// TODO +int uname(struct utsname *a) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/c/wait.c b/ulib/axlibc/c/wait.c new file mode 100644 index 000000000..a63fa278c --- /dev/null +++ b/ulib/axlibc/c/wait.c @@ -0,0 +1,17 @@ +#include +#include +#include + +// TODO +pid_t waitpid(pid_t pid, int *status, int options) +{ + unimplemented(); + return 0; +} + +// TODO +pid_t wait3(int *status, int _options, struct rusage *usage) +{ + unimplemented(); + return 0; +} diff --git a/ulib/axlibc/ctypes.h b/ulib/axlibc/ctypes.h new file mode 100644 index 000000000..882ec691c --- /dev/null +++ b/ulib/axlibc/ctypes.h @@ -0,0 +1,2 @@ +#include +#include diff --git a/ulib/axlibc/include/arpa/inet.h b/ulib/axlibc/include/arpa/inet.h new file mode 100644 index 000000000..88889f61d --- /dev/null +++ b/ulib/axlibc/include/arpa/inet.h @@ -0,0 +1,14 @@ +#ifndef _ARPA_INET_H +#define _ARPA_INET_H + +#include + +uint32_t htonl(uint32_t); +uint16_t htons(uint16_t); +uint32_t ntohl(uint32_t); +uint16_t ntohs(uint16_t); + +int inet_pton(int, const char *__restrict, void *__restrict); +const char *inet_ntop(int, const void *__restrict, char *__restrict, socklen_t); + +#endif diff --git a/ulib/axlibc/include/assert.h b/ulib/axlibc/include/assert.h new file mode 100644 index 000000000..0385fa3af --- /dev/null +++ b/ulib/axlibc/include/assert.h @@ -0,0 +1,14 @@ +#ifndef __ASSERT_H__ +#define __ASSERT_H__ + +#include + +#if __STDC_VERSION__ >= 201112L && !defined(__cplusplus) +#define static_assert _Static_assert +#endif + +#define assert(x) ((void)((x) || (__assert_fail(#x, __FILE__, __LINE__, __func__), 0))) + +_Noreturn void __assert_fail(const char *, const char *, int, const char *); + +#endif // __ASSERT_H__ diff --git a/ulib/axlibc/include/ctype.h b/ulib/axlibc/include/ctype.h new file mode 100644 index 000000000..a3ba95b03 --- /dev/null +++ b/ulib/axlibc/include/ctype.h @@ -0,0 +1,20 @@ +#ifndef _CTYPE_H +#define _CTYPE_H + +int tolower(int __c); +int toupper(int __c); + +#define isalpha(a) ((((unsigned)(a) | 32) - 'a') < 26) +#define isdigit(a) (((unsigned)(a) - '0') < 10) +#define islower(a) (((unsigned)(a) - 'a') < 26) +#define isupper(a) (((unsigned)(a) - 'A') < 26) +#define isprint(a) (((unsigned)(a)-0x20) < 0x5f) +#define isgraph(a) (((unsigned)(a)-0x21) < 0x5e) +#define isalnum(a) ((isalpha(a) || isdigit(a))) +#define iscntrl(a) (((unsigned)a < 0x20 || a == 0x7f)) +#define ispunct(a) ((isgraph(a) && !isalnum(a))) +#define isxdigit(a) ((isdigit(a) || ((unsigned)a | 32) - 'a' < 6)) +#define isascii(a) ((!(a & ~0x7f))) +#define isspace(a) ((a == ' ' || (unsigned)a - '\t' < 5)) + +#endif diff --git a/ulib/axlibc/include/dirent.h b/ulib/axlibc/include/dirent.h new file mode 100644 index 000000000..41897510b --- /dev/null +++ b/ulib/axlibc/include/dirent.h @@ -0,0 +1,45 @@ +#ifndef _DIRENT_H +#define _DIRENT_H + +#include + +struct __dirstream { + long long tell; + int fd; + int buf_pos; + int buf_end; + int lock[1]; + char buf[2048]; +}; + +typedef struct __dirstream DIR; + +struct dirent { + ino_t d_ino; + off_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[256]; +}; + +int closedir(DIR *); +DIR *fdopendir(int); +DIR *opendir(const char *); +struct dirent *readdir(DIR *); +int readdir_r(DIR *__restrict, struct dirent *__restrict, struct dirent **__restrict); +void rewinddir(DIR *); +int dirfd(DIR *); + +#define DT_UNKNOWN 0 +#define DT_FIFO 1 +#define DT_CHR 2 +#define DT_DIR 4 +#define DT_BLK 6 +#define DT_REG 8 +#define DT_LNK 10 +#define DT_SOCK 12 +#define DT_WHT 14 +#define IFTODT(x) ((x) >> 12 & 017) +#define DTTOIF(x) ((x) << 12) + +#endif //_DIRENT_H diff --git a/ulib/axlibc/include/dlfcn.h b/ulib/axlibc/include/dlfcn.h new file mode 100644 index 000000000..a1caea91e --- /dev/null +++ b/ulib/axlibc/include/dlfcn.h @@ -0,0 +1,41 @@ +#ifndef _DLFCN_H +#define _DLFCN_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define RTLD_LAZY 1 +#define RTLD_NOW 2 +#define RTLD_NOLOAD 4 +#define RTLD_NODELETE 4096 +#define RTLD_GLOBAL 256 +#define RTLD_LOCAL 0 +#define RTLD_NEXT ((void *)-1) +#define RTLD_DEFAULT ((void *)0) +#define RTLD_DI_LINKMAP 2 + +typedef struct { + const char *dli_fname; + void *dli_fbase; + const char *dli_sname; + void *dli_saddr; +} Dl_info; + +int dladdr(const void *, Dl_info *); +int dlclose(void *); +char *dlerror(void); +void *dlopen(const char *, int); +void *dlsym(void *__restrict, const char *__restrict); + +#if _REDIR_TIME64 +__REDIR(dlsym, __dlsym_time64); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ulib/axlibc/include/endian.h b/ulib/axlibc/include/endian.h new file mode 100644 index 000000000..819a726a4 --- /dev/null +++ b/ulib/axlibc/include/endian.h @@ -0,0 +1,68 @@ +#ifndef _ENDIAN_H +#define _ENDIAN_H + +#include + +#if defined(__aarch64__) +#if __AARCH64EB__ +#define __BYTE_ORDER 4321 +#else +#define __BYTE_ORDER 1234 +#endif +#else +#define __BYTE_ORDER 1234 +#endif + +#define __LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 +#define __PDP_ENDIAN 3412 + +#define BIG_ENDIAN __BIG_ENDIAN +#define LITTLE_ENDIAN __LITTLE_ENDIAN +#define PDP_ENDIAN __PDP_ENDIAN +#define BYTE_ORDER __BYTE_ORDER + +static __inline uint16_t __bswap16(uint16_t __x) +{ + return __x << 8 | __x >> 8; +} + +static __inline uint32_t __bswap32(uint32_t __x) +{ + return __x >> 24 | (__x >> 8 & 0xff00) | (__x << 8 & 0xff0000) | __x << 24; +} + +static __inline uint64_t __bswap64(uint64_t __x) +{ + return (__bswap32(__x) + 0ULL) << 32 | __bswap32(__x >> 32); +} + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htobe16(x) __bswap16(x) +#define be16toh(x) __bswap16(x) +#define htobe32(x) __bswap32(x) +#define be32toh(x) __bswap32(x) +#define htobe64(x) __bswap64(x) +#define be64toh(x) __bswap64(x) +#define htole16(x) (uint16_t)(x) +#define le16toh(x) (uint16_t)(x) +#define htole32(x) (uint32_t)(x) +#define le32toh(x) (uint32_t)(x) +#define htole64(x) (uint64_t)(x) +#define le64toh(x) (uint64_t)(x) +#else +#define htobe16(x) (uint16_t)(x) +#define be16toh(x) (uint16_t)(x) +#define htobe32(x) (uint32_t)(x) +#define be32toh(x) (uint32_t)(x) +#define htobe64(x) (uint64_t)(x) +#define be64toh(x) (uint64_t)(x) +#define htole16(x) __bswap16(x) +#define le16toh(x) __bswap16(x) +#define htole32(x) __bswap32(x) +#define le32toh(x) __bswap32(x) +#define htole64(x) __bswap64(x) +#define le64toh(x) __bswap64(x) +#endif + +#endif diff --git a/ulib/axlibc/include/errno.h b/ulib/axlibc/include/errno.h new file mode 100644 index 000000000..16ed78e31 --- /dev/null +++ b/ulib/axlibc/include/errno.h @@ -0,0 +1,150 @@ +#ifndef __ERRNO_H__ +#define __ERRNO_H__ + +#define EPERM 1 /* Operation not permitted */ +#define ENOENT 2 /* No such file or directory */ +#define ESRCH 3 /* No such process */ +#define EINTR 4 /* Interrupted system call */ +#define EIO 5 /* I/O error */ +#define ENXIO 6 /* No such device or address */ +#define E2BIG 7 /* Argument list too long */ +#define ENOEXEC 8 /* Exec format error */ +#define EBADF 9 /* Bad file number */ +#define ECHILD 10 /* No child processes */ +#define EAGAIN 11 /* Try again */ +#define ENOMEM 12 /* Out of memory */ +#define EACCES 13 /* Permission denied */ +#define EFAULT 14 /* Bad address */ +#define ENOTBLK 15 /* Block device required */ +#define EBUSY 16 /* Device or resource busy */ +#define EEXIST 17 /* File exists */ +#define EXDEV 18 /* Cross-device link */ +#define ENODEV 19 /* No such device */ +#define ENOTDIR 20 /* Not a directory */ +#define EISDIR 21 /* Is a directory */ +#define EINVAL 22 /* Invalid argument */ +#define ENFILE 23 /* File table overflow */ +#define EMFILE 24 /* Too many open files */ +#define ENOTTY 25 /* Not a typewriter */ +#define ETXTBSY 26 /* Text file busy */ +#define EFBIG 27 /* File too large */ +#define ENOSPC 28 /* No space left on device */ +#define ESPIPE 29 /* Illegal seek */ +#define EROFS 30 /* Read-only file system */ +#define EMLINK 31 /* Too many links */ +#define EPIPE 32 /* Broken pipe */ +#define EDOM 33 /* Math argument out of domain of func */ +#define ERANGE 34 /* Math result not representable */ +#define EDEADLK 35 /* Resource deadlock would occur */ +#define ENAMETOOLONG 36 /* File name too long */ +#define ENOLCK 37 /* No record locks available */ +#define ENOSYS 38 /* Invalid system call number */ +#define ENOTEMPTY 39 /* Directory not empty */ +#define ELOOP 40 /* Too many symbolic links encountered */ +#define EWOULDBLOCK EAGAIN /* Operation would block */ +#define ENOMSG 42 /* No message of desired type */ +#define EIDRM 43 /* Identifier removed */ +#define ECHRNG 44 /* Channel number out of range */ +#define EL2NSYNC 45 /* Level 2 not synchronized */ +#define EL3HLT 46 /* Level 3 halted */ +#define EL3RST 47 /* Level 3 reset */ +#define ELNRNG 48 /* Link number out of range */ +#define EUNATCH 49 /* Protocol driver not attached */ +#define ENOCSI 50 /* No CSI structure available */ +#define EL2HLT 51 /* Level 2 halted */ +#define EBADE 52 /* Invalid exchange */ +#define EBADR 53 /* Invalid request descriptor */ +#define EXFULL 54 /* Exchange full */ +#define ENOANO 55 /* No anode */ +#define EBADRQC 56 /* Invalid request code */ +#define EBADSLT 57 /* Invalid slot */ +#define EDEADLOCK EDEADLK +#define EBFONT 59 /* Bad font file format */ +#define ENOSTR 60 /* Device not a stream */ +#define ENODATA 61 /* No data available */ +#define ETIME 62 /* Timer expired */ +#define ENOSR 63 /* Out of streams resources */ +#define ENONET 64 /* Machine is not on the network */ +#define ENOPKG 65 /* Package not installed */ +#define EREMOTE 66 /* Object is remote */ +#define ENOLINK 67 /* Link has been severed */ +#define EADV 68 /* Advertise error */ +#define ESRMNT 69 /* Srmount error */ +#define ECOMM 70 /* Communication error on send */ +#define EPROTO 71 /* Protocol error */ +#define EMULTIHOP 72 /* Multihop attempted */ +#define EDOTDOT 73 /* RFS specific error */ +#define EBADMSG 74 /* Not a data message */ +#define EOVERFLOW 75 /* Value too large for defined data type */ +#define ENOTUNIQ 76 /* Name not unique on network */ +#define EBADFD 77 /* File descriptor in bad state */ +#define EREMCHG 78 /* Remote address changed */ +#define ELIBACC 79 /* Can not access a needed shared library */ +#define ELIBBAD 80 /* Accessing a corrupted shared library */ +#define ELIBSCN 81 /* .lib section in a.out corrupted */ +#define ELIBMAX 82 /* Attempting to link in too many shared libraries */ +#define ELIBEXEC 83 /* Cannot exec a shared library directly */ +#define EILSEQ 84 /* Illegal byte sequence */ +#define ERESTART 85 /* Interrupted system call should be restarted */ +#define ESTRPIPE 86 /* Streams pipe error */ +#define EUSERS 87 /* Too many users */ +#define ENOTSOCK 88 /* Socket operation on non-socket */ +#define EDESTADDRREQ 89 /* Destination address required */ +#define EMSGSIZE 90 /* Message too long */ +#define EPROTOTYPE 91 /* Protocol wrong type for socket */ +#define ENOPROTOOPT 92 /* Protocol not available */ +#define EPROTONOSUPPORT 93 /* Protocol not supported */ +#define ESOCKTNOSUPPORT 94 /* Socket type not supported */ +#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ +#define EPFNOSUPPORT 96 /* Protocol family not supported */ +#define EAFNOSUPPORT 97 /* Address family not supported by protocol */ +#define EADDRINUSE 98 /* Address already in use */ +#define EADDRNOTAVAIL 99 /* Cannot assign requested address */ +#define ENETDOWN 100 /* Network is down */ +#define ENETUNREACH 101 /* Network is unreachable */ +#define ENETRESET 102 /* Network dropped connection because of reset */ +#define ECONNABORTED 103 /* Software caused connection abort */ +#define ECONNRESET 104 /* Connection reset by peer */ +#define ENOBUFS 105 /* No buffer space available */ +#define EISCONN 106 /* Transport endpoint is already connected */ +#define ENOTCONN 107 /* Transport endpoint is not connected */ +#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */ +#define ETOOMANYREFS 109 /* Too many references: cannot splice */ +#define ETIMEDOUT 110 /* Connection timed out */ +#define ECONNREFUSED 111 /* Connection refused */ +#define EHOSTDOWN 112 /* Host is down */ +#define EHOSTUNREACH 113 /* No route to host */ +#define EALREADY 114 /* Operation already in progress */ +#define EINPROGRESS 115 /* Operation now in progress */ +#define ESTALE 116 /* Stale file handle */ +#define EUCLEAN 117 /* Structure needs cleaning */ +#define ENOTNAM 118 /* Not a XENIX named type file */ +#define ENAVAIL 119 /* No XENIX semaphores available */ +#define EISNAM 120 /* Is a named type file */ +#define EREMOTEIO 121 /* Remote I/O error */ +#define EDQUOT 122 /* Quota exceeded */ + +#define ENOMEDIUM 123 /* No medium found */ +#define EMEDIUMTYPE 124 /* Wrong medium type */ +#define ECANCELED 125 /* Operation Canceled */ +#define ENOKEY 126 /* Required key not available */ +#define EKEYEXPIRED 127 /* Key has expired */ +#define EKEYREVOKED 128 /* Key has been revoked */ +#define EKEYREJECTED 129 /* Key was rejected by service */ +#define EOWNERDEAD 130 /* Owner died */ +#define ENOTRECOVERABLE 131 /* State not recoverable */ +#define ERFKILL 132 /* Operation not possible due to RF-kill */ +#define EHWPOISON 133 /* Memory page has hardware error */ + +#ifndef ENOTSUP +#define ENOTSUP EOPNOTSUPP +#endif + +int *__errno_location(void); +#define errno (*__errno_location()) + +#ifdef _GNU_SOURCE +extern char *program_invocation_short_name, *program_invocation_name; +#endif + +#endif // __ERRNO_H__ diff --git a/ulib/axlibc/include/fcntl.h b/ulib/axlibc/include/fcntl.h new file mode 100644 index 000000000..48750af66 --- /dev/null +++ b/ulib/axlibc/include/fcntl.h @@ -0,0 +1,123 @@ +#ifndef __FCNTL_H__ +#define __FCNTL_H__ + +#include + +#define O_CREAT 0100 +#define O_EXCL 0200 +#define O_NOCTTY 0400 +#define O_TRUNC 01000 +#define O_APPEND 02000 +#define O_NONBLOCK 04000 +#define O_DSYNC 010000 +#define O_SYNC 04010000 +#define O_RSYNC 04010000 +#define O_DIRECTORY 0200000 +#define O_NOFOLLOW 0400000 +#define O_CLOEXEC 02000000 + +#define O_ASYNC 020000 +#define O_DIRECT 040000 +#define O_LARGEFILE 0100000 +#define O_NOATIME 01000000 +#define O_PATH 010000000 +#define O_TMPFILE 020200000 +#define O_NDELAY O_NONBLOCK + +#define O_SEARCH O_PATH +#define O_EXEC O_PATH +#define O_TTY_INIT 0 + +#define O_ACCMODE (03 | O_SEARCH) +#define O_RDONLY 00 +#define O_WRONLY 01 +#define O_RDWR 02 + +#define F_DUPFD 0 +#define F_GETFD 1 +#define F_SETFD 2 +#define F_GETFL 3 +#define F_SETFL 4 + +#define F_SETOWN 8 +#define F_GETOWN 9 +#define F_SETSIG 10 +#define F_GETSIG 11 + +#if __LONG_MAX == 0x7fffffffL +#define F_GETLK 12 +#define F_SETLK 13 +#define F_SETLKW 14 +#else +#define F_GETLK 5 +#define F_SETLK 6 +#define F_SETLKW 7 +#endif + +#define FD_CLOEXEC 1 +#define F_DUPFD_CLOEXEC 1030 + +#define F_RDLCK 0 +#define F_WRLCK 1 +#define F_UNLCK 2 + +#define F_OK 0 +#define R_OK 4 +#define W_OK 2 +#define X_OK 1 +#define F_ULOCK 0 +#define F_LOCK 1 +#define F_TLOCK 2 +#define F_TEST 3 + +#ifndef S_IRUSR +#define S_ISUID 04000 +#define S_ISGID 02000 +#define S_ISVTX 01000 +#define S_IRUSR 0400 +#define S_IWUSR 0200 +#define S_IXUSR 0100 +#define S_IRWXU 0700 +#define S_IRGRP 0040 +#define S_IWGRP 0020 +#define S_IXGRP 0010 +#define S_IRWXG 0070 +#define S_IROTH 0004 +#define S_IWOTH 0002 +#define S_IXOTH 0001 +#define S_IRWXO 0007 +#endif + +#define POSIX_FADV_NORMAL 0 +#define POSIX_FADV_RANDOM 1 +#define POSIX_FADV_SEQUENTIAL 2 +#define POSIX_FADV_WILLNEED 3 +#ifndef POSIX_FADV_DONTNEED +#define POSIX_FADV_DONTNEED 4 +#define POSIX_FADV_NOREUSE 5 +#endif + +#define AT_FDCWD (-100) +#define AT_EMPTY_PATH 0x1000 + +#define SYNC_FILE_RANGE_WAIT_BEFORE 1 +#define SYNC_FILE_RANGE_WRITE 2 +#define SYNC_FILE_RANGE_WAIT_AFTER 4 + +#define loff_t off_t + +struct flock { + short l_type; + short l_whence; + off_t l_start; + off_t l_len; + pid_t l_pid; +}; + +int fcntl(int fd, int cmd, ... /* arg */); +int posix_fadvise(int __fd, unsigned long __offset, unsigned long __len, int __advise); +int sync_file_range(int, off_t, off_t, unsigned); + +int open(const char *filename, int flags, ...); + +#endif diff --git a/ulib/axlibc/include/features.h b/ulib/axlibc/include/features.h new file mode 100644 index 000000000..2801151cc --- /dev/null +++ b/ulib/axlibc/include/features.h @@ -0,0 +1,11 @@ +#ifndef _FEATURES_H +#define _FEATURES_H + +#if __STDC_VERSION__ >= 201112L +#elif defined(__GNUC__) +#define _Noreturn __attribute__((__noreturn__)) +#else +#define _Noreturn +#endif + +#endif diff --git a/ulib/axlibc/include/float.h b/ulib/axlibc/include/float.h new file mode 100644 index 000000000..32ac40abf --- /dev/null +++ b/ulib/axlibc/include/float.h @@ -0,0 +1,99 @@ +#ifndef _FLOAT_H +#define _FLOAT_H + +// int __flt_rounds(void); +#define FLT_ROUNDS (__flt_rounds()) + +#define FLT_RADIX 2 + +#define FLT_TRUE_MIN 1.40129846432481707092e-45F +#define FLT_MIN 1.17549435082228750797e-38F +#define FLT_MAX 3.40282346638528859812e+38F +#define FLT_EPSILON 1.1920928955078125e-07F + +#define FLT_MANT_DIG 24 +#define FLT_MIN_EXP (-125) +#define FLT_MAX_EXP 128 +#define FLT_HAS_SUBNORM 1 + +#define FLT_DIG 6 +#define FLT_DECIMAL_DIG 9 +#define FLT_MIN_10_EXP (-37) +#define FLT_MAX_10_EXP 38 + +#define DBL_TRUE_MIN 4.94065645841246544177e-324 +#define DBL_MIN 2.22507385850720138309e-308 +#define DBL_MAX 1.79769313486231570815e+308 +#define DBL_EPSILON 2.22044604925031308085e-16 + +#define DBL_MANT_DIG 53 +#define DBL_MIN_EXP (-1021) +#define DBL_MAX_EXP 1024 +#define DBL_HAS_SUBNORM 1 + +#define DBL_DIG 15 +#define DBL_DECIMAL_DIG 17 +#define DBL_MIN_10_EXP (-307) +#define DBL_MAX_10_EXP 308 + +#define LDBL_HAS_SUBNORM 1 +#define LDBL_DECIMAL_DIG DECIMAL_DIG + +#if defined(__aarch64__) +#define FLT_EVAL_METHOD 0 + +#define LDBL_TRUE_MIN 6.47517511943802511092443895822764655e-4966L +#define LDBL_MIN 3.36210314311209350626267781732175260e-4932L +#define LDBL_MAX 1.18973149535723176508575932662800702e+4932L +#define LDBL_EPSILON 1.92592994438723585305597794258492732e-34L + +#define LDBL_MANT_DIG 113 +#define LDBL_MIN_EXP (-16381) +#define LDBL_MAX_EXP 16384 + +#define LDBL_DIG 33 +#define LDBL_MIN_10_EXP (-4931) +#define LDBL_MAX_10_EXP 4932 + +#define DECIMAL_DIG 36 +#elif defined(__riscv__) || defined(__riscv) +#define FLT_EVAL_METHOD 0 + +#define LDBL_TRUE_MIN 6.47517511943802511092443895822764655e-4966L +#define LDBL_MIN 3.36210314311209350626267781732175260e-4932L +#define LDBL_MAX 1.18973149535723176508575932662800702e+4932L +#define LDBL_EPSILON 1.92592994438723585305597794258492732e-34L + +#define LDBL_MANT_DIG 113 +#define LDBL_MIN_EXP (-16381) +#define LDBL_MAX_EXP 16384 + +#define LDBL_DIG 33 +#define LDBL_MIN_10_EXP (-4931) +#define LDBL_MAX_10_EXP 4932 + +#define DECIMAL_DIG 36 +#elif defined(__x86_64__) +#ifdef __FLT_EVAL_METHOD__ +#define FLT_EVAL_METHOD __FLT_EVAL_METHOD__ +#else +#define FLT_EVAL_METHOD 0 +#endif + +#define LDBL_TRUE_MIN 3.6451995318824746025e-4951L +#define LDBL_MIN 3.3621031431120935063e-4932L +#define LDBL_MAX 1.1897314953572317650e+4932L +#define LDBL_EPSILON 1.0842021724855044340e-19L + +#define LDBL_MANT_DIG 64 +#define LDBL_MIN_EXP (-16381) +#define LDBL_MAX_EXP 16384 + +#define LDBL_DIG 18 +#define LDBL_MIN_10_EXP (-4931) +#define LDBL_MAX_10_EXP 4932 + +#define DECIMAL_DIG 21 +#endif + +#endif diff --git a/ulib/axlibc/include/fnmatch.h b/ulib/axlibc/include/fnmatch.h new file mode 100644 index 000000000..c387c5ce7 --- /dev/null +++ b/ulib/axlibc/include/fnmatch.h @@ -0,0 +1,24 @@ +#ifndef _FNMATCH_H +#define _FNMATCH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define FNM_PATHNAME 0x1 +#define FNM_NOESCAPE 0x2 +#define FNM_PERIOD 0x4 +#define FNM_LEADING_DIR 0x8 +#define FNM_CASEFOLD 0x10 +#define FNM_FILE_NAME FNM_PATHNAME + +#define FNM_NOMATCH 1 +#define FNM_NOSYS (-1) + +int fnmatch(const char *, const char *, int); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ulib/axlibc/include/glob.h b/ulib/axlibc/include/glob.h new file mode 100644 index 000000000..4dbd34705 --- /dev/null +++ b/ulib/axlibc/include/glob.h @@ -0,0 +1,34 @@ +#ifndef _GLOB_H +#define _GLOB_H + +#include + +#define GLOB_ERR 0x01 +#define GLOB_MARK 0x02 +#define GLOB_NOSORT 0x04 +#define GLOB_DOOFFS 0x08 +#define GLOB_NOCHECK 0x10 +#define GLOB_APPEND 0x20 +#define GLOB_NOESCAPE 0x40 +#define GLOB_PERIOD 0x80 + +#define GLOB_TILDE 0x1000 +#define GLOB_TILDE_CHECK 0x4000 + +#define GLOB_NOSPACE 1 +#define GLOB_ABORTED 2 +#define GLOB_NOMATCH 3 +#define GLOB_NOSYS 4 + +typedef struct { + size_t gl_pathc; + char **gl_pathv; + size_t gl_offs; + int __dummy1; + void *__dummy2[5]; +} glob_t; + +int glob(const char *__restrict, int, int (*)(const char *, int), glob_t *__restrict); +void globfree(glob_t *); + +#endif diff --git a/ulib/axlibc/include/inttypes.h b/ulib/axlibc/include/inttypes.h new file mode 100644 index 000000000..536fd3ffa --- /dev/null +++ b/ulib/axlibc/include/inttypes.h @@ -0,0 +1,10 @@ +#ifndef _INTTYPES_H +#define _INTTYPES_H + +#include + +#define __PRI64 "l" + +#define PRIu64 __PRI64 "u" + +#endif diff --git a/ulib/axlibc/include/langinfo.h b/ulib/axlibc/include/langinfo.h new file mode 100644 index 000000000..9114fba47 --- /dev/null +++ b/ulib/axlibc/include/langinfo.h @@ -0,0 +1,82 @@ +#ifndef _LANGINFO_H +#define _LANGINFO_H + +typedef int nl_item; + +#define ABDAY_1 0x20000 +#define ABDAY_2 0x20001 +#define ABDAY_3 0x20002 +#define ABDAY_4 0x20003 +#define ABDAY_5 0x20004 +#define ABDAY_6 0x20005 +#define ABDAY_7 0x20006 + +#define DAY_1 0x20007 +#define DAY_2 0x20008 +#define DAY_3 0x20009 +#define DAY_4 0x2000A +#define DAY_5 0x2000B +#define DAY_6 0x2000C +#define DAY_7 0x2000D + +#define ABMON_1 0x2000E +#define ABMON_2 0x2000F +#define ABMON_3 0x20010 +#define ABMON_4 0x20011 +#define ABMON_5 0x20012 +#define ABMON_6 0x20013 +#define ABMON_7 0x20014 +#define ABMON_8 0x20015 +#define ABMON_9 0x20016 +#define ABMON_10 0x20017 +#define ABMON_11 0x20018 +#define ABMON_12 0x20019 + +#define MON_1 0x2001A +#define MON_2 0x2001B +#define MON_3 0x2001C +#define MON_4 0x2001D +#define MON_5 0x2001E +#define MON_6 0x2001F +#define MON_7 0x20020 +#define MON_8 0x20021 +#define MON_9 0x20022 +#define MON_10 0x20023 +#define MON_11 0x20024 +#define MON_12 0x20025 + +#define AM_STR 0x20026 +#define PM_STR 0x20027 + +#define D_T_FMT 0x20028 +#define D_FMT 0x20029 +#define T_FMT 0x2002A +#define T_FMT_AMPM 0x2002B + +#define ERA 0x2002C +#define ERA_D_FMT 0x2002E +#define ALT_DIGITS 0x2002F +#define ERA_D_T_FMT 0x20030 +#define ERA_T_FMT 0x20031 + +#define CODESET 14 + +#define CRNCYSTR 0x4000F + +#define RADIXCHAR 0x10000 +#define THOUSEP 0x10001 +#define YESEXPR 0x50000 +#define NOEXPR 0x50001 + +#define _NL_LOCALE_NAME(cat) (((cat) << 16) | 0xffff) + +#if defined(_GNU_SOURCE) +#define NL_LOCALE_NAME(cat) _NL_LOCALE_NAME(cat) +#endif + +#if defined(_GNU_SOURCE) || defined(_BSD_SOURCE) +#define YESSTR 0x50002 +#define NOSTR 0x50003 +#endif + +#endif diff --git a/ulib/axlibc/include/libgen.h b/ulib/axlibc/include/libgen.h new file mode 100644 index 000000000..7c7fd9c6d --- /dev/null +++ b/ulib/axlibc/include/libgen.h @@ -0,0 +1,15 @@ +#ifndef _LIBGEN_H +#define _LIBGEN_H + +#ifdef __cplusplus +extern "C" { +#endif + +char *dirname(char *); +char *basename(char *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ulib/axlibc/include/limits.h b/ulib/axlibc/include/limits.h new file mode 100644 index 000000000..26b213a96 --- /dev/null +++ b/ulib/axlibc/include/limits.h @@ -0,0 +1,35 @@ +#ifndef __LIMITS__ +#define __LIMITS__ + +#define __LONG_MAX 0x7fffffffffffffffL +#define CHAR_BIT 8 +#define SCHAR_MIN (-128) +#define SCHAR_MAX 127 +#define UCHAR_MAX 255 +#define SHRT_MIN (-1 - 0x7fff) +#define SHRT_MAX 0x7fff +#define USHRT_MAX 0xffff +#define INT_MIN (-1 - 0x7fffffff) +#define INT_MAX 0x7fffffff +#define UINT_MAX 0xffffffffU +#define LONG_MIN (-LONG_MAX - 1) +#define LONG_MAX __LONG_MAX +#define ULONG_MAX (2UL * LONG_MAX + 1) +#define LLONG_MIN (-LLONG_MAX - 1) +#define LLONG_MAX 0x7fffffffffffffffLL +#define ULLONG_MAX (2ULL * LLONG_MAX + 1) +#define IOV_MAX 1024 + +#define PTHREAD_STACK_MIN 2048 + +#define LOGIN_NAME_MAX 256 +#ifndef NAME_MAX +#define NAME_MAX 255 +#endif +#define TZNAME_MAX 6 + +#define PATH_MAX 4096 +#define SSIZE_MAX LONG_MAX +#define CHAR_MAX 127 + +#endif diff --git a/ulib/axlibc/include/locale.h b/ulib/axlibc/include/locale.h new file mode 100644 index 000000000..e33b13712 --- /dev/null +++ b/ulib/axlibc/include/locale.h @@ -0,0 +1,59 @@ +#ifndef _LOCALE_H +#define _LOCALE_H + +#define LC_CTYPE 0 +#define LC_NUMERIC 1 +#define LC_TIME 2 +#define LC_COLLATE 3 +#define LC_MONETARY 4 +#define LC_MESSAGES 5 +#define LC_ALL 6 +#define LOCALE_NAME_MAX 23 + +#include + +struct lconv { + char *decimal_point; + char *thousands_sep; + char *grouping; + + char *int_curr_symbol; + char *currency_symbol; + char *mon_decimal_point; + char *mon_thousands_sep; + char *mon_grouping; + char *positive_sign; + char *negative_sign; + char int_frac_digits; + char frac_digits; + char p_cs_precedes; + char p_sep_by_space; + char n_cs_precedes; + char n_sep_by_space; + char p_sign_posn; + char n_sign_posn; + char int_p_cs_precedes; + char int_p_sep_by_space; + char int_n_cs_precedes; + char int_n_sep_by_space; + char int_p_sign_posn; + char int_n_sign_posn; +}; + +struct __locale_map { + const void *map; + size_t map_size; + char name[LOCALE_NAME_MAX + 1]; + const struct __locale_map *next; +}; + +struct __locale_struct { + const struct __locale_map *cat[6]; +}; + +typedef struct __locale_struct *locale_t; + +char *setlocale(int, const char *); +struct lconv *localeconv(void); + +#endif // _LOCALE_H diff --git a/ulib/axlibc/include/math.h b/ulib/axlibc/include/math.h new file mode 100644 index 000000000..649b85126 --- /dev/null +++ b/ulib/axlibc/include/math.h @@ -0,0 +1,321 @@ +#ifndef _MATH_H +#define _MATH_H + +#ifdef AX_CONFIG_FP_SIMD + +typedef double double_t; + +#if 100 * __GNUC__ + __GNUC_MINOR__ >= 303 +#define NAN __builtin_nanf("") +#define INFINITY __builtin_inff() +#else +#define NAN (0.0f / 0.0f) +#define INFINITY 1e5000f +#endif + +#define M_PI 3.14159265358979323846 /* pi */ + +#define LOG_TABLE_BITS 7 +#define LOG_POLY_ORDER 6 +#define LOG_POLY1_ORDER 12 + +#define HUGE_VALF INFINITY +#define HUGE_VAL ((double)INFINITY) +#define HUGE_VALL ((long double)INFINITY) + +#define MATH_ERRNO 1 +#define MATH_ERREXCEPT 2 +#define math_errhandling 2 + +#define FP_ILOGBNAN (-1 - 0x7fffffff) +#define FP_ILOGB0 FP_ILOGBNAN + +#define LOG_TABLE_BITS 7 +#define LOG_POLY_ORDER 6 +#define LOG_POLY1_ORDER 12 + +#define FP_NAN 0 +#define FP_INFINITE 1 +#define FP_ZERO 2 +#define FP_SUBNORMAL 3 +#define FP_NORMAL 4 + +int __fpclassify(double); +int __fpclassifyf(float); +int __fpclassifyl(long double); + +static __inline unsigned __FLOAT_BITS(float __f) +{ + union { + float __f; + unsigned __i; + } __u; + __u.__f = __f; + return __u.__i; +} + +static __inline unsigned long long __DOUBLE_BITS(double __f) +{ + union { + double __f; + unsigned long long __i; + } __u; + __u.__f = __f; + return __u.__i; +} + +#define fpclassify(x) \ + (sizeof(x) == sizeof(float) ? __fpclassifyf(x) \ + : sizeof(x) == sizeof(double) ? __fpclassify(x) \ + : __fpclassifyl(x)) + +#define isnan(x) \ + (sizeof(x) == sizeof(float) ? (__FLOAT_BITS(x) & 0x7fffffff) > 0x7f800000 \ + : sizeof(x) == sizeof(double) ? (__DOUBLE_BITS(x) & -1ULL >> 1) > 0x7ffULL << 52 \ + : __fpclassifyl(x) == FP_NAN) + +#define isinf(x) \ + (sizeof(x) == sizeof(float) ? (__FLOAT_BITS(x) & 0x7fffffff) == 0x7f800000 \ + : sizeof(x) == sizeof(double) ? (__DOUBLE_BITS(x) & -1ULL >> 1) == 0x7ffULL << 52 \ + : __fpclassifyl(x) == FP_INFINITE) + +#define isfinite(x) \ + (sizeof(x) == sizeof(float) ? (__FLOAT_BITS(x) & 0x7fffffff) < 0x7f800000 \ + : sizeof(x) == sizeof(double) ? (__DOUBLE_BITS(x) & -1ULL >> 1) < 0x7ffULL << 52 \ + : __fpclassifyl(x) > FP_INFINITE) + +#define isnormal(x) \ + (sizeof(x) == sizeof(float) ? ((__FLOAT_BITS(x) + 0x00800000) & 0x7fffffff) >= 0x01000000 \ + : ((__DOUBLE_BITS(x) + (1ULL << 52)) & -1ULL >> 1) >= 1ULL << 53) + +double acos(double); +float acosf(float); +long double acosl(long double); + +double acosh(double); +float acoshf(float); +long double acoshl(long double); + +double asin(double); +float asinf(float); +long double asinl(long double); + +double asinh(double); +float asinhf(float); +long double asinhl(long double); + +double atan(double); +float atanf(float); +long double atanl(long double); + +double atan2(double, double); +float atan2f(float, float); +long double atan2l(long double, long double); + +double atanh(double); +float atanhf(float); +long double atanhl(long double); + +double cbrt(double); +float cbrtf(float); +long double cbrtl(long double); + +double ceil(double); +float ceilf(float); +long double ceill(long double); + +double copysign(double, double); +float copysignf(float, float); +long double copysignl(long double, long double); + +double cos(double); +float cosf(float); +long double cosl(long double); + +double cosh(double); +float coshf(float); +long double coshl(long double); + +double erf(double); +float erff(float); +long double erfl(long double); + +double erfc(double); +float erfcf(float); +long double erfcl(long double); + +double exp(double); +float expf(float); +long double expl(long double); + +double exp2(double); +float exp2f(float); +long double exp2l(long double); + +double expm1(double); +float expm1f(float); +long double expm1l(long double); + +double fabs(double); +float fabsf(float); +long double fabsl(long double); + +double fdim(double, double); +float fdimf(float, float); +long double fdiml(long double, long double); + +double floor(double); +float floorf(float); +long double floorl(long double); + +double fma(double, double, double); +float fmaf(float, float, float); +long double fmal(long double, long double, long double); + +double fmax(double, double); +float fmaxf(float, float); +long double fmaxl(long double, long double); + +double fmin(double, double); +float fminf(float, float); +long double fminl(long double, long double); + +double fmod(double, double); +float fmodf(float, float); +long double fmodl(long double, long double); + +double frexp(double, int *); +float frexpf(float, int *); +long double frexpl(long double, int *); + +double hypot(double, double); +float hypotf(float, float); +long double hypotl(long double, long double); + +int ilogb(double); +int ilogbf(float); +int ilogbl(long double); + +double ldexp(double, int); +float ldexpf(float, int); +long double ldexpl(long double, int); + +double lgamma(double); +float lgammaf(float); +long double lgammal(long double); + +long long llrint(double); +long long llrintf(float); +long long llrintl(long double); + +long long llround(double); +long long llroundf(float); +long long llroundl(long double); + +double log(double); +float logf(float); +long double logl(long double); + +double log10(double); +float log10f(float); +long double log10l(long double); + +double log1p(double); +float log1pf(float); +long double log1pl(long double); + +double log2(double); +float log2f(float); +long double log2l(long double); + +double logb(double); +float logbf(float); +long double logbl(long double); + +long lrint(double); +long lrintf(float); +long lrintl(long double); + +long lround(double); +long lroundf(float); +long lroundl(long double); + +double modf(double, double *); +float modff(float, float *); +long double modfl(long double, long double *); + +double nan(const char *); +float nanf(const char *); +long double nanl(const char *); + +double nearbyint(double); +float nearbyintf(float); +long double nearbyintl(long double); + +double nextafter(double, double); +float nextafterf(float, float); +long double nextafterl(long double, long double); + +double nexttoward(double, long double); +float nexttowardf(float, long double); +long double nexttowardl(long double, long double); + +double pow(double, double); +float powf(float, float); +long double powl(long double, long double); + +double remainder(double, double); +float remainderf(float, float); +long double remainderl(long double, long double); + +double remquo(double, double, int *); +float remquof(float, float, int *); +long double remquol(long double, long double, int *); + +double rint(double); +float rintf(float); +long double rintl(long double); + +double round(double); +float roundf(float); +long double roundl(long double); + +double scalbln(double, long); +float scalblnf(float, long); +long double scalblnl(long double, long); + +double scalbn(double, int); +float scalbnf(float, int); +long double scalbnl(long double, int); + +double sin(double); +float sinf(float); +long double sinl(long double); + +double sinh(double); +float sinhf(float); +long double sinhl(long double); + +double sqrt(double); +float sqrtf(float); +long double sqrtl(long double); + +double tan(double); +float tanf(float); +long double tanl(long double); + +double tanh(double); +float tanhf(float); +long double tanhl(long double); + +double tgamma(double); +float tgammaf(float); +long double tgammal(long double); + +double trunc(double); +float truncf(float); +long double truncl(long double); + +#endif // AX_CONFIG_FP_SIMD + +#endif // _MATH_H diff --git a/ulib/axlibc/include/memory.h b/ulib/axlibc/include/memory.h new file mode 100644 index 000000000..d51b932ee --- /dev/null +++ b/ulib/axlibc/include/memory.h @@ -0,0 +1,6 @@ +#ifndef __MEMORY_H__ +#define __MEMORY_H__ + +#include + +#endif // __MEMORY_H__ diff --git a/ulib/axlibc/include/netdb.h b/ulib/axlibc/include/netdb.h new file mode 100644 index 000000000..6272578b7 --- /dev/null +++ b/ulib/axlibc/include/netdb.h @@ -0,0 +1,85 @@ +#ifndef _NETDB_H +#define _NETDB_H + +#include + +struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; + +struct aibuf { + struct addrinfo ai; + union sa { + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } sa; + volatile int lock[1]; + short slot, ref; +}; + +struct service { + uint16_t port; + unsigned char proto, socktype; +}; + +struct address { + int family; + unsigned scopeid; + uint8_t addr[16]; + int sortkey; +}; + +#define AI_PASSIVE 0x01 +#define AI_CANONNAME 0x02 +#define AI_NUMERICHOST 0x04 +#define AI_V4MAPPED 0x08 +#define AI_ALL 0x10 +#define AI_ADDRCONFIG 0x20 +#define AI_NUMERICSERV 0x400 + +#define NI_NUMERICHOST 0x01 +#define NI_NUMERICSERV 0x02 +#define NI_NOFQDN 0x04 +#define NI_NAMEREQD 0x08 +#define NI_DGRAM 0x10 +#define NI_NUMERICSCOPE 0x100 + +#define EAI_BADFLAGS -1 +#define EAI_NONAME -2 +#define EAI_AGAIN -3 +#define EAI_FAIL -4 +#define EAI_FAMILY -6 +#define EAI_SOCKTYPE -7 +#define EAI_SERVICE -8 +#define EAI_MEMORY -10 +#define EAI_SYSTEM -11 +#define EAI_OVERFLOW -12 + +#define HOST_NOT_FOUND 1 +#define TRY_AGAIN 2 +#define NO_RECOVERY 3 +#define NO_DATA 4 +#define NO_ADDRESS NO_DATA + +extern int h_errno; +const char *hstrerror(int ecode); + +#define MAXSERVS 2 +#define MAXADDRS 48 + +#ifdef AX_CONFIG_NET + +int getaddrinfo(const char *, const char *, const struct addrinfo *, struct addrinfo **); +void freeaddrinfo(struct addrinfo *); +const char *gai_strerror(int __ecode); + +#endif // AX_CONFIG_NET + +#endif // _NETDB_H diff --git a/ulib/axlibc/include/netinet/in.h b/ulib/axlibc/include/netinet/in.h new file mode 100644 index 000000000..370f3b17b --- /dev/null +++ b/ulib/axlibc/include/netinet/in.h @@ -0,0 +1,129 @@ +#ifndef _NETINET_IN_H +#define _NETINET_IN_H + +#include + +#define INET_ADDRSTRLEN 16 +#define INET6_ADDRSTRLEN 46 + +uint32_t htonl(uint32_t); +uint16_t htons(uint16_t); +uint32_t ntohl(uint32_t); +uint16_t ntohs(uint16_t); + +#define IPPROTO_IP 0 +#define IPPROTO_HOPOPTS 0 +#define IPPROTO_ICMP 1 +#define IPPROTO_IGMP 2 +#define IPPROTO_IPIP 4 +#define IPPROTO_TCP 6 +#define IPPROTO_EGP 8 +#define IPPROTO_PUP 12 +#define IPPROTO_UDP 17 +#define IPPROTO_IDP 22 +#define IPPROTO_TP 29 +#define IPPROTO_DCCP 33 +#define IPPROTO_IPV6 41 +#define IPPROTO_ROUTING 43 +#define IPPROTO_FRAGMENT 44 +#define IPPROTO_RSVP 46 +#define IPPROTO_GRE 47 +#define IPPROTO_ESP 50 +#define IPPROTO_AH 51 +#define IPPROTO_ICMPV6 58 +#define IPPROTO_NONE 59 +#define IPPROTO_DSTOPTS 60 +#define IPPROTO_MTP 92 +#define IPPROTO_BEETPH 94 +#define IPPROTO_ENCAP 98 +#define IPPROTO_PIM 103 +#define IPPROTO_COMP 108 +#define IPPROTO_SCTP 132 +#define IPPROTO_MH 135 +#define IPPROTO_UDPLITE 136 +#define IPPROTO_MPLS 137 +#define IPPROTO_ETHERNET 143 +#define IPPROTO_RAW 255 +#define IPPROTO_MPTCP 262 +#define IPPROTO_MAX 263 + +#define IPV6_ADDRFORM 1 +#define IPV6_2292PKTINFO 2 +#define IPV6_2292HOPOPTS 3 +#define IPV6_2292DSTOPTS 4 +#define IPV6_2292RTHDR 5 +#define IPV6_2292PKTOPTIONS 6 +#define IPV6_CHECKSUM 7 +#define IPV6_2292HOPLIMIT 8 +#define IPV6_NEXTHOP 9 +#define IPV6_AUTHHDR 10 +#define IPV6_UNICAST_HOPS 16 +#define IPV6_MULTICAST_IF 17 +#define IPV6_MULTICAST_HOPS 18 +#define IPV6_MULTICAST_LOOP 19 +#define IPV6_JOIN_GROUP 20 +#define IPV6_LEAVE_GROUP 21 +#define IPV6_ROUTER_ALERT 22 +#define IPV6_MTU_DISCOVER 23 +#define IPV6_MTU 24 +#define IPV6_RECVERR 25 +#define IPV6_V6ONLY 26 +#define IPV6_JOIN_ANYCAST 27 +#define IPV6_LEAVE_ANYCAST 28 +#define IPV6_MULTICAST_ALL 29 +#define IPV6_ROUTER_ALERT_ISOLATE 30 +#define IPV6_IPSEC_POLICY 34 +#define IPV6_XFRM_POLICY 35 +#define IPV6_HDRINCL 36 + +#define IN6ADDR_ANY_INIT \ + { \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 \ + } \ + } \ + } +#define IN6ADDR_LOOPBACK_INIT \ + { \ + { \ + { \ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 \ + } \ + } \ + } + +typedef uint16_t in_port_t; +typedef uint32_t in_addr_t; + +struct in_addr { + in_addr_t s_addr; +}; + +struct sockaddr_in { + sa_family_t sin_family; + in_port_t sin_port; + struct in_addr sin_addr; + uint8_t sin_zero[8]; +}; + +struct in6_addr { + union { + uint8_t __s6_addr[16]; + uint16_t __s6_addr16[8]; + uint32_t __s6_addr32[4]; + } __in6_union; +}; +#define s6_addr __in6_union.__s6_addr +#define s6_addr16 __in6_union.__s6_addr16 +#define s6_addr32 __in6_union.__s6_addr32 + +struct sockaddr_in6 { + sa_family_t sin6_family; + in_port_t sin6_port; + uint32_t sin6_flowinfo; + struct in6_addr sin6_addr; + uint32_t sin6_scope_id; +}; + +#endif // _NETINET_IN_H diff --git a/ulib/axlibc/include/netinet/tcp.h b/ulib/axlibc/include/netinet/tcp.h new file mode 100644 index 000000000..dfe65d108 --- /dev/null +++ b/ulib/axlibc/include/netinet/tcp.h @@ -0,0 +1,45 @@ +#ifndef _NETINET_TCP_H +#define _NETINET_TCP_H + +#define TCP_NODELAY 1 +#define TCP_MAXSEG 2 +#define TCP_CORK 3 +#define TCP_KEEPIDLE 4 +#define TCP_KEEPINTVL 5 +#define TCP_KEEPCNT 6 +#define TCP_SYNCNT 7 +#define TCP_LINGER2 8 +#define TCP_DEFER_ACCEPT 9 +#define TCP_WINDOW_CLAMP 10 +#define TCP_INFO 11 +#define TCP_QUICKACK 12 +#define TCP_CONGESTION 13 +#define TCP_MD5SIG 14 +#define TCP_THIN_LINEAR_TIMEOUTS 16 +#define TCP_THIN_DUPACK 17 +#define TCP_USER_TIMEOUT 18 +#define TCP_REPAIR 19 +#define TCP_REPAIR_QUEUE 20 +#define TCP_QUEUE_SEQ 21 +#define TCP_REPAIR_OPTIONS 22 +#define TCP_FASTOPEN 23 +#define TCP_TIMESTAMP 24 +#define TCP_NOTSENT_LOWAT 25 +#define TCP_CC_INFO 26 +#define TCP_SAVE_SYN 27 +#define TCP_SAVED_SYN 28 +#define TCP_REPAIR_WINDOW 29 +#define TCP_FASTOPEN_CONNECT 30 +#define TCP_ULP 31 +#define TCP_MD5SIG_EXT 32 +#define TCP_FASTOPEN_KEY 33 +#define TCP_FASTOPEN_NO_COOKIE 34 +#define TCP_ZEROCOPY_RECEIVE 35 +#define TCP_INQ 36 +#define TCP_TX_DELAY 37 + +#define TCP_REPAIR_ON 1 +#define TCP_REPAIR_OFF 0 +#define TCP_REPAIR_OFF_NO_WP -1 + +#endif // _NETINET_TCP_H diff --git a/ulib/axlibc/include/poll.h b/ulib/axlibc/include/poll.h new file mode 100644 index 000000000..e5903f249 --- /dev/null +++ b/ulib/axlibc/include/poll.h @@ -0,0 +1,21 @@ +#ifndef _POLL_H +#define _POLL_H + +struct pollfd { + int fd; + short events; + short revents; +}; + +#define POLLIN 0x001 +#define POLLPRI 0x002 +#define POLLOUT 0x004 +#define POLLERR 0x008 +#define POLLHUP 0x010 +#define POLLNVAL 0x020 + +typedef unsigned long nfds_t; + +int poll(struct pollfd *__fds, nfds_t __nfds, int __timeout); + +#endif // _POLL_H diff --git a/ulib/axlibc/include/pthread.h b/ulib/axlibc/include/pthread.h new file mode 100644 index 000000000..5f8cd98aa --- /dev/null +++ b/ulib/axlibc/include/pthread.h @@ -0,0 +1,84 @@ +#ifndef _PTHREAD_H +#define _PTHREAD_H + +#include +#include + +#define PTHREAD_CANCEL_ENABLE 0 +#define PTHREAD_CANCEL_DISABLE 1 +#define PTHREAD_CANCEL_MASKED 2 + +#define PTHREAD_CANCEL_DEFERRED 0 +#define PTHREAD_CANCEL_ASYNCHRONOUS 1 + +typedef struct { + unsigned __attr; +} pthread_condattr_t; + +#include + +typedef struct { + unsigned __attr; +} pthread_mutexattr_t; + +typedef struct { + union { + int __i[sizeof(long) == 8 ? 14 : 9]; + volatile int __vi[sizeof(long) == 8 ? 14 : 9]; + unsigned long __s[sizeof(long) == 8 ? 7 : 9]; + } __u; +} pthread_attr_t; +#define _a_stacksize __u.__s[0] +#define _a_guardsize __u.__s[1] +#define _a_stackaddr __u.__s[2] + +typedef struct { + union { + int __i[12]; + volatile int __vi[12]; + void *__p[12 * sizeof(int) / sizeof(void *)]; + } __u; +} pthread_cond_t; +#define _c_clock __u.__i[4] +#define _c_shared __u.__p[0] + +typedef void *pthread_t; + +#define PTHREAD_CANCELED ((void *)-1) +#define SIGCANCEL 33 + +#ifdef AX_CONFIG_MULTITASK + +_Noreturn void pthread_exit(void *); +pthread_t pthread_self(void); + +int pthread_create(pthread_t *__restrict, const pthread_attr_t *__restrict, void *(*)(void *), + void *__restrict); +int pthread_join(pthread_t t, void **res); + +int pthread_setcancelstate(int, int *); +int pthread_setcanceltype(int, int *); +void pthread_testcancel(void); +int pthread_cancel(pthread_t); + +int pthread_mutex_init(pthread_mutex_t *__restrict, const pthread_mutexattr_t *__restrict); +int pthread_mutex_lock(pthread_mutex_t *); +int pthread_mutex_unlock(pthread_mutex_t *); +int pthread_mutex_trylock(pthread_mutex_t *); + +int pthread_setname_np(pthread_t, const char *); + +int pthread_cond_init(pthread_cond_t *__restrict__ __cond, + const pthread_condattr_t *__restrict__ __cond_attr); +int pthread_cond_signal(pthread_cond_t *__cond); +int pthread_cond_wait(pthread_cond_t *__restrict__ __cond, pthread_mutex_t *__restrict__ __mutex); +int pthread_cond_broadcast(pthread_cond_t *); + +int pthread_attr_init(pthread_attr_t *__attr); +int pthread_attr_getstacksize(const pthread_attr_t *__restrict__ __attr, + size_t *__restrict__ __stacksize); +int pthread_attr_setstacksize(pthread_attr_t *__attr, size_t __stacksize); + +#endif // AX_CONFIG_MULTITASK + +#endif // _PTHREAD_H diff --git a/ulib/axlibc/include/pwd.h b/ulib/axlibc/include/pwd.h new file mode 100644 index 000000000..354854619 --- /dev/null +++ b/ulib/axlibc/include/pwd.h @@ -0,0 +1,45 @@ +#ifndef _PWD_H +#define _PWD_H + +#include +#include +#include +#include + +#define NSCDVERSION 2 +#define GETPWBYNAME 0 +#define GETPWBYUID 1 +#define GETGRBYNAME 2 +#define GETGRBYGID 3 +#define GETINITGR 15 + +#define PWVERSION 0 +#define PWFOUND 1 +#define PWNAMELEN 2 +#define PWPASSWDLEN 3 +#define PWUID 4 +#define PWGID 5 +#define PWGECOSLEN 6 +#define PWDIRLEN 7 +#define PWSHELLLEN 8 +#define PW_LEN 9 + +#define REQVERSION 0 +#define REQTYPE 1 +#define REQKEYLEN 2 +#define REQ_LEN 3 + +struct passwd { + char *pw_name; + char *pw_passwd; + uid_t pw_uid; + gid_t pw_gid; + char *pw_gecos; + char *pw_dir; + char *pw_shell; +}; + +int getpwuid_r(uid_t, struct passwd *, char *, size_t, struct passwd **); +int getpwnam_r(const char *, struct passwd *, char *, size_t, struct passwd **); + +#endif // _PWD_H diff --git a/ulib/axlibc/include/regex.h b/ulib/axlibc/include/regex.h new file mode 100644 index 000000000..50b382885 --- /dev/null +++ b/ulib/axlibc/include/regex.h @@ -0,0 +1,4 @@ +#ifndef _REGEX_H +#define _REGEX_H + +#endif // _REGEX_H diff --git a/ulib/axlibc/include/sched.h b/ulib/axlibc/include/sched.h new file mode 100644 index 000000000..ed201c584 --- /dev/null +++ b/ulib/axlibc/include/sched.h @@ -0,0 +1,23 @@ +#ifndef _SCHED_H +#define _SCHED_H + +#include + +typedef struct cpu_set_t { + unsigned long __bits[128 / sizeof(long)]; +} cpu_set_t; + +#define __CPU_op_S(i, size, set, op) \ + ((i) / 8U >= (size) ? 0 \ + : (((unsigned long *)(set))[(i) / 8 / sizeof(long)] op( \ + 1UL << ((i) % (8 * sizeof(long)))))) + +#define CPU_SET_S(i, size, set) __CPU_op_S(i, size, set, |=) +#define CPU_ZERO_S(size, set) memset(set, 0, size) + +#define CPU_SET(i, set) CPU_SET_S(i, sizeof(cpu_set_t), set); +#define CPU_ZERO(set) CPU_ZERO_S(sizeof(cpu_set_t), set) + +int sched_setaffinity(pid_t, size_t, const cpu_set_t *); + +#endif // _SCHED_H diff --git a/ulib/axlibc/include/setjmp.h b/ulib/axlibc/include/setjmp.h new file mode 100644 index 000000000..70981dfb4 --- /dev/null +++ b/ulib/axlibc/include/setjmp.h @@ -0,0 +1,23 @@ +#ifndef _SETJMP_H +#define _SETJMP_H + +#include + +#if defined(__aarch64__) +typedef unsigned long __jmp_buf[22]; +#elif defined(__riscv__) || defined(__riscv) +typedef unsigned long __jmp_buf[26]; +#elif defined(__x86_64__) +typedef unsigned long __jmp_buf[8]; +#endif + +typedef struct __jmp_buf_tag { + __jmp_buf __jb; + unsigned long __fl; + unsigned long __ss[128 / sizeof(long)]; +} jmp_buf[1]; + +int setjmp(jmp_buf); +_Noreturn void longjmp(jmp_buf, int); + +#endif diff --git a/ulib/axlibc/include/signal.h b/ulib/axlibc/include/signal.h new file mode 100644 index 000000000..52e21cee7 --- /dev/null +++ b/ulib/axlibc/include/signal.h @@ -0,0 +1,179 @@ +#ifndef _SIGNAL_H +#define _SIGNAL_H + +#include +#include +#include + +typedef int sig_atomic_t; + +union sigval { + int sival_int; + void *sival_ptr; +}; + +typedef union sigval __sigval_t; + +#define SA_NOCLDSTOP 1 +#define SA_NOCLDWAIT 2 +#define SA_SIGINFO 4 +#define SA_ONSTACK 0x08000000 +#define SA_RESTART 0x10000000 +#define SA_NODEFER 0x40000000 +#define SA_RESETHAND 0x80000000 +#define SA_RESTORER 0x04000000 + +#define SIG_BLOCK 0 +#define SIG_UNBLOCK 1 +#define SIG_SETMASK 2 + +#define SI_ASYNCNL (-60) +#define SI_TKILL (-6) +#define SI_SIGIO (-5) +#define SI_ASYNCIO (-4) +#define SI_MESGQ (-3) +#define SI_TIMER (-2) +#define SI_QUEUE (-1) +#define SI_USER 0 +#define SI_KERNEL 128 + +typedef struct { + int si_signo, si_errno, si_code; + union { + char __pad[128 - 2 * sizeof(int) - sizeof(long)]; + struct { + union { + struct { + int si_pid; + unsigned int si_uid; + } __piduid; + struct { + int si_timerid; + int si_overrun; + } __timer; + } __first; + union { + union sigval si_value; + struct { + int si_status; + long si_utime, si_stime; + } __sigchld; + } __second; + } __si_common; + struct { + void *si_addr; + short si_addr_lsb; + union { + struct { + void *si_lower; + void *si_upper; + } __addr_bnd; + unsigned si_pkey; + } __first; + } __sigfault; + struct { + long si_band; + int si_fd; + } __sigpoll; + struct { + void *si_call_addr; + int si_syscall; + unsigned si_arch; + } __sigsys; + } __si_fields; +} siginfo_t; + +#define si_pid __si_fields.__si_common.__first.__piduid.si_pid +#define si_uid __si_fields.__si_common.__first.__piduid.si_uid +#define si_status __si_fields.__si_common.__second.__sigchld.si_status +#define si_utime __si_fields.__si_common.__second.__sigchld.si_utime +#define si_stime __si_fields.__si_common.__second.__sigchld.si_stime +#define si_value __si_fields.__si_common.__second.si_value +#define si_addr __si_fields.__sigfault.si_addr +#define si_addr_lsb __si_fields.__sigfault.si_addr_lsb +#define si_lower __si_fields.__sigfault.__first.__addr_bnd.si_lower +#define si_upper __si_fields.__sigfault.__first.__addr_bnd.si_upper +#define si_pkey __si_fields.__sigfault.__first.si_pkey +#define si_band __si_fields.__sigpoll.si_band +#define si_fd __si_fields.__sigpoll.si_fd +#define si_timerid __si_fields.__si_common.__first.__timer.si_timerid +#define si_overrun __si_fields.__si_common.__first.__timer.si_overrun +#define si_ptr si_value.sival_ptr +#define si_int si_value.sival_int +#define si_call_addr __si_fields.__sigsys.si_call_addr +#define si_syscall __si_fields.__sigsys.si_syscall +#define si_arch __si_fields.__sigsys.si_arch + +#define SIGHUP 1 +#define SIGINT 2 +#define SIGQUIT 3 +#define SIGILL 4 +#define SIGTRAP 5 +#define SIGABRT 6 +#define SIGIOT SIGABRT +#define SIGBUS 7 +#define SIGFPE 8 +#define SIGKILL 9 +#define SIGUSR1 10 +#define SIGSEGV 11 +#define SIGUSR2 12 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGTERM 15 +#define SIGSTKFLT 16 +#define SIGCHLD 17 +#define SIGCONT 18 +#define SIGSTOP 19 +#define SIGTSTP 20 +#define SIGTTIN 21 +#define SIGTTOU 22 +#define SIGURG 23 +#define SIGXCPU 24 +#define SIGXFSZ 25 +#define SIGVTALRM 26 +#define SIGPROF 27 +#define SIGWINCH 28 +#define SIGIO 29 +#define SIGPOLL 29 +#define SIGPWR 30 +#define SIGSYS 31 +#define SIGUNUSED SIGSYS + +#define _NSIG 65 + +typedef void (*sighandler_t)(int); +#define SIG_ERR ((void (*)(int)) - 1) +#define SIG_DFL ((void (*)(int))0) +#define SIG_IGN ((void (*)(int))1) + +typedef struct __sigset_t { + unsigned long __bits[128 / sizeof(long)]; +} sigset_t; + +struct sigaction { + union { + void (*sa_handler)(int); + void (*sa_sigaction)(int, siginfo_t *, void *); + } __sa_handler; + sigset_t sa_mask; + int sa_flags; + void (*sa_restorer)(void); +}; + +#define sa_handler __sa_handler.sa_handler +#define sa_sigaction __sa_handler.sa_sigaction + +void (*signal(int, void (*)(int)))(int); +int sigaction(int, const struct sigaction *__restrict, struct sigaction *__restrict); +int sigemptyset(sigset_t *); +int raise(int); +int sigaddset(sigset_t *, int); +int pthread_sigmask(int, const sigset_t *__restrict, sigset_t *__restrict); + +int kill(pid_t, int); + +#ifdef AX_CONFIG_MULTITASK +int pthread_kill(pthread_t t, int sig); +#endif + +#endif // _SIGNAL_H diff --git a/ulib/axlibc/include/stdarg.h b/ulib/axlibc/include/stdarg.h new file mode 100644 index 000000000..3c23d618c --- /dev/null +++ b/ulib/axlibc/include/stdarg.h @@ -0,0 +1,11 @@ +#ifndef __STDARG_H__ +#define __STDARG_H__ + +#define va_start(v, l) __builtin_va_start(v, l) +#define va_end(v) __builtin_va_end(v) +#define va_arg(v, l) __builtin_va_arg(v, l) +#define va_copy(d, s) __builtin_va_copy(d, s) + +typedef __builtin_va_list va_list; + +#endif // __STDARG_H__ diff --git a/ulib/axlibc/include/stdbool.h b/ulib/axlibc/include/stdbool.h new file mode 100644 index 000000000..e3325f24c --- /dev/null +++ b/ulib/axlibc/include/stdbool.h @@ -0,0 +1,11 @@ +#ifndef __STDBOOL_H__ +#define __STDBOOL_H__ + +/* Represents true-or-false values */ +#ifndef __cplusplus +#define true 1 +#define false 0 +#define bool _Bool +#endif + +#endif // __STDBOOL_H__ diff --git a/ulib/axlibc/include/stddef.h b/ulib/axlibc/include/stddef.h new file mode 100644 index 000000000..faf08bdd7 --- /dev/null +++ b/ulib/axlibc/include/stddef.h @@ -0,0 +1,27 @@ +#ifndef __STDDEF_H__ +#define __STDDEF_H__ + +#include +#include + +/* size_t is used for memory object sizes */ +typedef uintptr_t size_t; +typedef intptr_t ssize_t; +typedef ssize_t ptrdiff_t; + +typedef long clock_t; +typedef int clockid_t; + +#ifdef __cplusplus +#define NULL 0L +#else +#define NULL ((void *)0) +#endif + +#if __GNUC__ > 3 +#define offsetof(type, member) __builtin_offsetof(type, member) +#else +#define offsetof(type, member) ((size_t)((char *)&(((type *)0)->member) - (char *)0)) +#endif + +#endif // __STDDEF_H__ diff --git a/ulib/axlibc/include/stdint.h b/ulib/axlibc/include/stdint.h new file mode 100644 index 000000000..3081bf011 --- /dev/null +++ b/ulib/axlibc/include/stdint.h @@ -0,0 +1,66 @@ +#ifndef __STDINT_H__ +#define __STDINT_H__ + +/* Explicitly-sized versions of integer types */ +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef long long int64_t; +typedef unsigned long long uint64_t; + +typedef int64_t int_fast64_t; +typedef int64_t intmax_t; + +#define INT8_MIN (-1 - 0x7f) +#define INT16_MIN (-1 - 0x7fff) +#define INT32_MIN (-1 - 0x7fffffff) +#define INT64_MIN (-1 - 0x7fffffffffffffff) + +#define INT8_MAX (0x7f) +#define INT16_MAX (0x7fff) +#define INT32_MAX (0x7fffffff) +#define INT64_MAX (0x7fffffffffffffff) + +#define UINT8_MAX (0xff) +#define UINT16_MAX (0xffff) +#define UINT32_MAX (0xffffffffu) +#define UINT64_MAX (0xffffffffffffffffu) + +#define INTPTR_MIN INT64_MIN +#define INTPTR_MAX INT64_MAX +#define UINTPTR_MAX UINT64_MAX + +/* * + * Pointers and addresses are 32 bits long. + * We use pointer types to represent addresses, + * uintptr_t to represent the numerical values of addresses. + * */ +#if __riscv_xlen == 64 || defined(__x86_64__) || defined(__aarch64__) +typedef int64_t intptr_t; +typedef uint64_t uintptr_t; +#elif __riscv_xlen == 32 || defined(__i386__) +typedef int32_t intptr_t; +typedef uint32_t uintptr_t; +#endif + +typedef uint8_t uint_fast8_t; +typedef uint64_t uint_fast64_t; + +#if UINTPTR_MAX == UINT64_MAX +#define INT64_C(c) c##L +#define UINT64_C(c) c##UL +#define INTMAX_C(c) c##L +#define UINTMAX_C(c) c##UL +#else +#define INT64_C(c) c##LL +#define UINT64_C(c) c##ULL +#define INTMAX_C(c) c##LL +#define UINTMAX_C(c) c##ULL +#endif + +#define SIZE_MAX UINT64_MAX + +#endif // __STDINT_H__ diff --git a/ulib/axlibc/include/stdio.h b/ulib/axlibc/include/stdio.h new file mode 100644 index 000000000..12aca2a88 --- /dev/null +++ b/ulib/axlibc/include/stdio.h @@ -0,0 +1,116 @@ +#ifndef __STDIO_H__ +#define __STDIO_H__ + +#include +#include + +#define _IOFBF 0 +#define _IOLBF 1 +#define _IONBF 2 + +#define FILE_BUF_SIZE 1024 +// TODO: complete this struct +struct IO_FILE { + int fd; + uint16_t buffer_len; + char buf[FILE_BUF_SIZE]; +}; + +typedef struct IO_FILE FILE; + +extern FILE *const stdin; +extern FILE *const stdout; +extern FILE *const stderr; + +#define stdin (stdin) +#define stdout (stdout) +#define stderr (stderr) + +#define EOF (-1) + +#if defined(AX_LOG_WARN) || defined(AX_LOG_INFO) || defined(AX_LOG_DEBUG) || defined(AX_LOG_TRACE) + +#define unimplemented(fmt, ...) \ + printf("\x1b[33m%s%s:\x1b[0m " fmt "\n", "WARN: no ax_call implementation for ", __func__, \ + ##__VA_ARGS__) +#else + +#define unimplemented(fmt, ...) \ + do { \ + } while (0) + +#endif + +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 + +#define F_EOF 16 +#define F_ERR 32 +#define F_SVB 64 +#define F_NORD 4 +#define UNGET 8 + +#define FILENAME_MAX 4096 +#define BUFSIZ 1024 +#define L_tmpnam 20 + +FILE *fopen(const char *filename, const char *mode); +FILE *freopen(const char *__restrict, const char *__restrict, FILE *__restrict); +int fclose(FILE *); + +int remove(const char *); +int rename(const char *, const char *); + +int feof(FILE *__stream); +int ferror(FILE *); +int fflush(FILE *); +void clearerr(FILE *); + +int fseek(FILE *__stream, long __off, int __whence); +long ftell(FILE *); + +size_t fread(void *__restrict, size_t, size_t, FILE *__restrict); +size_t fwrite(const void *__restrict, size_t, size_t, FILE *__restrict); + +int getc(FILE *); +int getchar(void); +int ungetc(int, FILE *); + +int fputc(int, FILE *); +int putc(int, FILE *); +int putchar(int); + +char *fgets(char *__restrict, int, FILE *__restrict); + +int fputs(const char *__restrict, FILE *__restrict); +int puts(const char *s); + +int printf(const char *__restrict, ...); +int fprintf(FILE *__restrict, const char *__restrict, ...); +int sprintf(char *__restrict, const char *__restrict, ...); +int snprintf(char *__restrict, size_t, const char *__restrict, ...); + +int vfprintf(FILE *__restrict, const char *__restrict, va_list); +int vsprintf(char *__restrict, const char *__restrict, va_list); +int vsnprintf(char *__restrict, size_t, const char *__restrict, va_list); + +int fscanf(FILE *__restrict, const char *__restrict, ...); +int sscanf(const char *__restrict, const char *__restrict, ...); + +void perror(const char *); + +int setvbuf(FILE *__restrict, char *__restrict, int, size_t); + +char *tmpnam(char *); +FILE *tmpfile(void); + +FILE *fdopen(int, const char *); +int fileno(FILE *); +off_t ftello(FILE *); + +int getc_unlocked(FILE *); +ssize_t getdelim(char **__restrict, size_t *__restrict, int, FILE *__restrict); +ssize_t getline(char **__restrict, size_t *__restrict, FILE *__restrict); + +#endif // __STDIO_H__ diff --git a/ulib/axlibc/include/stdlib.h b/ulib/axlibc/include/stdlib.h new file mode 100644 index 000000000..411ea48b1 --- /dev/null +++ b/ulib/axlibc/include/stdlib.h @@ -0,0 +1,60 @@ +#ifndef __STDLIB_H__ +#define __STDLIB_H__ + +#include +#include + +#define RAND_MAX (0x7fffffff) + +#define WEXITSTATUS(s) (((s)&0xff00) >> 8) +#define WTERMSIG(s) ((s)&0x7f) +#define WIFEXITED(s) (!WTERMSIG(s)) +#define WIFSIGNALED(s) (((s)&0xffff) - 1U < 0xffu) + +#define EXIT_FAILURE 1 +#define EXIT_SUCCESS 0 + +long long atoll(const char *nptr); + +float strtof(const char *__restrict, char **__restrict); +double strtod(const char *__restrict, char **__restrict); + +long strtol(const char *__restrict, char **__restrict, int); +unsigned long strtoul(const char *nptr, char **endptr, int base); +long long strtoll(const char *nptr, char **endptr, int base); +unsigned long long strtoull(const char *nptr, char **endptr, int base); + +int rand(void); +void srand(unsigned); +long random(void); +void srandom(unsigned int); + +#ifdef AX_CONFIG_FP_SIMD +float strtof(const char *__restrict, char **__restrict); +double strtod(const char *__restrict, char **__restrict); +long double strtold(const char *__restrict, char **__restrict); +#endif + +void qsort(void *, size_t, size_t, int (*)(const void *, const void *)); + +void *malloc(size_t); +void *calloc(size_t, size_t); +void *realloc(void *, size_t); +void free(void *); + +_Noreturn void abort(void); +_Noreturn void exit(int); + +char *getenv(const char *); + +int abs(int); +long labs(long); +long long llabs(long long); + +int mkstemp(char *); +int mkostemp(char *, int); +int setenv(const char *, const char *, int); +int unsetenv(const char *); +int system(const char *); + +#endif //__STDLIB_H__ diff --git a/ulib/axlibc/include/string.h b/ulib/axlibc/include/string.h new file mode 100644 index 000000000..3160af4ed --- /dev/null +++ b/ulib/axlibc/include/string.h @@ -0,0 +1,50 @@ +#ifndef __STRING_H__ +#define __STRING_H__ + +#include + +int atoi(const char *s); + +void *memset(void *dest, int c, size_t n); +void *memchr(const void *src, int c, size_t n); + +size_t strlen(const char *s); +size_t strnlen(const char *s, size_t n); + +char *strcpy(char *restrict d, const char *restrict s); +char *strncpy(char *restrict d, const char *restrict s, size_t n); + +char *strcat(char *restrict d, const char *restrict s); +char *strncat(char *restrict d, const char *restrict s, size_t n); + +int strcmp(const char *l, const char *r); +int strncmp(const char *l, const char *r, size_t n); + +int strcoll(const char *, const char *); + +size_t strcspn(const char *s1, const char *s2); +size_t strspn(const char *s, const char *c); +char *strpbrk(const char *, const char *); + +char *strchrnul(const char *, int); + +char *strrchr(const char *str, int c); +char *strchr(const char *str, int c); + +int strcasecmp(const char *__s1, const char *__s2); +int strncasecmp(const char *__s1, const char *__s2, size_t __n); + +char *strstr(const char *h, const char *n); + +char *strerror(int e); +int strerror_r(int, char *, size_t); + +void *memcpy(void *restrict dest, const void *restrict src, size_t n); + +void *memmove(void *dest, const void *src, size_t n); + +int memcmp(const void *vl, const void *vr, size_t n); + +char *strdup(const char *__s); + +#endif // __STRING_H__ diff --git a/ulib/axlibc/include/strings.h b/ulib/axlibc/include/strings.h new file mode 100644 index 000000000..3616bae19 --- /dev/null +++ b/ulib/axlibc/include/strings.h @@ -0,0 +1,4 @@ +#ifndef __STRINGS_H__ +#define __STRINGS_H__ + +#endif // __STRINGS_H__ diff --git a/ulib/axlibc/include/sys/epoll.h b/ulib/axlibc/include/sys/epoll.h new file mode 100644 index 000000000..bd23bcf46 --- /dev/null +++ b/ulib/axlibc/include/sys/epoll.h @@ -0,0 +1,60 @@ +#ifndef _SYS_EPOLL_H +#define _SYS_EPOLL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define EPOLL_CLOEXEC O_CLOEXEC +#define EPOLL_NONBLOCK O_NONBLOCK + +enum EPOLL_EVENTS { __EPOLL_DUMMY }; +#define EPOLLIN 0x001 +#define EPOLLPRI 0x002 +#define EPOLLOUT 0x004 +#define EPOLLRDNORM 0x040 +#define EPOLLNVAL 0x020 +#define EPOLLRDBAND 0x080 +#define EPOLLWRNORM 0x100 +#define EPOLLWRBAND 0x200 +#define EPOLLMSG 0x400 +#define EPOLLERR 0x008 +#define EPOLLHUP 0x010 +#define EPOLLRDHUP 0x2000 +#define EPOLLEXCLUSIVE (1U << 28) +#define EPOLLWAKEUP (1U << 29) +#define EPOLLONESHOT (1U << 30) +#define EPOLLET (1U << 31) + +#define EPOLL_CTL_ADD 1 +#define EPOLL_CTL_DEL 2 +#define EPOLL_CTL_MOD 3 + +typedef union epoll_data { + void *ptr; + int fd; + uint32_t u32; + uint64_t u64; +} epoll_data_t; + +struct epoll_event { + uint32_t events; + epoll_data_t data; +} +#ifdef __x86_64__ +__attribute__((__packed__)) +#endif +; + +int epoll_create(int __size); +int epoll_ctl(int, int, int, struct epoll_event *); +int epoll_wait(int, struct epoll_event *, int, int); + +#ifdef __cplusplus +} +#endif + +#endif //_SYS_EPOLL_H diff --git a/ulib/axlibc/include/sys/file.h b/ulib/axlibc/include/sys/file.h new file mode 100644 index 000000000..d5e15a67f --- /dev/null +++ b/ulib/axlibc/include/sys/file.h @@ -0,0 +1,23 @@ +#ifndef _SYS_FILE_H +#define _SYS_FILE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define LOCK_SH 1 +#define LOCK_EX 2 +#define LOCK_NB 4 +#define LOCK_UN 8 + +#define L_SET 0 +#define L_INCR 1 +#define L_XTND 2 + +int flock(int, int); + +#ifdef __cplusplus +} +#endif + +#endif // _SYS_FILE_H diff --git a/ulib/axlibc/include/sys/ioctl.h b/ulib/axlibc/include/sys/ioctl.h new file mode 100644 index 000000000..e43c51013 --- /dev/null +++ b/ulib/axlibc/include/sys/ioctl.h @@ -0,0 +1,65 @@ +#ifndef __SYS_IOCTL_H__ +#define __SYS_IOCTL_H__ + +#define TCGETS 0x5401 +#define TCSETS 0x5402 +#define TCSETSW 0x5403 +#define TCSETSF 0x5404 +#define TCGETA 0x5405 +#define TCSETA 0x5406 +#define TCSETAW 0x5407 +#define TCSETAF 0x5408 +#define TCSBRK 0x5409 +#define TCXONC 0x540A +#define TCFLSH 0x540B +#define TIOCEXCL 0x540C +#define TIOCNXCL 0x540D +#define TIOCSCTTY 0x540E +#define TIOCGPGRP 0x540F +#define TIOCSPGRP 0x5410 +#define TIOCOUTQ 0x5411 +#define TIOCSTI 0x5412 +#define TIOCGWINSZ 0x5413 +#define TIOCSWINSZ 0x5414 +#define TIOCMGET 0x5415 +#define TIOCMBIS 0x5416 +#define TIOCMBIC 0x5417 +#define TIOCMSET 0x5418 +#define TIOCGSOFTCAR 0x5419 +#define TIOCSSOFTCAR 0x541A +#define FIONREAD 0x541B +#define TIOCINQ FIONREAD +#define TIOCLINUX 0x541C +#define TIOCCONS 0x541D +#define TIOCGSERIAL 0x541E +#define TIOCSSERIAL 0x541F +#define TIOCPKT 0x5420 +#define FIONBIO 0x5421 +#define TIOCNOTTY 0x5422 +#define TIOCSETD 0x5423 +#define TIOCGETD 0x5424 +#define TCSBRKP 0x5425 +#define TIOCSBRK 0x5427 +#define TIOCCBRK 0x5428 +#define TIOCGSID 0x5429 +#define TIOCGRS485 0x542E +#define TIOCSRS485 0x542F +#define TIOCGPTN 0x80045430 +#define TIOCSPTLCK 0x40045431 +#define TIOCGDEV 0x80045432 +#define TCGETX 0x5432 +#define TCSETX 0x5433 +#define TCSETXF 0x5434 +#define TCSETXW 0x5435 +#define TIOCSIG 0x40045436 +#define TIOCVHANGUP 0x5437 +#define TIOCGPKT 0x80045438 +#define TIOCGPTLCK 0x80045439 +#define TIOCGEXCL 0x80045440 +#define TIOCGPTPEER 0x5441 +#define TIOCGISO7816 0x80285442 +#define TIOCSISO7816 0xc0285443 + +int ioctl(int, int, ...); + +#endif // __SYS_IOCTL_H__ diff --git a/ulib/axlibc/include/sys/mman.h b/ulib/axlibc/include/sys/mman.h new file mode 100644 index 000000000..3d3502345 --- /dev/null +++ b/ulib/axlibc/include/sys/mman.h @@ -0,0 +1,52 @@ +#ifndef __SYS_MMAN_H__ +#define __SYS_MMAN_H__ + +#include + +#define PROT_READ 0x1 /* Page can be read. */ +#define PROT_WRITE 0x2 /* Page can be written. */ +#define PROT_EXEC 0x4 /* Page can be executed. */ +#define PROT_NONE 0x0 /* Page can not be accessed. */ +#define PROT_GROWSDOWN \ + 0x01000000 /* Extend change to start of \ + growsdown vma (mprotect only). */ +#define PROT_GROWSUP \ + 0x02000000 /* Extend change to start of \ + growsup vma (mprotect only). */ + +/* Sharing types (must choose one and only one of these). */ +#define MAP_SHARED 0x01 /* Share changes. */ +#define MAP_PRIVATE 0x02 /* Changes are private. */ +#define MAP_SHARED_VALIDATE \ + 0x03 /* Share changes and validate \ + extension flags. */ +#define MAP_TYPE 0x0f /* Mask for type of mapping. */ + +/* Other flags. */ +#define MAP_FIXED 0x10 /* Interpret addr exactly. */ +#define MAP_FILE 0 +#ifdef __MAP_ANONYMOUS +#define MAP_ANONYMOUS __MAP_ANONYMOUS /* Don't use a file. */ +#else +#define MAP_ANONYMOUS 0x20 /* Don't use a file. */ +#endif +#define MAP_ANON MAP_ANONYMOUS +/* When MAP_HUGETLB is set bits [26:31] encode the log2 of the huge page size. */ +#define MAP_HUGE_SHIFT 26 +#define MAP_HUGE_MASK 0x3f + +#define MAP_FAILED ((void *)-1) + +/* Flags for mremap. */ +#define MREMAP_MAYMOVE 1 +#define MREMAP_FIXED 2 +#define MREMAP_DONTUNMAP 4 + +void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); +int munmap(void *addr, size_t length); +void *mremap(void *old_address, size_t old_size, size_t new_size, int flags, + ... /* void *new_address */); +int mprotect(void *addr, size_t len, int prot); +int madvise(void *addr, size_t length, int advice); + +#endif diff --git a/ulib/axlibc/include/sys/param.h b/ulib/axlibc/include/sys/param.h new file mode 100644 index 000000000..bbb762f54 --- /dev/null +++ b/ulib/axlibc/include/sys/param.h @@ -0,0 +1,6 @@ +#ifndef _SYS_PARAM_H +#define _SYS_PARAM_H + +#define MAXPATHLEN 4096 + +#endif // _SYS_PARAM_H diff --git a/ulib/axlibc/include/sys/prctl.h b/ulib/axlibc/include/sys/prctl.h new file mode 100644 index 000000000..1ed9cc10e --- /dev/null +++ b/ulib/axlibc/include/sys/prctl.h @@ -0,0 +1,4 @@ +#ifndef _SYS_PRCTL_H +#define _SYS_PRCTL_H + +#endif // _SYS_PRCTL_H diff --git a/ulib/axlibc/include/sys/resource.h b/ulib/axlibc/include/sys/resource.h new file mode 100644 index 000000000..99c8bbc6d --- /dev/null +++ b/ulib/axlibc/include/sys/resource.h @@ -0,0 +1,63 @@ +#ifndef _SYS_RESOURCE_H +#define _SYS_RESOURCE_H + +#include + +typedef unsigned long long rlim_t; + +struct rlimit { + rlim_t rlim_cur; + rlim_t rlim_max; +}; + +#define RLIMIT_CPU 0 +#define RLIMIT_FSIZE 1 +#define RLIMIT_DATA 2 +#define RLIMIT_STACK 3 +#define RLIMIT_CORE 4 +#ifndef RLIMIT_RSS +#define RLIMIT_RSS 5 +#define RLIMIT_NPROC 6 +#define RLIMIT_NOFILE 7 +#define RLIMIT_MEMLOCK 8 +#define RLIMIT_AS 9 +#endif +#define RLIMIT_LOCKS 10 +#define RLIMIT_SIGPENDING 11 +#define RLIMIT_MSGQUEUE 12 +#define RLIMIT_NICE 13 +#define RLIMIT_RTPRIO 14 +#define RLIMIT_RTTIME 15 +#define RLIMIT_NLIMITS 16 + +#define RUSAGE_SELF 0 +#define RUSAGE_CHILDREN -1 + +struct rusage { + struct timeval ru_utime; + struct timeval ru_stime; + /* linux extentions, but useful */ + long ru_maxrss; + long ru_ixrss; + long ru_idrss; + long ru_isrss; + long ru_minflt; + long ru_majflt; + long ru_nswap; + long ru_inblock; + long ru_oublock; + long ru_msgsnd; + long ru_msgrcv; + long ru_nsignals; + long ru_nvcsw; + long ru_nivcsw; + /* room for more... */ + long __reserved[16]; +}; + +int setrlimit(int __resource, struct rlimit *__rlimits); +int getrlimit(int __resource, struct rlimit *__rlimits); + +int getrusage(int __who, struct rusage *__usage); + +#endif diff --git a/ulib/axlibc/include/sys/select.h b/ulib/axlibc/include/sys/select.h new file mode 100644 index 000000000..e8cc2c5ae --- /dev/null +++ b/ulib/axlibc/include/sys/select.h @@ -0,0 +1,36 @@ +#ifndef _SYS_SELECT_H +#define _SYS_SELECT_H + +#include +#include +#include + +#define FD_SETSIZE 1024 + +typedef unsigned long fd_mask; + +typedef struct { + unsigned long fds_bits[FD_SETSIZE / 8 / sizeof(long)]; +} fd_set; + +#define FD_ZERO(s) \ + do { \ + int __i; \ + unsigned long *__b = (s)->fds_bits; \ + for (__i = sizeof(fd_set) / sizeof(long); __i; __i--) *__b++ = 0; \ + } while (0) +#define FD_SET(d, s) \ + ((s)->fds_bits[(d) / (8 * sizeof(long))] |= (1UL << ((d) % (8 * sizeof(long))))) +#define FD_CLR(d, s) \ + ((s)->fds_bits[(d) / (8 * sizeof(long))] &= ~(1UL << ((d) % (8 * sizeof(long))))) +#define FD_ISSET(d, s) \ + !!((s)->fds_bits[(d) / (8 * sizeof(long))] & (1UL << ((d) % (8 * sizeof(long))))) + +int select(int n, fd_set *__restrict rfds, fd_set *__restrict wfds, fd_set *__restrict efds, + struct timeval *__restrict tv); +int pselect(int, fd_set *__restrict, fd_set *__restrict, fd_set *__restrict, + const struct timespec *__restrict, const sigset_t *__restrict); + +#define NFDBITS (8 * (int)sizeof(long)) + +#endif diff --git a/ulib/axlibc/include/sys/socket.h b/ulib/axlibc/include/sys/socket.h new file mode 100644 index 000000000..c89c5f1c5 --- /dev/null +++ b/ulib/axlibc/include/sys/socket.h @@ -0,0 +1,310 @@ +#ifndef __SOCKET_H__ +#define __SOCKET_H__ + +#include +#include +#include + +typedef unsigned socklen_t; +typedef unsigned short sa_family_t; + +struct msghdr { + void *msg_name; + socklen_t msg_namelen; + struct iovec *msg_iov; +#if __LONG_MAX > 0x7fffffff && __BYTE_ORDER == __BIG_ENDIAN + int __pad1; +#endif + int msg_iovlen; +#if __LONG_MAX > 0x7fffffff && __BYTE_ORDER == __LITTLE_ENDIAN + int __pad1; +#endif + void *msg_control; +#if __LONG_MAX > 0x7fffffff && __BYTE_ORDER == __BIG_ENDIAN + int __pad2; +#endif + socklen_t msg_controllen; +#if __LONG_MAX > 0x7fffffff && __BYTE_ORDER == __LITTLE_ENDIAN + int __pad2; +#endif + int msg_flags; +}; + +struct cmsghdr { +#if __LONG_MAX > 0x7fffffff && __BYTE_ORDER == __BIG_ENDIAN + int __pad1; +#endif + socklen_t cmsg_len; +#if __LONG_MAX > 0x7fffffff && __BYTE_ORDER == __LITTLE_ENDIAN + int __pad1; +#endif + int cmsg_level; + int cmsg_type; +}; + +struct sockaddr { + sa_family_t sa_family; + char sa_data[14]; +}; + +struct sockaddr_storage { + sa_family_t ss_family; + char __ss_padding[128 - sizeof(long) - sizeof(sa_family_t)]; + unsigned long __ss_align; +}; + +int socket(int, int, int); +int shutdown(int, int); + +int bind(int, const struct sockaddr *, socklen_t); +int connect(int, const struct sockaddr *, socklen_t); +int listen(int, int); +int accept(int, struct sockaddr *__restrict, socklen_t *__restrict); +int accept4(int, struct sockaddr *__restrict, socklen_t *__restrict, int); + +ssize_t send(int, const void *, size_t, int); +ssize_t recv(int, void *, size_t, int); +ssize_t sendto(int, const void *, size_t, int, const struct sockaddr *, socklen_t); +ssize_t recvfrom(int, void *__restrict, size_t, int, struct sockaddr *__restrict, + socklen_t *__restrict); +ssize_t sendmsg(int, const struct msghdr *, int); + +int getsockopt(int, int, int, void *__restrict, socklen_t *__restrict); +int setsockopt(int, int, int, const void *, socklen_t); + +int getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict addrlen); +int getpeername(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict addrlen); + +#define SO_BINDTODEVICE 25 +#define SO_ATTACH_FILTER 26 +#define SO_DETACH_FILTER 27 +#define SO_GET_FILTER SO_ATTACH_FILTER +#define SO_PEERNAME 28 +#define SO_ACCEPTCONN 30 +#define SO_PEERSEC 31 +#define SO_PASSSEC 34 +#define SO_MARK 36 +#define SO_RXQ_OVFL 40 +#define SO_WIFI_STATUS 41 +#define SCM_WIFI_STATUS SO_WIFI_STATUS +#define SO_PEEK_OFF 42 +#define SO_NOFCS 43 +#define SO_LOCK_FILTER 44 +#define SO_SELECT_ERR_QUEUE 45 +#define SO_BUSY_POLL 46 +#define SO_MAX_PACING_RATE 47 +#define SO_BPF_EXTENSIONS 48 +#define SO_INCOMING_CPU 49 +#define SO_ATTACH_BPF 50 +#define SO_DETACH_BPF SO_DETACH_FILTER +#define SO_ATTACH_REUSEPORT_CBPF 51 +#define SO_ATTACH_REUSEPORT_EBPF 52 +#define SO_CNX_ADVICE 53 +#define SCM_TIMESTAMPING_OPT_STATS 54 +#define SO_MEMINFO 55 +#define SO_INCOMING_NAPI_ID 56 +#define SO_COOKIE 57 +#define SCM_TIMESTAMPING_PKTINFO 58 +#define SO_PEERGROUPS 59 +#define SO_ZEROCOPY 60 +#define SO_TXTIME 61 +#define SCM_TXTIME SO_TXTIME +#define SO_BINDTOIFINDEX 62 +#define SO_DETACH_REUSEPORT_BPF 68 +#define SO_PREFER_BUSY_POLL 69 +#define SO_BUSY_POLL_BUDGET 70 + +#define MSG_NOSIGNAL 0x4000 + +#define SHUT_RD 0 +#define SHUT_WR 1 +#define SHUT_RDWR 2 + +#ifndef SOCK_STREAM +#define SOCK_STREAM 1 +#define SOCK_DGRAM 2 +#endif + +#define SOCK_RAW 3 +#define SOCK_RDM 4 +#define SOCK_SEQPACKET 5 +#define SOCK_DCCP 6 +#define SOCK_PACKET 10 + +#ifndef SOCK_CLOEXEC +#define SOCK_CLOEXEC 02000000 +#define SOCK_NONBLOCK 04000 +#endif + +#define PF_UNSPEC 0 +#define PF_LOCAL 1 +#define PF_UNIX PF_LOCAL +#define PF_FILE PF_LOCAL +#define PF_INET 2 +#define PF_AX25 3 +#define PF_IPX 4 +#define PF_APPLETALK 5 +#define PF_NETROM 6 +#define PF_BRIDGE 7 +#define PF_ATMPVC 8 +#define PF_X25 9 +#define PF_INET6 10 +#define PF_ROSE 11 +#define PF_DECnet 12 +#define PF_NETBEUI 13 +#define PF_SECURITY 14 +#define PF_KEY 15 +#define PF_NETLINK 16 +#define PF_ROUTE PF_NETLINK +#define PF_PACKET 17 +#define PF_ASH 18 +#define PF_ECONET 19 +#define PF_ATMSVC 20 +#define PF_RDS 21 +#define PF_SNA 22 +#define PF_IRDA 23 +#define PF_PPPOX 24 +#define PF_WANPIPE 25 +#define PF_LLC 26 +#define PF_IB 27 +#define PF_MPLS 28 +#define PF_CAN 29 +#define PF_TIPC 30 +#define PF_BLUETOOTH 31 +#define PF_IUCV 32 +#define PF_RXRPC 33 +#define PF_ISDN 34 +#define PF_PHONET 35 +#define PF_IEEE802154 36 +#define PF_CAIF 37 +#define PF_ALG 38 +#define PF_NFC 39 +#define PF_VSOCK 40 +#define PF_KCM 41 +#define PF_QIPCRTR 42 +#define PF_SMC 43 +#define PF_XDP 44 +#define PF_MAX 45 + +#define AF_UNSPEC PF_UNSPEC +#define AF_LOCAL PF_LOCAL +#define AF_UNIX AF_LOCAL +#define AF_FILE AF_LOCAL +#define AF_INET PF_INET +#define AF_AX25 PF_AX25 +#define AF_IPX PF_IPX +#define AF_APPLETALK PF_APPLETALK +#define AF_NETROM PF_NETROM +#define AF_BRIDGE PF_BRIDGE +#define AF_ATMPVC PF_ATMPVC +#define AF_X25 PF_X25 +#define AF_INET6 PF_INET6 +#define AF_ROSE PF_ROSE +#define AF_DECnet PF_DECnet +#define AF_NETBEUI PF_NETBEUI +#define AF_SECURITY PF_SECURITY +#define AF_KEY PF_KEY +#define AF_NETLINK PF_NETLINK +#define AF_ROUTE PF_ROUTE +#define AF_PACKET PF_PACKET +#define AF_ASH PF_ASH +#define AF_ECONET PF_ECONET +#define AF_ATMSVC PF_ATMSVC +#define AF_RDS PF_RDS +#define AF_SNA PF_SNA +#define AF_IRDA PF_IRDA +#define AF_PPPOX PF_PPPOX +#define AF_WANPIPE PF_WANPIPE +#define AF_LLC PF_LLC +#define AF_IB PF_IB +#define AF_MPLS PF_MPLS +#define AF_CAN PF_CAN +#define AF_TIPC PF_TIPC +#define AF_BLUETOOTH PF_BLUETOOTH +#define AF_IUCV PF_IUCV +#define AF_RXRPC PF_RXRPC +#define AF_ISDN PF_ISDN +#define AF_PHONET PF_PHONET +#define AF_IEEE802154 PF_IEEE802154 +#define AF_CAIF PF_CAIF +#define AF_ALG PF_ALG +#define AF_NFC PF_NFC +#define AF_VSOCK PF_VSOCK +#define AF_KCM PF_KCM +#define AF_QIPCRTR PF_QIPCRTR +#define AF_SMC PF_SMC +#define AF_XDP PF_XDP +#define AF_MAX PF_MAX + +#define SO_DEBUG 1 +#define SO_REUSEADDR 2 +#define SO_TYPE 3 +#define SO_ERROR 4 +#define SO_DONTROUTE 5 +#define SO_BROADCAST 6 +#define SO_SNDBUF 7 +#define SO_RCVBUF 8 +#define SO_KEEPALIVE 9 +#define SO_OOBINLINE 10 +#define SO_NO_CHECK 11 +#define SO_PRIORITY 12 +#define SO_LINGER 13 +#define SO_BSDCOMPAT 14 +#define SO_REUSEPORT 15 +#define SO_PASSCRED 16 +#define SO_PEERCRED 17 +#define SO_RCVLOWAT 18 +#define SO_SNDLOWAT 19 +#define SO_ACCEPTCONN 30 +#define SO_PEERSEC 31 +#define SO_SNDBUFFORCE 32 +#define SO_RCVBUFFORCE 33 +#define SO_PROTOCOL 38 +#define SO_DOMAIN 39 + +#define SO_SECURITY_AUTHENTICATION 22 +#define SO_SECURITY_ENCRYPTION_TRANSPORT 23 +#define SO_SECURITY_ENCRYPTION_NETWORK 24 + +#define SOL_SOCKET 1 + +#define SOL_IP 0 +#define SOL_IPV6 41 +#define SOL_ICMPV6 58 + +#define SOL_RAW 255 +#define SOL_DECNET 261 +#define SOL_X25 262 +#define SOL_PACKET 263 +#define SOL_ATM 264 +#define SOL_AAL 265 +#define SOL_IRDA 266 +#define SOL_NETBEUI 267 +#define SOL_LLC 268 +#define SOL_DCCP 269 +#define SOL_NETLINK 270 +#define SOL_TIPC 271 +#define SOL_RXRPC 272 +#define SOL_PPPOL2TP 273 +#define SOL_BLUETOOTH 274 +#define SOL_PNPIPE 275 +#define SOL_RDS 276 +#define SOL_IUCV 277 +#define SOL_CAIF 278 +#define SOL_ALG 279 +#define SOL_NFC 280 +#define SOL_KCM 281 +#define SOL_TLS 282 +#define SOL_XDP 283 + +#ifndef SO_RCVTIMEO_OLD +#define SO_RCVTIMEO_OLD 20 +#endif +#ifndef SO_SNDTIMEO_OLD +#define SO_SNDTIMEO_OLD 21 +#endif + +#define SO_RCVTIMEO SO_RCVTIMEO_OLD +#define SO_SNDTIMEO SO_SNDTIMEO_OLD + +#endif // __SOCKET_H__ diff --git a/ulib/axlibc/include/sys/stat.h b/ulib/axlibc/include/sys/stat.h new file mode 100644 index 000000000..24806af7a --- /dev/null +++ b/ulib/axlibc/include/sys/stat.h @@ -0,0 +1,78 @@ +#ifndef __SYS_STAT_H__ +#define __SYS_STAT_H__ + +#include +#include + +struct stat { + dev_t st_dev; /* ID of device containing file*/ + ino_t st_ino; /* inode number*/ + mode_t st_mode; /* protection*/ + nlink_t st_nlink; /* number of hard links*/ + uid_t st_uid; /* user ID of owner*/ + gid_t st_gid; /* group ID of owner*/ + dev_t st_rdev; /* device ID (if special file)*/ + off_t st_size; /* total size, in bytes*/ + blksize_t st_blksize; /* blocksize for filesystem I/O*/ + blkcnt_t st_blocks; /* number of blocks allocated*/ + struct timespec st_atime; /* time of last access*/ + struct timespec st_mtime; /* time of last modification*/ + struct timespec st_ctime; /* time of last status change*/ +}; + +#define st_atime st_atim.tv_sec +#define st_mtime st_mtim.tv_sec +#define st_ctime st_ctim.tv_sec + +#define S_IFMT 0170000 + +#define S_IFDIR 0040000 +#define S_IFCHR 0020000 +#define S_IFBLK 0060000 +#define S_IFREG 0100000 +#define S_IFIFO 0010000 +#define S_IFLNK 0120000 +#define S_IFSOCK 0140000 + +#define S_TYPEISMQ(buf) 0 +#define S_TYPEISSEM(buf) 0 +#define S_TYPEISSHM(buf) 0 +#define S_TYPEISTMO(buf) 0 + +#define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) +#define S_ISCHR(mode) (((mode)&S_IFMT) == S_IFCHR) +#define S_ISBLK(mode) (((mode)&S_IFMT) == S_IFBLK) +#define S_ISREG(mode) (((mode)&S_IFMT) == S_IFREG) +#define S_ISFIFO(mode) (((mode)&S_IFMT) == S_IFIFO) +#define S_ISLNK(mode) (((mode)&S_IFMT) == S_IFLNK) +#define S_ISSOCK(mode) (((mode)&S_IFMT) == S_IFSOCK) + +#ifndef S_IRUSR +#define S_ISUID 04000 +#define S_ISGID 02000 +#define S_ISVTX 01000 +#define S_IRUSR 0400 +#define S_IWUSR 0200 +#define S_IXUSR 0100 +#define S_IRWXU 0700 +#define S_IRGRP 0040 +#define S_IWGRP 0020 +#define S_IXGRP 0010 +#define S_IRWXG 0070 +#define S_IROTH 0004 +#define S_IWOTH 0002 +#define S_IXOTH 0001 +#define S_IRWXO 0007 +#endif + +int stat(const char *path, struct stat *buf); +int fstat(int fd, struct stat *buf); +int lstat(const char *path, struct stat *buf); + +int fchmod(int fd, mode_t mode); +int chmod(const char *file, mode_t mode); +int mkdir(const char *pathname, mode_t mode); +mode_t umask(mode_t mask); +int fstatat(int, const char *__restrict, struct stat *__restrict, int); + +#endif diff --git a/ulib/axlibc/include/sys/time.h b/ulib/axlibc/include/sys/time.h new file mode 100644 index 000000000..5eeaf4c6b --- /dev/null +++ b/ulib/axlibc/include/sys/time.h @@ -0,0 +1,41 @@ +#ifndef __SYS_TIME_H__ +#define __SYS_TIME_H__ + +#include + +#define ITIMER_REAL 0 +#define ITIMER_VIRTUAL 1 +#define ITIMER_PROF 2 + +extern long timezone; +typedef long long time_t; + +struct timeval { + time_t tv_sec; /* seconds */ + long tv_usec; /* microseconds */ +}; + +struct timespec { + time_t tv_sec; /* seconds */ + long tv_nsec; /* nanoseconds */ +}; + +typedef struct timespec timespec; + +struct timezone { + int tz_minuteswest; /* (minutes west of Greenwich) */ + int tz_dsttime; /* (type of DST correction) */ +}; + +struct itimerval { + struct timeval it_interval; + struct timeval it_value; +}; + +int gettimeofday(struct timeval *tv, struct timezone *tz); + +int getitimer(int, struct itimerval *); +int setitimer(int, const struct itimerval *__restrict, struct itimerval *__restrict); +int utimes(const char *filename, const struct timeval times[2]); + +#endif diff --git a/ulib/axlibc/include/sys/types.h b/ulib/axlibc/include/sys/types.h new file mode 100644 index 000000000..adb6c3531 --- /dev/null +++ b/ulib/axlibc/include/sys/types.h @@ -0,0 +1,20 @@ +#ifndef __SYS_TYPES_H__ +#define __SYS_TYPES_H__ + +#include + +typedef unsigned char u_char; + +typedef unsigned mode_t; +typedef uint32_t nlink_t; +typedef int64_t off_t; +typedef uint64_t ino_t; +typedef uint64_t dev_t; +typedef long blksize_t; +typedef int64_t blkcnt_t; + +typedef int pid_t; +typedef unsigned uid_t; +typedef unsigned gid_t; + +#endif // __SYS_TYPES_H__ diff --git a/ulib/axlibc/include/sys/uio.h b/ulib/axlibc/include/sys/uio.h new file mode 100644 index 000000000..d38b197c8 --- /dev/null +++ b/ulib/axlibc/include/sys/uio.h @@ -0,0 +1,13 @@ +#ifndef _SYS_UIO_H +#define _SYS_UIO_H + +#include + +struct iovec { + void *iov_base; /* Pointer to data. */ + size_t iov_len; /* Length of data. */ +}; + +ssize_t writev(int, const struct iovec *, int); + +#endif diff --git a/ulib/axlibc/include/sys/un.h b/ulib/axlibc/include/sys/un.h new file mode 100644 index 000000000..85e43ea7b --- /dev/null +++ b/ulib/axlibc/include/sys/un.h @@ -0,0 +1,11 @@ +#ifndef _SYS_UN_H +#define _SYS_UN_H + +#include + +struct sockaddr_un { + sa_family_t sun_family; + char sun_path[108]; +}; + +#endif // _SYS_UN_H diff --git a/ulib/axlibc/include/sys/utsname.h b/ulib/axlibc/include/sys/utsname.h new file mode 100644 index 000000000..fa708a129 --- /dev/null +++ b/ulib/axlibc/include/sys/utsname.h @@ -0,0 +1,27 @@ +#ifndef _SYS_UTSNAME_H +#define _SYS_UTSNAME_H + +#ifdef __cplusplus +extern "C" { +#endif + +struct utsname { + char sysname[65]; + char nodename[65]; + char release[65]; + char version[65]; + char machine[65]; +#ifdef _GNU_SOURCE + char domainname[65]; +#else + char __domainname[65]; +#endif +}; + +int uname(struct utsname *); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ulib/axlibc/include/sys/wait.h b/ulib/axlibc/include/sys/wait.h new file mode 100644 index 000000000..f5e38877e --- /dev/null +++ b/ulib/axlibc/include/sys/wait.h @@ -0,0 +1,12 @@ +#ifndef _SYS_WAIT_H +#define _SYS_WAIT_H + +#include +#include + +#define WNOHANG 1 + +pid_t waitpid(pid_t pid, int *status, int options); +pid_t wait3(int *, int, struct rusage *); + +#endif diff --git a/ulib/axlibc/include/syslog.h b/ulib/axlibc/include/syslog.h new file mode 100644 index 000000000..f98d66342 --- /dev/null +++ b/ulib/axlibc/include/syslog.h @@ -0,0 +1,45 @@ +#ifndef _SYSLOG_H +#define _SYSLOG_H + +#define LOG_EMERG 0 +#define LOG_ALERT 1 +#define LOG_CRIT 2 +#define LOG_ERR 3 +#define LOG_WARNING 4 +#define LOG_NOTICE 5 +#define LOG_INFO 6 +#define LOG_DEBUG 7 + +#define LOG_PID 0x01 +#define LOG_CONS 0x02 +#define LOG_ODELAY 0x04 +#define LOG_NDELAY 0x08 +#define LOG_NOWAIT 0x10 +#define LOG_PERROR 0x20 + +#define LOG_KERN (0 << 3) +#define LOG_USER (1 << 3) +#define LOG_MAIL (2 << 3) +#define LOG_DAEMON (3 << 3) +#define LOG_AUTH (4 << 3) +#define LOG_SYSLOG (5 << 3) +#define LOG_LPR (6 << 3) +#define LOG_NEWS (7 << 3) +#define LOG_UUCP (8 << 3) +#define LOG_CRON (9 << 3) +#define LOG_AUTHPRIV (10 << 3) +#define LOG_FTP (11 << 3) + +#define LOG_LOCAL0 (16 << 3) +#define LOG_LOCAL1 (17 << 3) +#define LOG_LOCAL2 (18 << 3) +#define LOG_LOCAL3 (19 << 3) +#define LOG_LOCAL4 (20 << 3) +#define LOG_LOCAL5 (21 << 3) +#define LOG_LOCAL6 (22 << 3) +#define LOG_LOCAL7 (23 << 3) + +void syslog(int, const char *, ...); +void openlog(const char *, int, int); + +#endif diff --git a/ulib/axlibc/include/termios.h b/ulib/axlibc/include/termios.h new file mode 100644 index 000000000..d319c5b81 --- /dev/null +++ b/ulib/axlibc/include/termios.h @@ -0,0 +1,8 @@ +#ifndef _TERMIOS_H +#define _TERMIOS_H + +struct winsize { + unsigned short ws_row, ws_col, ws_xpixel, ws_ypixel; +}; + +#endif // _TERMIOS_H diff --git a/ulib/axlibc/include/time.h b/ulib/axlibc/include/time.h new file mode 100644 index 000000000..356edf920 --- /dev/null +++ b/ulib/axlibc/include/time.h @@ -0,0 +1,43 @@ +#ifndef __TIME_H__ +#define __TIME_H__ + +#include +#include + +#define CLOCK_REALTIME 0 +#define CLOCK_MONOTONIC 1 +#define CLOCKS_PER_SEC 1000000L + +struct tm { + int tm_sec; /* seconds of minute */ + int tm_min; /* minutes of hour */ + int tm_hour; /* hours of day */ + int tm_mday; /* day of month */ + int tm_mon; /* month of year, 0 is first month(January) */ + int tm_year; /* years, whose value equals the actual year minus 1900 */ + int tm_wday; /* day of week, 0 is sunday, 1 is monday, and so on */ + int tm_yday; /* day of year */ + int tm_isdst; /* daylight saving time flag */ + long int __tm_gmtoff; + const char *__tm_zone; +}; + +clock_t clock(void); +time_t time(time_t *); +double difftime(time_t, time_t); +time_t mktime(struct tm *); +size_t strftime(char *__restrict, size_t, const char *__restrict, const struct tm *__restrict); +struct tm *gmtime(const time_t *); +struct tm *localtime(const time_t *); + +struct tm *gmtime_r(const time_t *__restrict, struct tm *__restrict); +struct tm *localtime_r(const time_t *__restrict, struct tm *__restrict); +char *asctime_r(const struct tm *__restrict, char *__restrict); +char *ctime_r(const time_t *, char *); + +void tzset(void); + +int nanosleep(const struct timespec *requested_time, struct timespec *remaining); +int clock_gettime(clockid_t _clk, struct timespec *ts); + +#endif // __TIME_H__ diff --git a/ulib/axlibc/include/unistd.h b/ulib/axlibc/include/unistd.h new file mode 100644 index 000000000..46b580df8 --- /dev/null +++ b/ulib/axlibc/include/unistd.h @@ -0,0 +1,240 @@ +#ifndef __UNISTD_H__ +#define __UNISTD_H__ + +#include +#include +#include + +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +#define SEEK_SET 0 +#define SEEK_CUR 1 +#define SEEK_END 2 +#define SEEK_DATA 3 +#define SEEK_HOLE 4 + +int pipe(int[2]); +int pipe2(int[2], int); +int close(int); +int posix_close(int, int); +int dup(int); +int dup2(int, int); +int dup3(int, int, int); +off_t lseek(int, off_t, int); +int fsync(int); +int fdatasync(int); + +ssize_t read(int, void *, size_t); +ssize_t write(int, const void *, size_t); +ssize_t pread(int, void *, size_t, off_t); +ssize_t pwrite(int, const void *, size_t, off_t); + +int chown(const char *, uid_t, gid_t); +int fchown(int, uid_t, gid_t); +int lchown(const char *, uid_t, gid_t); +int fchownat(int, const char *, uid_t, gid_t, int); + +int link(const char *, const char *); +int linkat(int, const char *, int, const char *, int); +int symlink(const char *, const char *); +int symlinkat(const char *, int, const char *); +ssize_t readlink(const char *__restrict, char *__restrict, size_t); +ssize_t readlinkat(int, const char *__restrict, char *__restrict, size_t); +int unlink(const char *); +int unlinkat(int, const char *, int); +int rmdir(const char *); +int truncate(const char *, off_t); +int ftruncate(int, off_t); + +#define F_OK 0 +#define R_OK 4 +#define W_OK 2 +#define X_OK 1 + +int access(const char *, int); +int faccessat(int, const char *, int, int); + +int chdir(const char *); +int fchdir(int); +char *getcwd(char *, size_t); + +unsigned alarm(unsigned); +unsigned sleep(unsigned); +int pause(void); +int usleep(unsigned); + +pid_t fork(void); +int execve(const char *, char *const[], char *const[]); +_Noreturn void _exit(int); + +pid_t getpid(void); +pid_t getppid(void); +pid_t getpgrp(void); +pid_t getpgid(pid_t); +int setpgid(pid_t, pid_t); +pid_t setsid(void); +pid_t getsid(pid_t); +char *ttyname(int); +int ttyname_r(int, char *, size_t); +int isatty(int); +pid_t tcgetpgrp(int); +int tcsetpgrp(int, pid_t); + +uid_t getuid(void); +uid_t geteuid(void); +gid_t getgid(void); +gid_t getegid(void); +int getgroups(int, gid_t[]); +int setuid(uid_t); +int seteuid(uid_t); +int setgid(gid_t); +int setegid(gid_t); + +long sysconf(int); + +#define _SC_ARG_MAX 0 +#define _SC_CHILD_MAX 1 +#define _SC_CLK_TCK 2 +#define _SC_NGROUPS_MAX 3 +#define _SC_OPEN_MAX 4 +#define _SC_STREAM_MAX 5 +#define _SC_TZNAME_MAX 6 +#define _SC_JOB_CONTROL 7 +#define _SC_SAVED_IDS 8 +#define _SC_REALTIME_SIGNALS 9 +#define _SC_PRIORITY_SCHEDULING 10 +#define _SC_TIMERS 11 +#define _SC_ASYNCHRONOUS_IO 12 +#define _SC_PRIORITIZED_IO 13 +#define _SC_SYNCHRONIZED_IO 14 +#define _SC_FSYNC 15 +#define _SC_MAPPED_FILES 16 +#define _SC_MEMLOCK 17 +#define _SC_MEMLOCK_RANGE 18 +#define _SC_MEMORY_PROTECTION 19 +#define _SC_MESSAGE_PASSING 20 +#define _SC_SEMAPHORES 21 +#define _SC_SHARED_MEMORY_OBJECTS 22 +#define _SC_AIO_LISTIO_MAX 23 +#define _SC_AIO_MAX 24 +#define _SC_AIO_PRIO_DELTA_MAX 25 +#define _SC_DELAYTIMER_MAX 26 +#define _SC_MQ_OPEN_MAX 27 +#define _SC_MQ_PRIO_MAX 28 +#define _SC_VERSION 29 +#define _SC_PAGE_SIZE 30 +#define _SC_PAGESIZE 30 /* !! */ +#define _SC_RTSIG_MAX 31 +#define _SC_SEM_NSEMS_MAX 32 +#define _SC_SEM_VALUE_MAX 33 +#define _SC_SIGQUEUE_MAX 34 +#define _SC_TIMER_MAX 35 +#define _SC_BC_BASE_MAX 36 +#define _SC_BC_DIM_MAX 37 +#define _SC_BC_SCALE_MAX 38 +#define _SC_BC_STRING_MAX 39 +#define _SC_COLL_WEIGHTS_MAX 40 +#define _SC_EXPR_NEST_MAX 42 +#define _SC_LINE_MAX 43 +#define _SC_RE_DUP_MAX 44 +#define _SC_2_VERSION 46 +#define _SC_2_C_BIND 47 +#define _SC_2_C_DEV 48 +#define _SC_2_FORT_DEV 49 +#define _SC_2_FORT_RUN 50 +#define _SC_2_SW_DEV 51 +#define _SC_2_LOCALEDEF 52 +#define _SC_UIO_MAXIOV 60 /* !! */ +#define _SC_IOV_MAX 60 +#define _SC_THREADS 67 +#define _SC_THREAD_SAFE_FUNCTIONS 68 +#define _SC_GETGR_R_SIZE_MAX 69 +#define _SC_GETPW_R_SIZE_MAX 70 +#define _SC_LOGIN_NAME_MAX 71 +#define _SC_TTY_NAME_MAX 72 +#define _SC_THREAD_DESTRUCTOR_ITERATIONS 73 +#define _SC_THREAD_KEYS_MAX 74 +#define _SC_THREAD_STACK_MIN 75 +#define _SC_THREAD_THREADS_MAX 76 +#define _SC_THREAD_ATTR_STACKADDR 77 +#define _SC_THREAD_ATTR_STACKSIZE 78 +#define _SC_THREAD_PRIORITY_SCHEDULING 79 +#define _SC_THREAD_PRIO_INHERIT 80 +#define _SC_THREAD_PRIO_PROTECT 81 +#define _SC_THREAD_PROCESS_SHARED 82 +#define _SC_NPROCESSORS_CONF 83 +#define _SC_NPROCESSORS_ONLN 84 +#define _SC_PHYS_PAGES 85 +#define _SC_AVPHYS_PAGES 86 +#define _SC_ATEXIT_MAX 87 +#define _SC_PASS_MAX 88 +#define _SC_XOPEN_VERSION 89 +#define _SC_XOPEN_XCU_VERSION 90 +#define _SC_XOPEN_UNIX 91 +#define _SC_XOPEN_CRYPT 92 +#define _SC_XOPEN_ENH_I18N 93 +#define _SC_XOPEN_SHM 94 +#define _SC_2_CHAR_TERM 95 +#define _SC_2_UPE 97 +#define _SC_XOPEN_XPG2 98 +#define _SC_XOPEN_XPG3 99 +#define _SC_XOPEN_XPG4 100 +#define _SC_NZERO 109 +#define _SC_XBS5_ILP32_OFF32 125 +#define _SC_XBS5_ILP32_OFFBIG 126 +#define _SC_XBS5_LP64_OFF64 127 +#define _SC_XBS5_LPBIG_OFFBIG 128 +#define _SC_XOPEN_LEGACY 129 +#define _SC_XOPEN_REALTIME 130 +#define _SC_XOPEN_REALTIME_THREADS 131 +#define _SC_ADVISORY_INFO 132 +#define _SC_BARRIERS 133 +#define _SC_CLOCK_SELECTION 137 +#define _SC_CPUTIME 138 +#define _SC_THREAD_CPUTIME 139 +#define _SC_MONOTONIC_CLOCK 149 +#define _SC_READER_WRITER_LOCKS 153 +#define _SC_SPIN_LOCKS 154 +#define _SC_REGEXP 155 +#define _SC_SHELL 157 +#define _SC_SPAWN 159 +#define _SC_SPORADIC_SERVER 160 +#define _SC_THREAD_SPORADIC_SERVER 161 +#define _SC_TIMEOUTS 164 +#define _SC_TYPED_MEMORY_OBJECTS 165 +#define _SC_2_PBS 168 +#define _SC_2_PBS_ACCOUNTING 169 +#define _SC_2_PBS_LOCATE 170 +#define _SC_2_PBS_MESSAGE 171 +#define _SC_2_PBS_TRACK 172 +#define _SC_SYMLOOP_MAX 173 +#define _SC_STREAMS 174 +#define _SC_2_PBS_CHECKPOINT 175 +#define _SC_V6_ILP32_OFF32 176 +#define _SC_V6_ILP32_OFFBIG 177 +#define _SC_V6_LP64_OFF64 178 +#define _SC_V6_LPBIG_OFFBIG 179 +#define _SC_HOST_NAME_MAX 180 +#define _SC_TRACE 181 +#define _SC_TRACE_EVENT_FILTER 182 +#define _SC_TRACE_INHERIT 183 +#define _SC_TRACE_LOG 184 + +#define _SC_IPV6 235 +#define _SC_RAW_SOCKETS 236 +#define _SC_V7_ILP32_OFF32 237 +#define _SC_V7_ILP32_OFFBIG 238 +#define _SC_V7_LP64_OFF64 239 +#define _SC_V7_LPBIG_OFFBIG 240 +#define _SC_SS_REPL_MAX 241 +#define _SC_TRACE_EVENT_NAME_MAX 242 +#define _SC_TRACE_NAME_MAX 243 +#define _SC_TRACE_SYS_MAX 244 +#define _SC_TRACE_USER_EVENT_MAX 245 +#define _SC_XOPEN_STREAMS 246 +#define _SC_THREAD_ROBUST_PRIO_INHERIT 247 +#define _SC_THREAD_ROBUST_PRIO_PROTECT 248 + +#endif diff --git a/ulib/axlibc/src/errno.rs b/ulib/axlibc/src/errno.rs new file mode 100644 index 000000000..a80479326 --- /dev/null +++ b/ulib/axlibc/src/errno.rs @@ -0,0 +1,39 @@ +use axerrno::LinuxError; +use core::ffi::{c_char, c_int}; + +/// The global errno variable. +#[cfg_attr(feature = "tls", thread_local)] +#[no_mangle] +#[allow(non_upper_case_globals)] +pub static mut errno: c_int = 0; + +pub fn set_errno(code: i32) { + unsafe { + errno = code; + } +} + +/// Returns a pointer to the global errno variable. +#[no_mangle] +pub unsafe extern "C" fn __errno_location() -> *mut c_int { + &mut errno +} + +/// Returns a pointer to the string representation of the given error code. +#[no_mangle] +pub unsafe extern "C" fn strerror(e: c_int) -> *mut c_char { + #[allow(non_upper_case_globals)] + static mut strerror_buf: [u8; 256] = [0; 256]; // TODO: thread safe + + let err_str = if e == 0 { + "Success" + } else { + LinuxError::try_from(e) + .map(|e| e.as_str()) + .unwrap_or("Unknown error") + }; + unsafe { + strerror_buf[..err_str.len()].copy_from_slice(err_str.as_bytes()); + strerror_buf.as_mut_ptr() as *mut c_char + } +} diff --git a/ulib/axlibc/src/fd_ops.rs b/ulib/axlibc/src/fd_ops.rs new file mode 100644 index 000000000..1b1a9d000 --- /dev/null +++ b/ulib/axlibc/src/fd_ops.rs @@ -0,0 +1,54 @@ +use crate::{ctypes, utils::e}; +use arceos_posix_api::{sys_close, sys_dup, sys_dup2, sys_fcntl}; +use axerrno::LinuxError; +use core::ffi::c_int; + +/// Close a file by `fd`. +#[no_mangle] +pub unsafe extern "C" fn close(fd: c_int) -> c_int { + e(sys_close(fd)) +} + +/// Duplicate a file descriptor. +#[no_mangle] +pub unsafe extern "C" fn dup(old_fd: c_int) -> c_int { + e(sys_dup(old_fd)) +} + +/// Duplicate a file descriptor, use file descriptor specified in `new_fd`. +#[no_mangle] +pub unsafe extern "C" fn dup2(old_fd: c_int, new_fd: c_int) -> c_int { + e(sys_dup2(old_fd, new_fd)) +} + +/// Duplicate a file descriptor, the caller can force the close-on-exec flag to +/// be set for the new file descriptor by specifying `O_CLOEXEC` in flags. +/// +/// If oldfd equals newfd, then `dup3()` fails with the error `EINVAL`. +#[no_mangle] +pub unsafe extern "C" fn dup3(old_fd: c_int, new_fd: c_int, flags: c_int) -> c_int { + if old_fd == new_fd { + return e((LinuxError::EINVAL as c_int).wrapping_neg()); + } + let r = e(sys_dup2(old_fd, new_fd)); + if r < 0 { + r + } else { + if flags as u32 & ctypes::O_CLOEXEC != 0 { + e(sys_fcntl( + new_fd, + ctypes::F_SETFD as c_int, + ctypes::FD_CLOEXEC as usize, + )); + } + new_fd + } +} + +/// Manipulate file descriptor. +/// +/// TODO: `SET/GET` command is ignored +#[no_mangle] +pub unsafe extern "C" fn ax_fcntl(fd: c_int, cmd: c_int, arg: usize) -> c_int { + e(sys_fcntl(fd, cmd, arg)) +} diff --git a/ulib/axlibc/src/fs.rs b/ulib/axlibc/src/fs.rs new file mode 100644 index 000000000..bd93f8876 --- /dev/null +++ b/ulib/axlibc/src/fs.rs @@ -0,0 +1,67 @@ +use core::ffi::{c_char, c_int}; + +use arceos_posix_api::{ + sys_fstat, sys_getcwd, sys_lseek, sys_lstat, sys_open, sys_rename, sys_stat, +}; + +use crate::{ctypes, utils::e}; + +/// Open a file by `filename` and insert it into the file descriptor table. +/// +/// Return its index in the file table (`fd`). Return `EMFILE` if it already +/// has the maximum number of files open. +#[no_mangle] +pub unsafe extern "C" fn ax_open( + filename: *const c_char, + flags: c_int, + mode: ctypes::mode_t, +) -> c_int { + e(sys_open(filename, flags, mode)) +} + +/// Set the position of the file indicated by `fd`. +/// +/// Return its position after seek. +#[no_mangle] +pub unsafe extern "C" fn lseek(fd: c_int, offset: ctypes::off_t, whence: c_int) -> ctypes::off_t { + e(sys_lseek(fd, offset, whence) as _) as _ +} + +/// Get the file metadata by `path` and write into `buf`. +/// +/// Return 0 if success. +#[no_mangle] +pub unsafe extern "C" fn stat(path: *const c_char, buf: *mut ctypes::stat) -> c_int { + e(sys_stat(path, buf)) +} + +/// Get file metadata by `fd` and write into `buf`. +/// +/// Return 0 if success. +#[no_mangle] +pub unsafe extern "C" fn fstat(fd: c_int, buf: *mut ctypes::stat) -> c_int { + e(sys_fstat(fd, buf)) +} + +/// Get the metadata of the symbolic link and write into `buf`. +/// +/// Return 0 if success. +#[no_mangle] +pub unsafe extern "C" fn lstat(path: *const c_char, buf: *mut ctypes::stat) -> c_int { + e(sys_lstat(path, buf) as _) +} + +/// Get the path of the current directory. +#[no_mangle] +pub unsafe extern "C" fn getcwd(buf: *mut c_char, size: usize) -> *mut c_char { + sys_getcwd(buf, size) +} + +/// Rename `old` to `new` +/// If new exists, it is first removed. +/// +/// Return 0 if the operation succeeds, otherwise return -1. +#[no_mangle] +pub unsafe extern "C" fn rename(old: *const c_char, new: *const c_char) -> c_int { + e(sys_rename(old, new)) +} diff --git a/ulib/axlibc/src/io.rs b/ulib/axlibc/src/io.rs new file mode 100644 index 000000000..ea6187b71 --- /dev/null +++ b/ulib/axlibc/src/io.rs @@ -0,0 +1,32 @@ +use core::ffi::{c_int, c_void}; + +use arceos_posix_api::{sys_read, sys_write, sys_writev}; + +use crate::{ctypes, utils::e}; + +/// Read data from the file indicated by `fd`. +/// +/// Return the read size if success. +#[no_mangle] +pub unsafe extern "C" fn read(fd: c_int, buf: *mut c_void, count: usize) -> ctypes::ssize_t { + e(sys_read(fd, buf, count) as _) as _ +} + +/// Write data to the file indicated by `fd`. +/// +/// Return the written size if success. +#[no_mangle] +#[cfg(not(test))] +pub unsafe extern "C" fn write(fd: c_int, buf: *const c_void, count: usize) -> ctypes::ssize_t { + e(sys_write(fd, buf, count) as _) as _ +} + +/// Write a vector. +#[no_mangle] +pub unsafe extern "C" fn writev( + fd: c_int, + iov: *const ctypes::iovec, + iocnt: c_int, +) -> ctypes::ssize_t { + e(sys_writev(fd, iov, iocnt) as _) as _ +} diff --git a/ulib/axlibc/src/io_mpx.rs b/ulib/axlibc/src/io_mpx.rs new file mode 100644 index 000000000..2bcffc7e0 --- /dev/null +++ b/ulib/axlibc/src/io_mpx.rs @@ -0,0 +1,54 @@ +use crate::{ctypes, utils::e}; + +use core::ffi::c_int; + +#[cfg(feature = "select")] +use arceos_posix_api::sys_select; +#[cfg(feature = "epoll")] +use arceos_posix_api::{sys_epoll_create, sys_epoll_ctl, sys_epoll_wait}; + +/// Creates a new epoll instance. +/// +/// It returns a file descriptor referring to the new epoll instance. +#[cfg(feature = "epoll")] +#[no_mangle] +pub unsafe extern "C" fn epoll_create(size: c_int) -> c_int { + e(sys_epoll_create(size)) +} + +/// Control interface for an epoll file descriptor +#[cfg(feature = "epoll")] +#[no_mangle] +pub unsafe extern "C" fn epoll_ctl( + epfd: c_int, + op: c_int, + fd: c_int, + event: *mut ctypes::epoll_event, +) -> c_int { + e(sys_epoll_ctl(epfd, op, fd, event)) +} + +/// Waits for events on the epoll instance referred to by the file descriptor epfd. +#[cfg(feature = "epoll")] +#[no_mangle] +pub unsafe extern "C" fn epoll_wait( + epfd: c_int, + events: *mut ctypes::epoll_event, + maxevents: c_int, + timeout: c_int, +) -> c_int { + e(sys_epoll_wait(epfd, events, maxevents, timeout)) +} + +/// Monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation +#[cfg(feature = "select")] +#[no_mangle] +pub unsafe extern "C" fn select( + nfds: c_int, + readfds: *mut ctypes::fd_set, + writefds: *mut ctypes::fd_set, + exceptfds: *mut ctypes::fd_set, + timeout: *mut ctypes::timeval, +) -> c_int { + e(sys_select(nfds, readfds, writefds, exceptfds, timeout)) +} diff --git a/ulib/axlibc/src/lib.rs b/ulib/axlibc/src/lib.rs new file mode 100644 index 000000000..7bb8cc80c --- /dev/null +++ b/ulib/axlibc/src/lib.rs @@ -0,0 +1,125 @@ +//! [ArceOS] user program library for C apps. +//! +//! ## Cargo Features +//! +//! - CPU +//! - `smp`: Enable SMP (symmetric multiprocessing) support. +//! - `fp_simd`: Enable floating point and SIMD support. +//! - Interrupts: +//! - `irq`: Enable interrupt handling support. +//! - Memory +//! - `alloc`: Enable dynamic memory allocation. +//! - `tls`: Enable thread-local storage. +//! - Task management +//! - `multitask`: Enable multi-threading support. +//! - Upperlayer stacks +//! - `fs`: Enable file system support. +//! - `net`: Enable networking support. +//! - Lib C functions +//! - `fd`: Enable file descriptor table. +//! - `pipe`: Enable pipe support. +//! - `select`: Enable synchronous I/O multiplexing ([select]) support. +//! - `epoll`: Enable event polling ([epoll]) support. +//! +//! [ArceOS]: https://github.com/rcore-os/arceos +//! [select]: https://man7.org/linux/man-pages/man2/select.2.html +//! [epoll]: https://man7.org/linux/man-pages/man7/epoll.7.html + +#![cfg_attr(all(not(test), not(doc)), no_std)] +#![feature(doc_cfg)] +#![feature(doc_auto_cfg)] +#![feature(naked_functions)] +#![feature(thread_local)] +#![allow(clippy::missing_safety_doc)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[path = "."] +mod ctypes { + #[rustfmt::skip] + #[path = "libctypes_gen.rs"] + #[allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals, clippy::upper_case_acronyms)] + mod libctypes; + + pub use arceos_posix_api::ctypes::*; + pub use libctypes::*; +} + +#[macro_use] +mod utils; + +#[cfg(feature = "fd")] +mod fd_ops; +#[cfg(feature = "fs")] +mod fs; +#[cfg(any(feature = "select", feature = "epoll"))] +mod io_mpx; +#[cfg(feature = "alloc")] +mod malloc; +#[cfg(feature = "net")] +mod net; +#[cfg(feature = "pipe")] +mod pipe; +#[cfg(feature = "multitask")] +mod pthread; +#[cfg(feature = "alloc")] +mod strftime; +#[cfg(feature = "fp_simd")] +mod strtod; + +mod errno; +mod io; +mod mktime; +mod rand; +mod resource; +mod setjmp; +mod sys; +mod time; +mod unistd; + +#[cfg(not(test))] +pub use self::io::write; +pub use self::io::{read, writev}; + +pub use self::errno::strerror; +pub use self::mktime::mktime; +pub use self::rand::{rand, random, srand}; +pub use self::resource::{getrlimit, setrlimit}; +pub use self::setjmp::{longjmp, setjmp}; +pub use self::sys::sysconf; +pub use self::time::{clock_gettime, nanosleep}; +pub use self::unistd::{abort, exit, getpid}; + +#[cfg(feature = "alloc")] +pub use self::malloc::{free, malloc}; +#[cfg(feature = "alloc")] +pub use self::strftime::strftime; + +#[cfg(feature = "fd")] +pub use self::fd_ops::{ax_fcntl, close, dup, dup2, dup3}; + +#[cfg(feature = "fs")] +pub use self::fs::{ax_open, fstat, getcwd, lseek, lstat, rename, stat}; + +#[cfg(feature = "net")] +pub use self::net::{ + accept, bind, connect, freeaddrinfo, getaddrinfo, getpeername, getsockname, listen, recv, + recvfrom, send, sendto, shutdown, socket, +}; + +#[cfg(feature = "multitask")] +pub use self::pthread::{pthread_create, pthread_exit, pthread_join, pthread_self}; +#[cfg(feature = "multitask")] +pub use self::pthread::{pthread_mutex_init, pthread_mutex_lock, pthread_mutex_unlock}; + +#[cfg(feature = "pipe")] +pub use self::pipe::pipe; + +#[cfg(feature = "select")] +pub use self::io_mpx::select; +#[cfg(feature = "epoll")] +pub use self::io_mpx::{epoll_create, epoll_ctl, epoll_wait}; + +#[cfg(feature = "fp_simd")] +pub use self::strtod::{strtod, strtof}; diff --git a/ulib/axlibc/src/malloc.rs b/ulib/axlibc/src/malloc.rs new file mode 100644 index 000000000..f635b7ff0 --- /dev/null +++ b/ulib/axlibc/src/malloc.rs @@ -0,0 +1,56 @@ +//! Provides the corresponding malloc(size_t) and free(size_t) when using the C user program. +//! +//! The normal malloc(size_t) and free(size_t) are provided by the library malloc.h, and +//! sys_brk is used internally to apply for memory from the kernel. But in a unikernel like +//! `ArceOS`, we noticed that the heap of the Rust user program is shared with the kernel. In +//! order to maintain consistency, C user programs also choose to share the kernel heap, +//! skipping the sys_brk step. + +use alloc::alloc::{alloc, dealloc}; +use core::alloc::Layout; +use core::ffi::c_void; + +use crate::ctypes; + +struct MemoryControlBlock { + size: usize, +} + +const CTRL_BLK_SIZE: usize = core::mem::size_of::(); + +/// Allocate memory and return the memory address. +/// +/// Returns 0 on failure (the current implementation does not trigger an exception) +#[no_mangle] +pub unsafe extern "C" fn malloc(size: ctypes::size_t) -> *mut c_void { + // Allocate `(actual length) + 8`. The lowest 8 Bytes are stored in the actual allocated space size. + // This is because free(uintptr_t) has only one parameter representing the address, + // So we need to save in advance to know the size of the memory space that needs to be released + let layout = Layout::from_size_align(size + CTRL_BLK_SIZE, 8).unwrap(); + unsafe { + let ptr = alloc(layout).cast::(); + assert!(!ptr.is_null(), "malloc failed"); + ptr.write(MemoryControlBlock { size }); + ptr.add(1).cast() + } +} + +/// Deallocate memory. +/// +/// (WARNING) If the address to be released does not match the allocated address, an error should +/// occur, but it will NOT be checked out. This is due to the global allocator `Buddy_system` +/// (currently used) does not check the validity of address to be released. +#[no_mangle] +pub unsafe extern "C" fn free(ptr: *mut c_void) { + if ptr.is_null() { + return; + } + let ptr = ptr.cast::(); + assert!(ptr as usize > CTRL_BLK_SIZE, "free a null pointer"); + unsafe { + let ptr = ptr.sub(1); + let size = ptr.read().size; + let layout = Layout::from_size_align(size + CTRL_BLK_SIZE, 8).unwrap(); + dealloc(ptr.cast(), layout) + } +} diff --git a/ulib/axlibc/src/mktime.rs b/ulib/axlibc/src/mktime.rs new file mode 100644 index 000000000..1caf3325b --- /dev/null +++ b/ulib/axlibc/src/mktime.rs @@ -0,0 +1,58 @@ +use core::ffi::c_int; + +use crate::ctypes; + +const MONTH_DAYS: [[c_int; 12]; 2] = [ + // Non-leap years: + [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], + // Leap years: + [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], +]; + +#[inline(always)] +fn leap_year(year: c_int) -> bool { + year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) +} + +/// Convert broken-down time into time since the Epoch. +#[no_mangle] +pub unsafe extern "C" fn mktime(t: *mut ctypes::tm) -> ctypes::time_t { + let mut year = (*t).tm_year + 1900; + let mut month = (*t).tm_mon; + let mut day = (*t).tm_mday as i64 - 1; + + let leap = if leap_year(year) { 1 } else { 0 }; + + if year < 1970 { + day = MONTH_DAYS[if leap_year(year) { 1 } else { 0 }][(*t).tm_mon as usize] as i64 - day; + + while year < 1969 { + year += 1; + day += if leap_year(year) { 366 } else { 365 }; + } + + while month < 11 { + month += 1; + day += MONTH_DAYS[leap][month as usize] as i64; + } + + (-(day * (60 * 60 * 24) + - (((*t).tm_hour as i64) * (60 * 60) + ((*t).tm_min as i64) * 60 + (*t).tm_sec as i64))) + as ctypes::time_t + } else { + while year > 1970 { + year -= 1; + day += if leap_year(year) { 366 } else { 365 }; + } + + while month > 0 { + month -= 1; + day += MONTH_DAYS[leap][month as usize] as i64; + } + + (day * (60 * 60 * 24) + + ((*t).tm_hour as i64) * (60 * 60) + + ((*t).tm_min as i64) * 60 + + (*t).tm_sec as i64) as ctypes::time_t + } +} diff --git a/ulib/axlibc/src/net.rs b/ulib/axlibc/src/net.rs new file mode 100644 index 000000000..e09218157 --- /dev/null +++ b/ulib/axlibc/src/net.rs @@ -0,0 +1,180 @@ +use arceos_posix_api::{ + sys_accept, sys_bind, sys_connect, sys_freeaddrinfo, sys_getaddrinfo, sys_getpeername, + sys_getsockname, sys_listen, sys_recv, sys_recvfrom, sys_send, sys_sendto, sys_shutdown, + sys_socket, +}; +use core::ffi::{c_char, c_int, c_void}; + +use crate::{ctypes, utils::e}; + +/// Create an socket for communication. +/// +/// Return the socket file descriptor. +#[no_mangle] +pub unsafe extern "C" fn socket(domain: c_int, socktype: c_int, protocol: c_int) -> c_int { + e(sys_socket(domain, socktype, protocol)) +} + +/// Bind a address to a socket. +/// +/// Return 0 if success. +#[no_mangle] +pub unsafe extern "C" fn bind( + socket_fd: c_int, + socket_addr: *const ctypes::sockaddr, + addrlen: ctypes::socklen_t, +) -> c_int { + e(sys_bind(socket_fd, socket_addr, addrlen)) +} + +/// Connects the socket to the address specified. +/// +/// Return 0 if success. +#[no_mangle] +pub unsafe extern "C" fn connect( + socket_fd: c_int, + socket_addr: *const ctypes::sockaddr, + addrlen: ctypes::socklen_t, +) -> c_int { + e(sys_connect(socket_fd, socket_addr, addrlen)) +} + +/// Send a message on a socket to the address specified. +/// +/// Return the number of bytes sent if success. +#[no_mangle] +pub unsafe extern "C" fn sendto( + socket_fd: c_int, + buf_ptr: *const c_void, + len: ctypes::size_t, + flag: c_int, // currently not used + socket_addr: *const ctypes::sockaddr, + addrlen: ctypes::socklen_t, +) -> ctypes::ssize_t { + if socket_addr.is_null() && addrlen == 0 { + return e(sys_send(socket_fd, buf_ptr, len, flag) as _) as _; + } + e(sys_sendto(socket_fd, buf_ptr, len, flag, socket_addr, addrlen) as _) as _ +} + +/// Send a message on a socket to the address connected. +/// +/// Return the number of bytes sent if success. +#[no_mangle] +pub unsafe extern "C" fn send( + socket_fd: c_int, + buf_ptr: *const c_void, + len: ctypes::size_t, + flag: c_int, // currently not used +) -> ctypes::ssize_t { + e(sys_send(socket_fd, buf_ptr, len, flag) as _) as _ +} + +/// Receive a message on a socket and get its source address. +/// +/// Return the number of bytes received if success. +#[no_mangle] +pub unsafe extern "C" fn recvfrom( + socket_fd: c_int, + buf_ptr: *mut c_void, + len: ctypes::size_t, + flag: c_int, // currently not used + socket_addr: *mut ctypes::sockaddr, + addrlen: *mut ctypes::socklen_t, +) -> ctypes::ssize_t { + if socket_addr.is_null() { + return e(sys_recv(socket_fd, buf_ptr, len, flag) as _) as _; + } + e(sys_recvfrom(socket_fd, buf_ptr, len, flag, socket_addr, addrlen) as _) as _ +} + +/// Receive a message on a socket. +/// +/// Return the number of bytes received if success. +#[no_mangle] +pub unsafe extern "C" fn recv( + socket_fd: c_int, + buf_ptr: *mut c_void, + len: ctypes::size_t, + flag: c_int, // currently not used +) -> ctypes::ssize_t { + e(sys_recv(socket_fd, buf_ptr, len, flag) as _) as _ +} + +/// Listen for connections on a socket +/// +/// Return 0 if success. +#[no_mangle] +pub unsafe extern "C" fn listen( + socket_fd: c_int, + backlog: c_int, // currently not used +) -> c_int { + e(sys_listen(socket_fd, backlog)) +} + +/// Accept for connections on a socket +/// +/// Return file descriptor for the accepted socket if success. +#[no_mangle] +pub unsafe extern "C" fn accept( + socket_fd: c_int, + socket_addr: *mut ctypes::sockaddr, + socket_len: *mut ctypes::socklen_t, +) -> c_int { + e(sys_accept(socket_fd, socket_addr, socket_len)) +} + +/// Shut down a full-duplex connection. +/// +/// Return 0 if success. +#[no_mangle] +pub unsafe extern "C" fn shutdown( + socket_fd: c_int, + flag: c_int, // currently not used +) -> c_int { + e(sys_shutdown(socket_fd, flag)) +} + +/// Query addresses for a domain name. +/// +/// Return address number if success. +#[no_mangle] +pub unsafe extern "C" fn getaddrinfo( + nodename: *const c_char, + servname: *const c_char, + hints: *const ctypes::addrinfo, + res: *mut *mut ctypes::addrinfo, +) -> c_int { + let ret = e(sys_getaddrinfo(nodename, servname, hints, res)); + match ret { + r if r < 0 => ctypes::EAI_FAIL, + 0 => ctypes::EAI_NONAME, + _ => 0, + } +} + +/// Free queried `addrinfo` struct +#[no_mangle] +pub unsafe extern "C" fn freeaddrinfo(res: *mut ctypes::addrinfo) { + sys_freeaddrinfo(res); +} + +/// Get current address to which the socket sockfd is bound. +#[no_mangle] +pub unsafe extern "C" fn getsockname( + sock_fd: c_int, + addr: *mut ctypes::sockaddr, + addrlen: *mut ctypes::socklen_t, +) -> c_int { + e(sys_getsockname(sock_fd, addr, addrlen)) +} + +/// Get peer address to which the socket sockfd is connected. +#[no_mangle] +pub unsafe extern "C" fn getpeername( + sock_fd: c_int, + addr: *mut ctypes::sockaddr, + addrlen: *mut ctypes::socklen_t, +) -> c_int { + e(sys_getpeername(sock_fd, addr, addrlen)) +} diff --git a/ulib/axlibc/src/pipe.rs b/ulib/axlibc/src/pipe.rs new file mode 100644 index 000000000..2bed253c0 --- /dev/null +++ b/ulib/axlibc/src/pipe.rs @@ -0,0 +1,14 @@ +use core::ffi::c_int; + +use arceos_posix_api::sys_pipe; + +use crate::utils::e; + +/// Create a pipe +/// +/// Return 0 if succeed +#[no_mangle] +pub unsafe extern "C" fn pipe(fd: *mut c_int) -> c_int { + let fds = unsafe { core::slice::from_raw_parts_mut(fd, 2) }; + e(sys_pipe(fds)) +} diff --git a/ulib/axlibc/src/pthread.rs b/ulib/axlibc/src/pthread.rs new file mode 100644 index 000000000..b170b8b7a --- /dev/null +++ b/ulib/axlibc/src/pthread.rs @@ -0,0 +1,59 @@ +use crate::{ctypes, utils::e}; +use arceos_posix_api as api; +use core::ffi::{c_int, c_void}; + +/// Returns the `pthread` struct of current thread. +#[no_mangle] +pub unsafe extern "C" fn pthread_self() -> ctypes::pthread_t { + api::sys_pthread_self() +} + +/// Create a new thread with the given entry point and argument. +/// +/// If successful, it stores the pointer to the newly created `struct __pthread` +/// in `res` and returns 0. +#[no_mangle] +pub unsafe extern "C" fn pthread_create( + res: *mut ctypes::pthread_t, + attr: *const ctypes::pthread_attr_t, + start_routine: extern "C" fn(arg: *mut c_void) -> *mut c_void, + arg: *mut c_void, +) -> c_int { + e(api::sys_pthread_create(res, attr, start_routine, arg)) +} + +/// Exits the current thread. The value `retval` will be returned to the joiner. +#[no_mangle] +pub unsafe extern "C" fn pthread_exit(retval: *mut c_void) -> ! { + api::sys_pthread_exit(retval) +} + +/// Waits for the given thread to exit, and stores the return value in `retval`. +#[no_mangle] +pub unsafe extern "C" fn pthread_join( + thread: ctypes::pthread_t, + retval: *mut *mut c_void, +) -> c_int { + e(api::sys_pthread_join(thread, retval)) +} + +/// Initialize a mutex. +#[no_mangle] +pub unsafe extern "C" fn pthread_mutex_init( + mutex: *mut ctypes::pthread_mutex_t, + attr: *const ctypes::pthread_mutexattr_t, +) -> c_int { + e(api::sys_pthread_mutex_init(mutex, attr)) +} + +/// Lock the given mutex. +#[no_mangle] +pub unsafe extern "C" fn pthread_mutex_lock(mutex: *mut ctypes::pthread_mutex_t) -> c_int { + e(api::sys_pthread_mutex_lock(mutex)) +} + +/// Unlock the given mutex. +#[no_mangle] +pub unsafe extern "C" fn pthread_mutex_unlock(mutex: *mut ctypes::pthread_mutex_t) -> c_int { + e(api::sys_pthread_mutex_unlock(mutex)) +} diff --git a/ulib/axlibc/src/rand.rs b/ulib/axlibc/src/rand.rs new file mode 100644 index 000000000..682f4231c --- /dev/null +++ b/ulib/axlibc/src/rand.rs @@ -0,0 +1,30 @@ +//! Random number generator. + +use core::{ + ffi::{c_int, c_long, c_uint}, + sync::atomic::{AtomicU64, Ordering::SeqCst}, +}; + +static SEED: AtomicU64 = AtomicU64::new(0xa2ce_a2ce); + +/// Sets the seed for the random number generator. +#[no_mangle] +pub unsafe extern "C" fn srand(seed: c_uint) { + SEED.store(seed.wrapping_sub(1) as u64, SeqCst); +} + +/// Returns a 32-bit unsigned pseudo random interger. +#[no_mangle] +pub unsafe extern "C" fn rand() -> c_int { + let new_seed = SEED.load(SeqCst).wrapping_mul(6364136223846793005) + 1; + SEED.store(new_seed, SeqCst); + (new_seed >> 33) as c_int +} + +/// Returns a 64-bit unsigned pseudo random number. +#[no_mangle] +pub unsafe extern "C" fn random() -> c_long { + let new_seed = SEED.load(SeqCst).wrapping_mul(6364136223846793005) + 1; + SEED.store(new_seed, SeqCst); + new_seed as c_long +} diff --git a/ulib/axlibc/src/resource.rs b/ulib/axlibc/src/resource.rs new file mode 100644 index 000000000..20e013e50 --- /dev/null +++ b/ulib/axlibc/src/resource.rs @@ -0,0 +1,17 @@ +use core::ffi::c_int; + +use arceos_posix_api::{sys_getrlimit, sys_setrlimit}; + +use crate::utils::e; + +/// Get resource limitations +#[no_mangle] +pub unsafe extern "C" fn getrlimit(resource: c_int, rlimits: *mut crate::ctypes::rlimit) -> c_int { + e(sys_getrlimit(resource, rlimits)) +} + +/// Set resource limitations +#[no_mangle] +pub unsafe extern "C" fn setrlimit(resource: c_int, rlimits: *mut crate::ctypes::rlimit) -> c_int { + e(sys_setrlimit(resource, rlimits)) +} diff --git a/ulib/axlibc/src/setjmp.rs b/ulib/axlibc/src/setjmp.rs new file mode 100644 index 000000000..aed4441ff --- /dev/null +++ b/ulib/axlibc/src/setjmp.rs @@ -0,0 +1,238 @@ +use core::ffi::c_int; + +use crate::ctypes; + +/// `setjmp` implementation +#[naked] +#[no_mangle] +pub unsafe extern "C" fn setjmp(_buf: *mut ctypes::__jmp_buf_tag) { + #[cfg(all(target_arch = "aarch64", feature = "fp_simd"))] + core::arch::asm!( + " + stp x19, x20, [x0,#0] + stp x21, x22, [x0,#16] + stp x23, x24, [x0,#32] + stp x25, x26, [x0,#48] + stp x27, x28, [x0,#64] + stp x29, x30, [x0,#80] + mov x2, sp + str x2, [x0,#104] + stp d8, d9, [x0,#112] + stp d10, d11, [x0,#128] + stp d12, d13, [x0,#144] + stp d14, d15, [x0,#160] + mov x0, #0 + ret", + options(noreturn), + ); + #[cfg(all(target_arch = "aarch64", not(feature = "fp_simd")))] + core::arch::asm!( + " + stp x19, x20, [x0,#0] + stp x21, x22, [x0,#16] + stp x23, x24, [x0,#32] + stp x25, x26, [x0,#48] + stp x27, x28, [x0,#64] + stp x29, x30, [x0,#80] + mov x2, sp + str x2, [x0,#104] + mov x0, #0 + ret", + options(noreturn), + ); + #[cfg(target_arch = "x86_64")] + core::arch::asm!( + "mov [rdi], rbx + mov [rdi + 8], rbp + mov [rdi + 16], r12 + mov [rdi + 24], r13 + mov [rdi + 32], r14 + mov [rdi + 40], r15 + lea rdx, [rsp + 8] + mov [rdi + 48], rdx + mov rdx, [rsp] + mov [rdi + 56], rdx + xor rax, rax + ret", + options(noreturn), + ); + #[cfg(all(target_arch = "riscv64", feature = "fp_simd"))] + core::arch::asm!( + "sd s0, 0(a0) + sd s1, 8(a0) + sd s2, 16(a0) + sd s3, 24(a0) + sd s4, 32(a0) + sd s5, 40(a0) + sd s6, 48(a0) + sd s7, 56(a0) + sd s8, 64(a0) + sd s9, 72(a0) + sd s10, 80(a0) + sd s11, 88(a0) + sd sp, 96(a0) + sd ra, 104(a0) + + fsd fs0, 112(a0) + fsd fs1, 120(a0) + fsd fs2, 128(a0) + fsd fs3, 136(a0) + fsd fs4, 144(a0) + fsd fs5, 152(a0) + fsd fs6, 160(a0) + fsd fs7, 168(a0) + fsd fs8, 176(a0) + fsd fs9, 184(a0) + fsd fs10, 192(a0) + fsd fs11, 200(a0) + + li a0, 0 + ret", + options(noreturn), + ); + #[cfg(all(target_arch = "riscv64", not(feature = "fp_simd")))] + core::arch::asm!( + "sd s0, 0(a0) + sd s1, 8(a0) + sd s2, 16(a0) + sd s3, 24(a0) + sd s4, 32(a0) + sd s5, 40(a0) + sd s6, 48(a0) + sd s7, 56(a0) + sd s8, 64(a0) + sd s9, 72(a0) + sd s10, 80(a0) + sd s11, 88(a0) + sd sp, 96(a0) + sd ra, 104(a0) + + li a0, 0 + ret", + options(noreturn), + ); + #[cfg(not(any( + target_arch = "aarch64", + target_arch = "x86_64", + target_arch = "riscv64" + )))] + core::arch::asm!("ret", options(noreturn)) +} + +/// `longjmp` implementation +#[naked] +#[no_mangle] +pub unsafe extern "C" fn longjmp(_buf: *mut ctypes::__jmp_buf_tag, _val: c_int) -> ! { + #[cfg(all(target_arch = "aarch64", feature = "fp_simd"))] + core::arch::asm!( + "ldp x19, x20, [x0,#0] + ldp x21, x22, [x0,#16] + ldp x23, x24, [x0,#32] + ldp x25, x26, [x0,#48] + ldp x27, x28, [x0,#64] + ldp x29, x30, [x0,#80] + ldr x2, [x0,#104] + mov sp, x2 + ldp d8 , d9, [x0,#112] + ldp d10, d11, [x0,#128] + ldp d12, d13, [x0,#144] + ldp d14, d15, [x0,#160] + + cmp w1, 0 + csinc w0, w1, wzr, ne + br x30", + options(noreturn), + ); + #[cfg(all(target_arch = "aarch64", not(feature = "fp_simd")))] + core::arch::asm!( + "ldp x19, x20, [x0,#0] + ldp x21, x22, [x0,#16] + ldp x23, x24, [x0,#32] + ldp x25, x26, [x0,#48] + ldp x27, x28, [x0,#64] + ldp x29, x30, [x0,#80] + ldr x2, [x0,#104] + mov sp, x2 + + cmp w1, 0 + csinc w0, w1, wzr, ne + br x30", + options(noreturn), + ); + #[cfg(target_arch = "x86_64")] + core::arch::asm!( + "mov rax,rsi + test rax,rax + jnz 1f + inc rax + 1: + mov rbx, [rdi] + mov rbp, [rdi + 8] + mov r12, [rdi + 16] + mov r13, [rdi + 24] + mov r14, [rdi + 32] + mov r15, [rdi + 40] + mov rdx, [rdi + 48] + mov rsp, rdx + mov rdx, [rdi + 56] + jmp rdx", + options(noreturn), + ); + #[cfg(all(target_arch = "riscv64", feature = "fp_simd"))] + core::arch::asm!( + "ld s0, 0(a0) + ld s1, 8(a0) + ld s2, 16(a0) + ld s3, 24(a0) + ld s4, 32(a0) + ld s5, 40(a0) + ld s6, 48(a0) + ld s7, 56(a0) + ld s8, 64(a0) + ld s9, 72(a0) + ld s10, 80(a0) + ld s11, 88(a0) + ld sp, 96(a0) + ld ra, 104(a0) + + fld fs0, 112(a0) + fld fs1, 120(a0) + fld fs2, 128(a0) + fld fs3, 136(a0) + fld fs4, 144(a0) + fld fs5, 152(a0) + fld fs6, 160(a0) + fld fs7, 168(a0) + fld fs8, 176(a0) + fld fs9, 184(a0) + fld fs10, 192(a0) + fld fs11, 200(a0) + + seqz a0, a1 + add a0, a0, a1 + ret", + options(noreturn), + ); + #[cfg(all(target_arch = "riscv64", not(feature = "fp_simd")))] + core::arch::asm!( + "ld s0, 0(a0) + ld s1, 8(a0) + ld s2, 16(a0) + ld s3, 24(a0) + ld s4, 32(a0) + ld s5, 40(a0) + ld s6, 48(a0) + ld s7, 56(a0) + ld s8, 64(a0) + ld s9, 72(a0) + ld s10, 80(a0) + ld s11, 88(a0) + ld sp, 96(a0) + ld ra, 104(a0) + + seqz a0, a1 + add a0, a0, a1 + ret", + options(noreturn), + ); +} diff --git a/ulib/axlibc/src/strftime.rs b/ulib/axlibc/src/strftime.rs new file mode 100644 index 000000000..aa3c010a3 --- /dev/null +++ b/ulib/axlibc/src/strftime.rs @@ -0,0 +1,254 @@ +use alloc::string::String; +use core::{ffi::c_char, fmt}; + +use axio::Write; + +use crate::ctypes; + +pub trait WriteByte: fmt::Write { + fn write_u8(&mut self, byte: u8) -> fmt::Result; +} + +struct StringWriter(pub *mut u8, pub usize); + +impl Write for StringWriter { + fn write(&mut self, buf: &[u8]) -> axerrno::AxResult { + if self.1 > 1 { + let copy_size = buf.len().min(self.1 - 1); + unsafe { + core::ptr::copy_nonoverlapping(buf.as_ptr(), self.0, copy_size); + self.1 -= copy_size; + + self.0 = self.0.add(copy_size); + *self.0 = 0; + } + } + Ok(buf.len()) + } + fn flush(&mut self) -> axerrno::AxResult { + Ok(()) + } +} + +impl fmt::Write for StringWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + // can't fail + self.write(s.as_bytes()).unwrap(); + Ok(()) + } +} + +impl WriteByte for StringWriter { + fn write_u8(&mut self, byte: u8) -> fmt::Result { + // can't fail + self.write(&[byte]).unwrap(); + Ok(()) + } +} + +struct CountingWriter { + pub inner: T, + pub written: usize, +} + +impl CountingWriter { + pub fn new(writer: T) -> Self { + Self { + inner: writer, + written: 0, + } + } +} + +impl fmt::Write for CountingWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.written += s.len(); + self.inner.write_str(s) + } +} + +impl WriteByte for CountingWriter { + fn write_u8(&mut self, byte: u8) -> fmt::Result { + self.written += 1; + self.inner.write_u8(byte) + } +} + +impl Write for CountingWriter { + fn write(&mut self, buf: &[u8]) -> axerrno::AxResult { + let res = self.inner.write(buf); + if let Ok(written) = res { + self.written += written; + } + res + } + + fn write_all(&mut self, buf: &[u8]) -> axerrno::AxResult { + match self.inner.write_all(buf) { + Ok(()) => (), + Err(err) => return Err(err), + } + self.written += buf.len(); + Ok(()) + } + + fn flush(&mut self) -> axerrno::AxResult { + self.inner.flush() + } +} + +unsafe fn strftime_inner( + w: W, + format: *const c_char, + t: *const ctypes::tm, +) -> ctypes::size_t { + pub unsafe fn inner_strftime( + w: &mut W, + mut format: *const c_char, + t: *const ctypes::tm, + ) -> bool { + macro_rules! w { + (byte $b:expr) => {{ + if w.write_u8($b).is_err() { + return false; + } + }}; + (char $chr:expr) => {{ + if w.write_char($chr).is_err() { + return false; + } + }}; + (recurse $fmt:expr) => {{ + let mut fmt = String::with_capacity($fmt.len() + 1); + fmt.push_str($fmt); + fmt.push('\0'); + + if !inner_strftime(w, fmt.as_ptr() as *mut c_char, t) { + return false; + } + }}; + ($str:expr) => {{ + if w.write_str($str).is_err() { + return false; + } + }}; + ($fmt:expr, $($args:expr),+) => {{ + // Would use write!() if I could get the length written + if write!(w, $fmt, $($args),+).is_err() { + return false; + } + }}; + } + const WDAYS: [&str; 7] = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ]; + const MONTHS: [&str; 12] = [ + "January", + "Febuary", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ]; + + while *format != 0 { + if *format as u8 != b'%' { + w!(byte * format as u8); + format = format.offset(1); + continue; + } + + format = format.offset(1); + + if *format as u8 == b'E' || *format as u8 == b'O' { + // Ignore because these do nothing without locale + format = format.offset(1); + } + + match *format as u8 { + b'%' => w!(byte b'%'), + b'n' => w!(byte b'\n'), + b't' => w!(byte b'\t'), + b'a' => w!(&WDAYS[(*t).tm_wday as usize][..3]), + b'A' => w!(WDAYS[(*t).tm_wday as usize]), + b'b' | b'h' => w!(&MONTHS[(*t).tm_mon as usize][..3]), + b'B' => w!(MONTHS[(*t).tm_mon as usize]), + b'C' => { + let mut year = (*t).tm_year / 100; + // Round up + if (*t).tm_year % 100 != 0 { + year += 1; + } + w!("{:02}", year + 19); + } + b'd' => w!("{:02}", (*t).tm_mday), + b'D' => w!(recurse "%m/%d/%y"), + b'e' => w!("{:2}", (*t).tm_mday), + b'F' => w!(recurse "%Y-%m-%d"), + b'H' => w!("{:02}", (*t).tm_hour), + b'I' => w!("{:02}", ((*t).tm_hour + 12 - 1) % 12 + 1), + b'j' => w!("{:03}", (*t).tm_yday), + b'k' => w!("{:2}", (*t).tm_hour), + b'l' => w!("{:2}", ((*t).tm_hour + 12 - 1) % 12 + 1), + b'm' => w!("{:02}", (*t).tm_mon + 1), + b'M' => w!("{:02}", (*t).tm_min), + b'p' => w!(if (*t).tm_hour < 12 { "AM" } else { "PM" }), + b'P' => w!(if (*t).tm_hour < 12 { "am" } else { "pm" }), + b'r' => w!(recurse "%I:%M:%S %p"), + b'R' => w!(recurse "%H:%M"), + // Nothing is modified in mktime, but the C standard of course requires a mutable pointer ._. + b's' => w!("{}", super::mktime(t as *mut ctypes::tm)), + b'S' => w!("{:02}", (*t).tm_sec), + b'T' => w!(recurse "%H:%M:%S"), + b'u' => w!("{}", ((*t).tm_wday + 7 - 1) % 7 + 1), + b'U' => w!("{}", ((*t).tm_yday + 7 - (*t).tm_wday) / 7), + b'w' => w!("{}", (*t).tm_wday), + b'W' => w!("{}", ((*t).tm_yday + 7 - ((*t).tm_wday + 6) % 7) / 7), + b'y' => w!("{:02}", (*t).tm_year % 100), + b'Y' => w!("{}", (*t).tm_year + 1900), + b'z' => w!("+0000"), // TODO + b'Z' => w!("UTC"), // TODO + b'+' => w!(recurse "%a %b %d %T %Z %Y"), + _ => return false, + } + + format = format.offset(1); + } + true + } + + let mut w: CountingWriter = CountingWriter::new(w); + if !inner_strftime(&mut w, format, t) { + return 0; + } + + w.written +} + +/// Convert date and time to a string. +#[no_mangle] +pub unsafe extern "C" fn strftime( + buf: *mut c_char, + size: ctypes::size_t, + format: *const c_char, + timeptr: *const ctypes::tm, +) -> ctypes::size_t { + let ret = strftime_inner(StringWriter(buf as *mut u8, size), format, timeptr); + if ret < size { + ret + } else { + 0 + } +} diff --git a/ulib/axlibc/src/strtod.rs b/ulib/axlibc/src/strtod.rs new file mode 100644 index 000000000..d93087892 --- /dev/null +++ b/ulib/axlibc/src/strtod.rs @@ -0,0 +1,131 @@ +use core::ffi::{c_char, c_double, c_float, c_int}; + +macro_rules! strto_float_impl { + ($type:ident, $s:expr, $endptr:expr) => {{ + let mut s = $s; + let endptr = $endptr; + + // TODO: Handle named floats: NaN, Inf... + + while isspace(*s as c_int) { + s = s.offset(1); + } + + let mut result: $type = 0.0; + let mut radix = 10; + + let result_sign = match *s as u8 { + b'-' => { + s = s.offset(1); + -1.0 + } + b'+' => { + s = s.offset(1); + 1.0 + } + _ => 1.0, + }; + + if *s as u8 == b'0' && *s.offset(1) as u8 == b'x' { + s = s.offset(2); + radix = 16; + } + + while let Some(digit) = (*s as u8 as char).to_digit(radix) { + result *= radix as $type; + result += digit as $type; + s = s.offset(1); + } + + if *s as u8 == b'.' { + s = s.offset(1); + + let mut i = 1.0; + while let Some(digit) = (*s as u8 as char).to_digit(radix) { + i *= radix as $type; + result += digit as $type / i; + s = s.offset(1); + } + } + + let s_before_exponent = s; + + let exponent = match (*s as u8, radix) { + (b'e' | b'E', 10) | (b'p' | b'P', 16) => { + s = s.offset(1); + + let is_exponent_positive = match *s as u8 { + b'-' => { + s = s.offset(1); + false + } + b'+' => { + s = s.offset(1); + true + } + _ => true, + }; + + // Exponent digits are always in base 10. + if (*s as u8 as char).is_digit(10) { + let mut exponent_value = 0; + + while let Some(digit) = (*s as u8 as char).to_digit(10) { + exponent_value *= 10; + exponent_value += digit; + s = s.offset(1); + } + + let exponent_base = match radix { + 10 => 10u128, + 16 => 2u128, + _ => unreachable!(), + }; + + if is_exponent_positive { + Some(exponent_base.pow(exponent_value) as $type) + } else { + Some(1.0 / (exponent_base.pow(exponent_value) as $type)) + } + } else { + // Exponent had no valid digits after 'e'/'p' and '+'/'-', rollback + s = s_before_exponent; + None + } + } + _ => None, + }; + + // Return pointer should be *mut + if !endptr.is_null() { + *endptr = s as *mut _; + } + + if let Some(exponent) = exponent { + result_sign * result * exponent + } else { + result_sign * result + } + }}; +} + +fn isspace(c: c_int) -> bool { + c == c_int::from(b' ') + || c == c_int::from(b'\t') + || c == c_int::from(b'\n') + || c == c_int::from(b'\r') + || c == 0x0b + || c == 0x0c +} + +/// Convert a string to a double-precision number. +#[no_mangle] +pub unsafe extern "C" fn strtod(s: *const c_char, endptr: *mut *mut c_char) -> c_double { + strto_float_impl!(c_double, s, endptr) +} + +/// Convert a string to a float number. +#[no_mangle] +pub unsafe extern "C" fn strtof(s: *const c_char, endptr: *mut *mut c_char) -> c_float { + strto_float_impl!(c_float, s, endptr) +} diff --git a/ulib/axlibc/src/sys.rs b/ulib/axlibc/src/sys.rs new file mode 100644 index 000000000..253d76f1a --- /dev/null +++ b/ulib/axlibc/src/sys.rs @@ -0,0 +1,10 @@ +use arceos_posix_api::sys_sysconf; +use core::ffi::{c_int, c_long}; + +/// Return system configuration infomation +/// +/// Notice: currently only support what unikraft covers +#[no_mangle] +pub unsafe extern "C" fn sysconf(name: c_int) -> c_long { + sys_sysconf(name) +} diff --git a/ulib/axlibc/src/time.rs b/ulib/axlibc/src/time.rs new file mode 100644 index 000000000..f478cd7f3 --- /dev/null +++ b/ulib/axlibc/src/time.rs @@ -0,0 +1,21 @@ +use arceos_posix_api::{sys_clock_gettime, sys_nanosleep}; +use core::ffi::c_int; + +use crate::{ctypes, utils::e}; + +/// Get clock time since booting +#[no_mangle] +pub unsafe extern "C" fn clock_gettime(clk: ctypes::clockid_t, ts: *mut ctypes::timespec) -> c_int { + e(sys_clock_gettime(clk, ts)) +} + +/// Sleep some nanoseconds +/// +/// TODO: should be woken by signals, and set errno +#[no_mangle] +pub unsafe extern "C" fn nanosleep( + req: *const ctypes::timespec, + rem: *mut ctypes::timespec, +) -> c_int { + e(sys_nanosleep(req, rem)) +} diff --git a/ulib/axlibc/src/unistd.rs b/ulib/axlibc/src/unistd.rs new file mode 100644 index 000000000..46c81bdd7 --- /dev/null +++ b/ulib/axlibc/src/unistd.rs @@ -0,0 +1,20 @@ +use arceos_posix_api::{sys_exit, sys_getpid}; +use core::ffi::c_int; + +/// Get current thread ID. +#[no_mangle] +pub unsafe extern "C" fn getpid() -> c_int { + sys_getpid() +} + +/// Abort the current process. +#[no_mangle] +pub unsafe extern "C" fn abort() -> ! { + panic!() +} + +/// Exits the current thread. +#[no_mangle] +pub unsafe extern "C" fn exit(exit_code: c_int) -> ! { + sys_exit(exit_code) +} diff --git a/ulib/axlibc/src/utils.rs b/ulib/axlibc/src/utils.rs new file mode 100644 index 000000000..730f8c3de --- /dev/null +++ b/ulib/axlibc/src/utils.rs @@ -0,0 +1,10 @@ +use core::ffi::c_int; + +pub fn e(ret: c_int) -> c_int { + if ret < 0 { + crate::errno::set_errno(ret.abs()); + -1 + } else { + ret as _ + } +} diff --git a/ulib/axstd/Cargo.toml b/ulib/axstd/Cargo.toml new file mode 100644 index 000000000..83239c3ed --- /dev/null +++ b/ulib/axstd/Cargo.toml @@ -0,0 +1,76 @@ +[package] +name = "axstd" +version = "0.1.0" +edition = "2021" +authors = [ + "Yuekai Jia ", + "yanjuguang ", + "wudashuai ", + "yfblock <321353225@qq.com>", + "scPointer ", + "Shiping Yuan ", +] +description = "ArceOS user library with an interface similar to rust std" +license = "GPL-3.0-or-later OR Apache-2.0" +homepage = "https://github.com/rcore-os/arceos" +repository = "https://github.com/rcore-os/arceos/tree/main/ulib/axstd" +documentation = "https://rcore-os.github.io/arceos/axstd/index.html" + +[features] +default = [] + +# Multicore +smp = ["axfeat/smp", "spinlock/smp"] + +# Floating point/SIMD +fp_simd = ["axfeat/fp_simd"] + +# Interrupts +irq = ["arceos_api/irq", "axfeat/irq"] + +# Memory +alloc = ["arceos_api/alloc", "axfeat/alloc", "axio/alloc"] +alloc-tlsf = ["axfeat/alloc-tlsf"] +alloc-slab = ["axfeat/alloc-slab"] +alloc-buddy = ["axfeat/alloc-buddy"] +paging = ["axfeat/paging"] +tls = ["axfeat/tls"] + +# Multi-threading and scheduler +multitask = ["arceos_api/multitask", "axfeat/multitask"] +sched_fifo = ["axfeat/sched_fifo"] +sched_rr = ["axfeat/sched_rr"] +sched_cfs = ["axfeat/sched_cfs"] + +# File system +fs = ["arceos_api/fs", "axfeat/fs"] +myfs = ["arceos_api/myfs", "axfeat/myfs"] + +# Networking +net = ["arceos_api/net", "axfeat/net"] +dns = [] + +# Display +display = ["arceos_api/display", "axfeat/display"] + +# Device drivers +bus-mmio = ["axfeat/bus-mmio"] +bus-pci = ["axfeat/bus-pci"] +driver-ramdisk = ["axfeat/driver-ramdisk"] +driver-ixgbe = ["axfeat/driver-ixgbe"] +driver-bcm2835-sdhci = ["axfeat/driver-bcm2835-sdhci"] + +# Logging +log-level-off = ["axfeat/log-level-off"] +log-level-error = ["axfeat/log-level-error"] +log-level-warn = ["axfeat/log-level-warn"] +log-level-info = ["axfeat/log-level-info"] +log-level-debug = ["axfeat/log-level-debug"] +log-level-trace = ["axfeat/log-level-trace"] + +[dependencies] +axfeat = { path = "../../api/axfeat" } +arceos_api = { path = "../../api/arceos_api" } +axio = { path = "../../crates/axio" } +axerrno = { path = "../../crates/axerrno" } +spinlock = { path = "../../crates/spinlock" } diff --git a/ulib/axstd/src/env.rs b/ulib/axstd/src/env.rs new file mode 100644 index 000000000..13c06e2cb --- /dev/null +++ b/ulib/axstd/src/env.rs @@ -0,0 +1,19 @@ +//! Inspection and manipulation of the process’s environment. + +#[cfg(feature = "fs")] +extern crate alloc; + +#[cfg(feature = "fs")] +use {crate::io, alloc::string::String}; + +/// Returns the current working directory as a [`String`]. +#[cfg(feature = "fs")] +pub fn current_dir() -> io::Result { + arceos_api::fs::ax_current_dir() +} + +/// Changes the current working directory to the specified path. +#[cfg(feature = "fs")] +pub fn set_current_dir(path: &str) -> io::Result<()> { + arceos_api::fs::ax_set_current_dir(path) +} diff --git a/ulib/axstd/src/fs/dir.rs b/ulib/axstd/src/fs/dir.rs new file mode 100644 index 000000000..bf49cc85a --- /dev/null +++ b/ulib/axstd/src/fs/dir.rs @@ -0,0 +1,154 @@ +extern crate alloc; + +use alloc::string::String; +use core::fmt; + +use super::FileType; +use crate::io::Result; + +use arceos_api::fs as api; + +/// Iterator over the entries in a directory. +pub struct ReadDir<'a> { + path: &'a str, + inner: api::AxDirHandle, + buf_pos: usize, + buf_end: usize, + end_of_stream: bool, + dirent_buf: [api::AxDirEntry; 31], +} + +/// Entries returned by the [`ReadDir`] iterator. +pub struct DirEntry<'a> { + dir_path: &'a str, + entry_name: String, + entry_type: FileType, +} + +/// A builder used to create directories in various manners. +#[derive(Default, Debug)] +pub struct DirBuilder { + recursive: bool, +} + +impl<'a> ReadDir<'a> { + pub(super) fn new(path: &'a str) -> Result { + let mut opts = api::AxOpenOptions::new(); + opts.read(true); + let inner = api::ax_open_dir(path, &opts)?; + + const EMPTY: api::AxDirEntry = api::AxDirEntry::default(); + let dirent_buf = [EMPTY; 31]; + Ok(ReadDir { + path, + inner, + end_of_stream: false, + buf_pos: 0, + buf_end: 0, + dirent_buf, + }) + } +} + +impl<'a> Iterator for ReadDir<'a> { + type Item = Result>; + + fn next(&mut self) -> Option>> { + if self.end_of_stream { + return None; + } + + loop { + if self.buf_pos >= self.buf_end { + match api::ax_read_dir(&mut self.inner, &mut self.dirent_buf) { + Ok(n) => { + if n == 0 { + self.end_of_stream = true; + return None; + } + self.buf_pos = 0; + self.buf_end = n; + } + Err(e) => { + self.end_of_stream = true; + return Some(Err(e)); + } + } + } + let entry = &self.dirent_buf[self.buf_pos]; + self.buf_pos += 1; + let name_bytes = entry.name_as_bytes(); + if name_bytes == b"." || name_bytes == b".." { + continue; + } + let entry_name = unsafe { core::str::from_utf8_unchecked(name_bytes).into() }; + let entry_type = entry.entry_type(); + + return Some(Ok(DirEntry { + dir_path: self.path, + entry_name, + entry_type, + })); + } + } +} + +impl<'a> DirEntry<'a> { + /// Returns the full path to the file that this entry represents. + /// + /// The full path is created by joining the original path to `read_dir` + /// with the filename of this entry. + pub fn path(&self) -> String { + String::from(self.dir_path.trim_end_matches('/')) + "/" + &self.entry_name + } + + /// Returns the bare file name of this directory entry without any other + /// leading path component. + pub fn file_name(&self) -> String { + self.entry_name.clone() + } + + /// Returns the file type for the file that this entry points at. + pub fn file_type(&self) -> FileType { + self.entry_type + } +} + +impl fmt::Debug for DirEntry<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("DirEntry").field(&self.path()).finish() + } +} + +impl DirBuilder { + /// Creates a new set of options with default mode/security settings for all + /// platforms and also non-recursive. + pub fn new() -> Self { + Self { recursive: false } + } + + /// Indicates that directories should be created recursively, creating all + /// parent directories. Parents that do not exist are created with the same + /// security and permissions settings. + pub fn recursive(&mut self, recursive: bool) -> &mut Self { + self.recursive = recursive; + self + } + + /// Creates the specified directory with the options configured in this + /// builder. + pub fn create(&self, path: &str) -> Result<()> { + if self.recursive { + self.create_dir_all(path) + } else { + api::ax_create_dir(path) + } + } + + fn create_dir_all(&self, _path: &str) -> Result<()> { + axerrno::ax_err!( + Unsupported, + "Recursive directory creation is not supported yet" + ) + } +} diff --git a/ulib/axstd/src/fs/file.rs b/ulib/axstd/src/fs/file.rs new file mode 100644 index 000000000..6984057cc --- /dev/null +++ b/ulib/axstd/src/fs/file.rs @@ -0,0 +1,187 @@ +use crate::io::{prelude::*, Result, SeekFrom}; +use core::fmt; + +use arceos_api::fs as api; + +/// A structure representing a type of file with accessors for each file type. +/// It is returned by [`Metadata::file_type`] method. +pub type FileType = api::AxFileType; + +/// Representation of the various permissions on a file. +pub type Permissions = api::AxFilePerm; + +/// An object providing access to an open file on the filesystem. +pub struct File { + inner: api::AxFileHandle, +} + +/// Metadata information about a file. +pub struct Metadata(api::AxFileAttr); + +/// Options and flags which can be used to configure how a file is opened. +#[derive(Clone, Debug)] +pub struct OpenOptions(api::AxOpenOptions); + +impl OpenOptions { + /// Creates a blank new set of options ready for configuration. + pub const fn new() -> Self { + OpenOptions(api::AxOpenOptions::new()) + } + + /// Sets the option for read access. + pub fn read(&mut self, read: bool) -> &mut Self { + self.0.read(read); + self + } + + /// Sets the option for write access. + pub fn write(&mut self, write: bool) -> &mut Self { + self.0.write(write); + self + } + + /// Sets the option for the append mode. + pub fn append(&mut self, append: bool) -> &mut Self { + self.0.append(append); + self + } + + /// Sets the option for truncating a previous file. + pub fn truncate(&mut self, truncate: bool) -> &mut Self { + self.0.truncate(truncate); + self + } + + /// Sets the option to create a new file, or open it if it already exists. + pub fn create(&mut self, create: bool) -> &mut Self { + self.0.create(create); + self + } + + /// Sets the option to create a new file, failing if it already exists. + pub fn create_new(&mut self, create_new: bool) -> &mut Self { + self.0.create_new(create_new); + self + } + + /// Opens a file at `path` with the options specified by `self`. + pub fn open(&self, path: &str) -> Result { + api::ax_open_file(path, &self.0).map(|inner| File { inner }) + } +} + +impl Metadata { + /// Returns the file type for this metadata. + pub const fn file_type(&self) -> FileType { + self.0.file_type() + } + + /// Returns `true` if this metadata is for a directory. The + /// result is mutually exclusive to the result of + /// [`Metadata::is_file`]. + pub const fn is_dir(&self) -> bool { + self.0.is_dir() + } + + /// Returns `true` if this metadata is for a regular file. The + /// result is mutually exclusive to the result of + /// [`Metadata::is_dir`]. + pub const fn is_file(&self) -> bool { + self.0.is_file() + } + + /// Returns the size of the file, in bytes, this metadata is for. + #[allow(clippy::len_without_is_empty)] + pub const fn len(&self) -> u64 { + self.0.size() + } + + /// Returns the permissions of the file this metadata is for. + pub const fn permissions(&self) -> Permissions { + self.0.perm() + } + + /// Returns the total size of this file in bytes. + pub const fn size(&self) -> u64 { + self.0.size() + } + + /// Returns the number of blocks allocated to the file, in 512-byte units. + pub const fn blocks(&self) -> u64 { + self.0.blocks() + } +} + +impl fmt::Debug for Metadata { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Metadata") + .field("file_type", &self.file_type()) + .field("is_dir", &self.is_dir()) + .field("is_file", &self.is_file()) + .field("permissions", &self.permissions()) + .finish_non_exhaustive() + } +} + +impl File { + /// Attempts to open a file in read-only mode. + pub fn open(path: &str) -> Result { + OpenOptions::new().read(true).open(path) + } + + /// Opens a file in write-only mode. + pub fn create(path: &str) -> Result { + OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path) + } + + /// Creates a new file in read-write mode; error if the file exists. + pub fn create_new(path: &str) -> Result { + OpenOptions::new() + .read(true) + .write(true) + .create_new(true) + .open(path) + } + + /// Returns a new OpenOptions object. + pub fn options() -> OpenOptions { + OpenOptions::new() + } + + /// Truncates or extends the underlying file, updating the size of + /// this file to become `size`. + pub fn set_len(&self, size: u64) -> Result<()> { + api::ax_truncate_file(&self.inner, size) + } + + /// Queries metadata about the underlying file. + pub fn metadata(&self) -> Result { + api::ax_file_attr(&self.inner).map(Metadata) + } +} + +impl Read for File { + fn read(&mut self, buf: &mut [u8]) -> Result { + api::ax_read_file(&mut self.inner, buf) + } +} + +impl Write for File { + fn write(&mut self, buf: &[u8]) -> Result { + api::ax_write_file(&mut self.inner, buf) + } + + fn flush(&mut self) -> Result<()> { + api::ax_flush_file(&self.inner) + } +} + +impl Seek for File { + fn seek(&mut self, pos: SeekFrom) -> Result { + api::ax_seek_file(&mut self.inner, pos) + } +} diff --git a/ulib/axstd/src/fs/mod.rs b/ulib/axstd/src/fs/mod.rs new file mode 100644 index 000000000..5308045b9 --- /dev/null +++ b/ulib/axstd/src/fs/mod.rs @@ -0,0 +1,77 @@ +//! Filesystem manipulation operations. + +mod dir; +mod file; + +use crate::io::{self, prelude::*}; + +#[cfg(feature = "alloc")] +use alloc::{string::String, vec::Vec}; + +pub use self::dir::{DirBuilder, DirEntry, ReadDir}; +pub use self::file::{File, FileType, Metadata, OpenOptions, Permissions}; + +/// Read the entire contents of a file into a bytes vector. +#[cfg(feature = "alloc")] +pub fn read(path: &str) -> io::Result> { + let mut file = File::open(path)?; + let size = file.metadata().map(|m| m.len()).unwrap_or(0); + let mut bytes = Vec::with_capacity(size as usize); + file.read_to_end(&mut bytes)?; + Ok(bytes) +} + +/// Read the entire contents of a file into a string. +#[cfg(feature = "alloc")] +pub fn read_to_string(path: &str) -> io::Result { + let mut file = File::open(path)?; + let size = file.metadata().map(|m| m.len()).unwrap_or(0); + let mut string = String::with_capacity(size as usize); + file.read_to_string(&mut string)?; + Ok(string) +} + +/// Write a slice as the entire contents of a file. +pub fn write>(path: &str, contents: C) -> io::Result<()> { + File::create(path)?.write_all(contents.as_ref()) +} + +/// Given a path, query the file system to get information about a file, +/// directory, etc. +pub fn metadata(path: &str) -> io::Result { + File::open(path)?.metadata() +} + +/// Returns an iterator over the entries within a directory. +pub fn read_dir(path: &str) -> io::Result { + ReadDir::new(path) +} + +/// Creates a new, empty directory at the provided path. +pub fn create_dir(path: &str) -> io::Result<()> { + DirBuilder::new().create(path) +} + +/// Recursively create a directory and all of its parent components if they +/// are missing. +pub fn create_dir_all(path: &str) -> io::Result<()> { + DirBuilder::new().recursive(true).create(path) +} + +/// Removes an empty directory. +pub fn remove_dir(path: &str) -> io::Result<()> { + arceos_api::fs::ax_remove_dir(path) +} + +/// Removes a file from the filesystem. +pub fn remove_file(path: &str) -> io::Result<()> { + arceos_api::fs::ax_remove_file(path) +} + +/// Rename a file or directory to a new name. +/// Delete the original file if `old` already exists. +/// +/// This only works then the new path is in the same mounted fs. +pub fn rename(old: &str, new: &str) -> io::Result<()> { + arceos_api::fs::ax_rename(old, new) +} diff --git a/ulib/axstd/src/io/mod.rs b/ulib/axstd/src/io/mod.rs new file mode 100644 index 000000000..7a0cf1fc1 --- /dev/null +++ b/ulib/axstd/src/io/mod.rs @@ -0,0 +1,28 @@ +//! Traits, helpers, and type definitions for core I/O functionality. + +mod stdio; + +pub use axio::prelude; +pub use axio::{BufRead, BufReader, Error, Read, Seek, SeekFrom, Write}; + +#[doc(hidden)] +pub use self::stdio::__print_impl; +pub use self::stdio::{stdin, stdout, Stdin, StdinLock, Stdout, StdoutLock}; + +/// A specialized [`Result`] type for I/O operations. +/// +/// This type is broadly used across [`axstd::io`] for any operation which may +/// produce an error. +/// +/// This typedef is generally used to avoid writing out [`io::Error`] directly and +/// is otherwise a direct mapping to [`Result`]. +/// +/// While usual Rust style is to import types directly, aliases of [`Result`] +/// often are not, to make it easier to distinguish between them. [`Result`] is +/// generally assumed to be [`std::result::Result`][`Result`], and so users of this alias +/// will generally use `io::Result` instead of shadowing the [prelude]'s import +/// of [`std::result::Result`][`Result`]. +/// +/// [`axstd::io`]: crate::io +/// [`io::Error`]: Error +pub type Result = axio::Result; diff --git a/ulib/axstd/src/io/stdio.rs b/ulib/axstd/src/io/stdio.rs new file mode 100644 index 000000000..0b07f4bb6 --- /dev/null +++ b/ulib/axstd/src/io/stdio.rs @@ -0,0 +1,173 @@ +use crate::io::{self, prelude::*, BufReader}; +use crate::sync::{Mutex, MutexGuard}; + +#[cfg(feature = "alloc")] +use alloc::{string::String, vec::Vec}; + +struct StdinRaw; +struct StdoutRaw; + +impl Read for StdinRaw { + // Non-blocking read, returns number of bytes read. + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut read_len = 0; + while read_len < buf.len() { + if let Some(c) = arceos_api::stdio::ax_console_read_byte() { + buf[read_len] = c; + read_len += 1; + } else { + break; + } + } + Ok(read_len) + } +} + +impl Write for StdoutRaw { + fn write(&mut self, buf: &[u8]) -> io::Result { + arceos_api::stdio::ax_console_write_bytes(buf) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// A handle to the standard input stream of a process. +pub struct Stdin { + inner: &'static Mutex>, +} + +/// A locked reference to the [`Stdin`] handle. +pub struct StdinLock<'a> { + inner: MutexGuard<'a, BufReader>, +} + +impl Stdin { + /// Locks this handle to the standard input stream, returning a readable + /// guard. + /// + /// The lock is released when the returned lock goes out of scope. The + /// returned guard also implements the [`Read`] and [`BufRead`] traits for + /// accessing the underlying data. + pub fn lock(&self) -> StdinLock<'static> { + // Locks this handle with 'static lifetime. This depends on the + // implementation detail that the underlying `Mutex` is static. + StdinLock { + inner: self.inner.lock(), + } + } + + /// Locks this handle and reads a line of input, appending it to the specified buffer. + #[cfg(feature = "alloc")] + pub fn read_line(&self, buf: &mut String) -> io::Result { + self.inner.lock().read_line(buf) + } +} + +impl Read for Stdin { + // Block until at least one byte is read. + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let read_len = self.inner.lock().read(buf)?; + if buf.is_empty() || read_len > 0 { + return Ok(read_len); + } + // try again until we got something + loop { + let read_len = self.inner.lock().read(buf)?; + if read_len > 0 { + return Ok(read_len); + } + crate::thread::yield_now(); + } + } +} + +impl Read for StdinLock<'_> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.inner.read(buf) + } +} + +impl BufRead for StdinLock<'_> { + fn fill_buf(&mut self) -> io::Result<&[u8]> { + self.inner.fill_buf() + } + + fn consume(&mut self, n: usize) { + self.inner.consume(n) + } + + #[cfg(feature = "alloc")] + fn read_until(&mut self, byte: u8, buf: &mut Vec) -> io::Result { + self.inner.read_until(byte, buf) + } + + #[cfg(feature = "alloc")] + fn read_line(&mut self, buf: &mut String) -> io::Result { + self.inner.read_line(buf) + } +} + +/// A handle to the global standard output stream of the current process. +pub struct Stdout { + inner: &'static Mutex, +} + +/// A locked reference to the [`Stdout`] handle. +pub struct StdoutLock<'a> { + inner: MutexGuard<'a, StdoutRaw>, +} + +impl Stdout { + /// Locks this handle to the standard output stream, returning a writable + /// guard. + /// + /// The lock is released when the returned lock goes out of scope. The + /// returned guard also implements the `Write` trait for writing data. + pub fn lock(&self) -> StdoutLock<'static> { + StdoutLock { + inner: self.inner.lock(), + } + } +} + +impl Write for Stdout { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.lock().write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.inner.lock().flush() + } +} + +impl Write for StdoutLock<'_> { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +/// Constructs a new handle to the standard input of the current process. +pub fn stdin() -> Stdin { + static INSTANCE: Mutex> = Mutex::new(BufReader::new(StdinRaw)); + Stdin { inner: &INSTANCE } +} + +/// Constructs a new handle to the standard output of the current process. +pub fn stdout() -> Stdout { + static INSTANCE: Mutex = Mutex::new(StdoutRaw); + Stdout { inner: &INSTANCE } +} + +#[doc(hidden)] +pub fn __print_impl(args: core::fmt::Arguments) { + if cfg!(feature = "smp") { + // synchronize using the lock in axlog, to avoid interleaving + // with kernel logs + arceos_api::stdio::ax_console_write_fmt(args).unwrap(); + } else { + stdout().lock().write_fmt(args).unwrap(); + } +} diff --git a/ulib/axstd/src/lib.rs b/ulib/axstd/src/lib.rs new file mode 100644 index 000000000..ced516cd7 --- /dev/null +++ b/ulib/axstd/src/lib.rs @@ -0,0 +1,78 @@ +//! # The ArceOS Standard Library +//! +//! The [ArceOS] Standard Library is a mini-std library, with an interface similar +//! to rust [std], but calling the functions directly in ArceOS modules, instead +//! of using libc and system calls. +//! +//! These features are exactly the same as those in [axfeat], they are used to +//! provide users with the selection of features in axfeat, without import +//! [axfeat] additionally: +//! +//! ## Cargo Features +//! +//! - CPU +//! - `smp`: Enable SMP (symmetric multiprocessing) support. +//! - `fp_simd`: Enable floating point and SIMD support. +//! - Interrupts: +//! - `irq`: Enable interrupt handling support. +//! - Memory +//! - `alloc`: Enable dynamic memory allocation. +//! - `alloc-tlsf`: Use the TLSF allocator. +//! - `alloc-slab`: Use the slab allocator. +//! - `alloc-buddy`: Use the buddy system allocator. +//! - `paging`: Enable page table manipulation. +//! - `tls`: Enable thread-local storage. +//! - Task management +//! - `multitask`: Enable multi-threading support. +//! - `sched_fifo`: Use the FIFO cooperative scheduler. +//! - `sched_rr`: Use the Round-robin preemptive scheduler. +//! - `sched_cfs`: Use the Completely Fair Scheduler (CFS) preemptive scheduler. +//! - Upperlayer stacks +//! - `fs`: Enable file system support. +//! - `myfs`: Allow users to define their custom filesystems to override the default. +//! - `net`: Enable networking support. +//! - `dns`: Enable DNS lookup support. +//! - `display`: Enable graphics support. +//! - Device drivers +//! - `bus-mmio`: Use device tree to probe all MMIO devices. +//! - `bus-pci`: Use PCI bus to probe all PCI devices. +//! - `driver-ramdisk`: Use the RAM disk to emulate the block device. +//! - `driver-ixgbe`: Enable the Intel 82599 10Gbit NIC driver. +//! - `driver-bcm2835-sdhci`: Enable the BCM2835 SDHCI driver (Raspberry Pi SD card). +//! - Logging +//! - `log-level-off`: Disable all logging. +//! - `log-level-error`, `log-level-warn`, `log-level-info`, `log-level-debug`, +//! `log-level-trace`: Keep logging only at the specified level or higher. +//! +//! [ArceOS]: https://github.com/rcore-os/arceos + +#![cfg_attr(all(not(test), not(doc)), no_std)] +#![feature(doc_cfg)] +#![feature(doc_auto_cfg)] +#![feature(ip_in_core)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +#[cfg(feature = "alloc")] +#[doc(no_inline)] +pub use alloc::{boxed, collections, format, string, vec}; + +#[doc(no_inline)] +pub use core::{arch, cell, cmp, hint, marker, mem, ops, ptr, slice, str}; + +#[macro_use] +mod macros; + +pub mod env; +pub mod io; +pub mod os; +pub mod process; +pub mod sync; +pub mod thread; +pub mod time; + +#[cfg(feature = "fs")] +pub mod fs; +#[cfg(feature = "net")] +pub mod net; diff --git a/ulib/axstd/src/macros.rs b/ulib/axstd/src/macros.rs new file mode 100644 index 000000000..c7cd6535e --- /dev/null +++ b/ulib/axstd/src/macros.rs @@ -0,0 +1,23 @@ +//! Standard library macros + +/// Prints to the standard output. +/// +/// Equivalent to the [`println!`] macro except that a newline is not printed at +/// the end of the message. +/// +/// [`println!`]: crate::println +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => { + $crate::io::__print_impl(format_args!($($arg)*)); + } +} + +/// Prints to the standard output, with a newline. +#[macro_export] +macro_rules! println { + () => { $crate::print!("\n") }; + ($($arg:tt)*) => { + $crate::io::__print_impl(format_args!("{}\n", format_args!($($arg)*))); + } +} diff --git a/ulib/axstd/src/net/mod.rs b/ulib/axstd/src/net/mod.rs new file mode 100644 index 000000000..3d789cebb --- /dev/null +++ b/ulib/axstd/src/net/mod.rs @@ -0,0 +1,46 @@ +//! Networking primitives for TCP/UDP communication. +//! +//! This module provides networking functionality for the Transmission Control and User +//! Datagram Protocols, as well as types for IP and socket addresses. +//! +//! # Organization +//! +//! * [`TcpListener`] and [`TcpStream`] provide functionality for communication over TCP +//! * [`UdpSocket`] provides functionality for communication over UDP +//! * [`IpAddr`] represents IP addresses of either IPv4 or IPv6; [`Ipv4Addr`] and +//! [`Ipv6Addr`] are respectively IPv4 and IPv6 addresses +//! * [`SocketAddr`] represents socket addresses of either IPv4 or IPv6; [`SocketAddrV4`] +//! and [`SocketAddrV6`] are respectively IPv4 and IPv6 socket addresses +//! * [`ToSocketAddrs`] is a trait that is used for generic address resolution when interacting +//! with networking objects like [`TcpListener`], [`TcpStream`] or [`UdpSocket`] + +mod socket_addr; +mod tcp; +mod udp; + +pub use self::socket_addr::{IpAddr, Ipv4Addr, Ipv6Addr}; +pub use self::socket_addr::{SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; +pub use self::tcp::{TcpListener, TcpStream}; +pub use self::udp::UdpSocket; + +use crate::io; + +fn each_addr(addr: A, mut f: F) -> io::Result +where + F: FnMut(io::Result<&SocketAddr>) -> io::Result, +{ + let addrs = match addr.to_socket_addrs() { + Ok(addrs) => addrs, + Err(e) => return f(Err(e)), + }; + let mut last_err = None; + for addr in addrs { + match f(Ok(&addr)) { + Ok(l) => return Ok(l), + Err(e) => last_err = Some(e), + } + } + Err(last_err.unwrap_or_else(|| { + axerrno::ax_err_type!(InvalidInput, "could not resolve to any addresses") + })) +} diff --git a/ulib/axstd/src/net/socket_addr.rs b/ulib/axstd/src/net/socket_addr.rs new file mode 100644 index 000000000..56f7e0b4a --- /dev/null +++ b/ulib/axstd/src/net/socket_addr.rs @@ -0,0 +1,190 @@ +extern crate alloc; + +use crate::io; +use alloc::string::String; +use core::{iter, option, slice}; + +pub use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; + +/// A trait for objects which can be converted or resolved to one or more +/// [`SocketAddr`] values. +/// +/// This trait is used for generic address resolution when constructing network +/// objects. By default it is implemented for the following types: +/// +/// * [`SocketAddr`]: [`to_socket_addrs`] is the identity function. +/// +/// * [`SocketAddrV4`], ([IpAddr], [u16]), +/// ([Ipv4Addr], [u16]): +/// [`to_socket_addrs`] constructs a [`SocketAddr`] trivially. +/// +/// * (&[str], [u16]): &[str] should be either a string representation +/// of an [`IpAddr`] address as expected by [`FromStr`] implementation or a host +/// name. [`u16`] is the port number. +/// +/// * &[str]: the string should be either a string representation of a +/// [`SocketAddr`] as expected by its [`FromStr`] implementation or a string like +/// `:` pair where `` is a [`u16`] value. +/// +/// [`FromStr`]: core::str::FromStr +/// [`to_socket_addrs`]: ToSocketAddrs::to_socket_addrs +pub trait ToSocketAddrs { + /// Returned iterator over socket addresses which this type may correspond to. + type Iter: Iterator; + + /// Converts this object to an iterator of resolved [`SocketAddr`]s. + fn to_socket_addrs(&self) -> io::Result; +} + +impl ToSocketAddrs for SocketAddr { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + Ok(Some(*self).into_iter()) + } +} + +impl ToSocketAddrs for SocketAddrV4 { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + SocketAddr::V4(*self).to_socket_addrs() + } +} + +impl ToSocketAddrs for (IpAddr, u16) { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + let (ip, port) = *self; + SocketAddr::new(ip, port).to_socket_addrs() + } +} + +impl ToSocketAddrs for (Ipv4Addr, u16) { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + let (ip, port) = *self; + SocketAddrV4::new(ip, port).to_socket_addrs() + } +} + +impl<'a> ToSocketAddrs for &'a [SocketAddr] { + type Iter = iter::Cloned>; + + fn to_socket_addrs(&self) -> io::Result { + Ok(self.iter().cloned()) + } +} + +impl ToSocketAddrs for &T { + type Iter = T::Iter; + fn to_socket_addrs(&self) -> io::Result { + (**self).to_socket_addrs() + } +} + +#[cfg(not(feature = "dns"))] +#[doc(cfg(feature = "net"))] +mod no_dns { + use super::*; + + impl ToSocketAddrs for (&str, u16) { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + let (host, port) = *self; + Ok(host + .parse::() + .ok() + .map(|addr| { + let addr = SocketAddrV4::new(addr, port); + SocketAddr::V4(addr) + }) + .into_iter()) + } + } + + impl ToSocketAddrs for str { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + // parse as a regular SocketAddr first + Ok(self.parse().ok().into_iter()) + } + } + + impl ToSocketAddrs for (String, u16) { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + (&*self.0, self.1).to_socket_addrs() + } + } + + impl ToSocketAddrs for String { + type Iter = option::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + (**self).to_socket_addrs() + } + } +} + +#[cfg(feature = "dns")] +#[doc(cfg(feature = "net"))] +mod dns { + use super::*; + use alloc::{vec, vec::Vec}; + + impl ToSocketAddrs for (&str, u16) { + type Iter = vec::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + let (host, port) = *self; + + // try to parse the host as a regular IP address first + if let Ok(addr) = host.parse::() { + let addr = SocketAddrV4::new(addr, port); + return Ok(vec![SocketAddr::V4(addr)].into_iter()); + } + + Ok(arceos_api::net::ax_dns_query(host)? + .into_iter() + .map(|ip| SocketAddr::new(ip, port)) + .collect::>() + .into_iter()) + } + } + + impl ToSocketAddrs for str { + type Iter = vec::IntoIter; + + fn to_socket_addrs(&self) -> io::Result> { + // try to parse as a regular SocketAddr first + if let Ok(addr) = self.parse() { + return Ok(vec![addr].into_iter()); + } + + // split the string by ':' and convert the second part to u16 + let (host, port_str) = self + .rsplit_once(':') + .ok_or_else(|| axerrno::ax_err_type!(InvalidInput, "invalid socket address"))?; + let port: u16 = port_str + .parse() + .map_err(|_| axerrno::ax_err_type!(InvalidInput, "invalid port value"))?; + + Ok(arceos_api::net::ax_dns_query(host)? + .into_iter() + .map(|ip| SocketAddr::new(ip, port)) + .collect::>() + .into_iter()) + } + } + + impl ToSocketAddrs for (String, u16) { + type Iter = vec::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + (&*self.0, self.1).to_socket_addrs() + } + } + + impl ToSocketAddrs for String { + type Iter = vec::IntoIter; + fn to_socket_addrs(&self) -> io::Result> { + (**self).to_socket_addrs() + } + } +} diff --git a/ulib/axstd/src/net/tcp.rs b/ulib/axstd/src/net/tcp.rs new file mode 100644 index 000000000..f527f1467 --- /dev/null +++ b/ulib/axstd/src/net/tcp.rs @@ -0,0 +1,105 @@ +use super::{SocketAddr, ToSocketAddrs}; +use crate::io::{self, prelude::*}; + +use arceos_api::net::{self as api, AxTcpSocketHandle}; + +/// A TCP stream between a local and a remote socket. +pub struct TcpStream(AxTcpSocketHandle); + +/// A TCP socket server, listening for connections. +pub struct TcpListener(AxTcpSocketHandle); + +impl TcpStream { + /// Opens a TCP connection to a remote host. + /// + /// `addr` is an address of the remote host. Anything which implements + /// [`ToSocketAddrs`] trait can be supplied for the address; see this trait + /// documentation for concrete examples. + /// + /// If `addr` yields multiple addresses, `connect` will be attempted with + /// each of the addresses until a connection is successful. If none of + /// the addresses result in a successful connection, the error returned from + /// the last connection attempt (the last address) is returned. + pub fn connect(addr: A) -> io::Result { + super::each_addr(addr, |addr: io::Result<&SocketAddr>| { + let addr = addr?; + let socket = api::ax_tcp_socket(); + api::ax_tcp_connect(&socket, *addr)?; + Ok(TcpStream(socket)) + }) + } + + /// Returns the socket address of the local half of this TCP connection. + pub fn local_addr(&self) -> io::Result { + api::ax_tcp_socket_addr(&self.0) + } + + /// Returns the socket address of the remote peer of this TCP connection. + pub fn peer_addr(&self) -> io::Result { + api::ax_tcp_peer_addr(&self.0) + } + + /// Shuts down the connection. + pub fn shutdown(&self) -> io::Result<()> { + api::ax_tcp_shutdown(&self.0) + } +} + +impl Read for TcpStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + api::ax_tcp_recv(&self.0, buf) + } +} + +impl Write for TcpStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + api::ax_tcp_send(&self.0, buf) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl TcpListener { + /// Creates a new `TcpListener` which will be bound to the specified + /// address. + /// + /// The returned listener is ready for accepting connections. + /// + /// Binding with a port number of 0 will request that the OS assigns a port + /// to this listener. The port allocated can be queried via the + /// [`TcpListener::local_addr`] method. + /// + /// The address type can be any implementor of [`ToSocketAddrs`] trait. See + /// its documentation for concrete examples. + /// + /// If `addr` yields multiple addresses, `bind` will be attempted with + /// each of the addresses until one succeeds and returns the listener. If + /// none of the addresses succeed in creating a listener, the error returned + /// from the last attempt (the last address) is returned. + pub fn bind(addr: A) -> io::Result { + super::each_addr(addr, |addr: io::Result<&SocketAddr>| { + let addr = addr?; + let backlog = 128; + let socket = api::ax_tcp_socket(); + api::ax_tcp_bind(&socket, *addr)?; + api::ax_tcp_listen(&socket, backlog)?; + Ok(TcpListener(socket)) + }) + } + + /// Returns the local socket address of this listener. + pub fn local_addr(&self) -> io::Result { + api::ax_tcp_socket_addr(&self.0) + } + + /// Accept a new incoming connection from this listener. + /// + /// This function will block the calling thread until a new TCP connection + /// is established. When established, the corresponding [`TcpStream`] and the + /// remote peer's address will be returned. + pub fn accept(&self) -> io::Result<(TcpStream, SocketAddr)> { + api::ax_tcp_accept(&self.0).map(|(a, b)| (TcpStream(a), b)) + } +} diff --git a/ulib/axstd/src/net/udp.rs b/ulib/axstd/src/net/udp.rs new file mode 100644 index 000000000..65df9e8ff --- /dev/null +++ b/ulib/axstd/src/net/udp.rs @@ -0,0 +1,96 @@ +use super::{SocketAddr, ToSocketAddrs}; +use crate::io; + +use arceos_api::net::{self as api, AxUdpSocketHandle}; + +/// A UDP socket. +pub struct UdpSocket(AxUdpSocketHandle); + +impl UdpSocket { + /// Creates a UDP socket from the given address. + /// + /// The address type can be any implementor of [`ToSocketAddrs`] trait. See + /// its documentation for concrete examples. + /// + /// If `addr` yields multiple addresses, `bind` will be attempted with + /// each of the addresses until one succeeds and returns the socket. If none + /// of the addresses succeed in creating a socket, the error returned from + /// the last attempt (the last address) is returned. + pub fn bind(addr: A) -> io::Result { + super::each_addr(addr, |addr: io::Result<&SocketAddr>| { + let addr = addr?; + let socket = api::ax_udp_socket(); + api::ax_udp_bind(&socket, *addr)?; + Ok(UdpSocket(socket)) + }) + } + + /// Returns the socket address that this socket was created from. + pub fn local_addr(&self) -> io::Result { + api::ax_udp_socket_addr(&self.0) + } + + /// Returns the socket address of the remote peer this socket was connected to. + pub fn peer_addr(&self) -> io::Result { + api::ax_udp_peer_addr(&self.0) + } + + /// Receives a single datagram message on the socket. On success, returns + /// the number of bytes read and the origin. + pub fn recv_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + api::ax_udp_recv_from(&self.0, buf) + } + + /// Receives a single datagram message on the socket, without removing it from + /// the queue. On success, returns the number of bytes read and the origin. + pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { + api::ax_udp_peek_from(&self.0, buf) + } + + /// Sends data on the socket to the given address. On success, returns the + /// number of bytes written. + /// + /// Address type can be any implementor of [`ToSocketAddrs`] trait. See its + /// documentation for concrete examples. + /// + /// It is possible for `addr` to yield multiple addresses, but `send_to` + /// will only send data to the first address yielded by `addr`. + pub fn send_to(&self, buf: &[u8], addr: A) -> io::Result { + match addr.to_socket_addrs()?.next() { + Some(addr) => api::ax_udp_send_to(&self.0, buf, addr), + None => axerrno::ax_err!(InvalidInput, "no addresses to send data to"), + } + } + + /// Connects this UDP socket to a remote address, allowing the `send` and + /// `recv` syscalls to be used to send data and also applies filters to only + /// receive data from the specified address. + /// + /// If `addr` yields multiple addresses, `connect` will be attempted with + /// each of the addresses until the underlying OS function returns no + /// error. Note that usually, a successful `connect` call does not specify + /// that there is a remote server listening on the port, rather, such an + /// error would only be detected after the first send. If the OS returns an + /// error for each of the specified addresses, the error returned from the + /// last connection attempt (the last address) is returned. + pub fn connect(&self, addr: SocketAddr) -> io::Result<()> { + super::each_addr(addr, |addr: io::Result<&SocketAddr>| { + let addr = addr?; + api::ax_udp_connect(&self.0, *addr) + }) + } + + /// Sends data on the socket to the remote address to which it is connected. + /// + /// [`UdpSocket::connect`] will connect this socket to a remote address. This + /// method will fail if the socket is not connected. + pub fn send(&self, buf: &[u8]) -> io::Result { + api::ax_udp_send(&self.0, buf) + } + + /// Receives a single datagram message on the socket from the remote address to + /// which it is connected. On success, returns the number of bytes read. + pub fn recv(&self, buf: &mut [u8]) -> io::Result { + api::ax_udp_recv(&self.0, buf) + } +} diff --git a/ulib/axstd/src/os.rs b/ulib/axstd/src/os.rs new file mode 100644 index 000000000..d1bdf8258 --- /dev/null +++ b/ulib/axstd/src/os.rs @@ -0,0 +1,6 @@ +//! OS-specific functionality. + +/// ArceOS-specific definitions. +pub mod arceos { + pub use arceos_api as api; +} diff --git a/ulib/axstd/src/process.rs b/ulib/axstd/src/process.rs new file mode 100644 index 000000000..28349d224 --- /dev/null +++ b/ulib/axstd/src/process.rs @@ -0,0 +1,10 @@ +//! A module for working with processes. +//! +//! Since ArceOS is a unikernel, there is no concept of processes. The +//! process-related functions will affect the entire system, such as [`exit`] +//! will shutdown the whole system. + +/// Shutdown the whole system. +pub fn exit(_exit_code: i32) -> ! { + arceos_api::sys::ax_terminate(); +} diff --git a/ulib/axstd/src/sync/mod.rs b/ulib/axstd/src/sync/mod.rs new file mode 100644 index 000000000..75f003fa5 --- /dev/null +++ b/ulib/axstd/src/sync/mod.rs @@ -0,0 +1,19 @@ +//! Useful synchronization primitives. + +#[doc(no_inline)] +pub use core::sync::atomic; + +#[cfg(feature = "alloc")] +#[doc(no_inline)] +pub use alloc::sync::{Arc, Weak}; + +#[cfg(feature = "multitask")] +mod mutex; + +#[cfg(feature = "multitask")] +#[doc(cfg(feature = "multitask"))] +pub use self::mutex::{Mutex, MutexGuard}; + +#[cfg(not(feature = "multitask"))] +#[doc(cfg(not(feature = "multitask")))] +pub use spinlock::{SpinRaw as Mutex, SpinRawGuard as MutexGuard}; // never used in IRQ context diff --git a/ulib/axstd/src/sync/mutex.rs b/ulib/axstd/src/sync/mutex.rs new file mode 100644 index 000000000..727324b74 --- /dev/null +++ b/ulib/axstd/src/sync/mutex.rs @@ -0,0 +1,198 @@ +//! A naïve sleeping mutex. + +use core::cell::UnsafeCell; +use core::fmt; +use core::ops::{Deref, DerefMut}; +use core::sync::atomic::{AtomicU64, Ordering}; + +use arceos_api::task::{self as api, AxWaitQueueHandle}; + +/// A mutual exclusion primitive useful for protecting shared data, similar to +/// [`std::sync::Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html). +/// +/// When the mutex is locked, the current task will block and be put into the +/// wait queue. When the mutex is unlocked, all tasks waiting on the queue +/// will be woken up. +pub struct Mutex { + wq: AxWaitQueueHandle, + owner_id: AtomicU64, + data: UnsafeCell, +} + +/// A guard that provides mutable data access. +/// +/// When the guard falls out of scope it will release the lock. +pub struct MutexGuard<'a, T: ?Sized + 'a> { + lock: &'a Mutex, + data: *mut T, +} + +// Same unsafe impls as `std::sync::Mutex` +unsafe impl Sync for Mutex {} +unsafe impl Send for Mutex {} + +impl Mutex { + /// Creates a new [`Mutex`] wrapping the supplied data. + #[inline(always)] + pub const fn new(data: T) -> Self { + Self { + wq: AxWaitQueueHandle::new(), + owner_id: AtomicU64::new(0), + data: UnsafeCell::new(data), + } + } + + /// Consumes this [`Mutex`] and unwraps the underlying data. + #[inline(always)] + pub fn into_inner(self) -> T { + // We know statically that there are no outstanding references to + // `self` so there's no need to lock. + let Mutex { data, .. } = self; + data.into_inner() + } +} + +impl Mutex { + /// Returns `true` if the lock is currently held. + /// + /// # Safety + /// + /// This function provides no synchronization guarantees and so its result should be considered 'out of date' + /// the instant it is called. Do not use it for synchronization purposes. However, it may be useful as a heuristic. + #[inline(always)] + pub fn is_locked(&self) -> bool { + self.owner_id.load(Ordering::Relaxed) != 0 + } + + /// Locks the [`Mutex`] and returns a guard that permits access to the inner data. + /// + /// The returned value may be dereferenced for data access + /// and the lock will be dropped when the guard falls out of scope. + pub fn lock(&self) -> MutexGuard { + let current_id = api::ax_current_task_id(); + loop { + // Can fail to lock even if the spinlock is not locked. May be more efficient than `try_lock` + // when called in a loop. + match self.owner_id.compare_exchange_weak( + 0, + current_id, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => break, + Err(owner_id) => { + assert_ne!( + owner_id, current_id, + "Thread({}) tried to acquire mutex it already owns.", + current_id, + ); + // Wait until the lock looks unlocked before retrying + api::ax_wait_queue_wait(&self.wq, || !self.is_locked(), None); + } + } + } + MutexGuard { + lock: self, + data: unsafe { &mut *self.data.get() }, + } + } + + /// Try to lock this [`Mutex`], returning a lock guard if successful. + #[inline(always)] + pub fn try_lock(&self) -> Option> { + let current_id = api::ax_current_task_id(); + // The reason for using a strong compare_exchange is explained here: + // https://github.com/Amanieu/parking_lot/pull/207#issuecomment-575869107 + if self + .owner_id + .compare_exchange(0, current_id, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + { + Some(MutexGuard { + lock: self, + data: unsafe { &mut *self.data.get() }, + }) + } else { + None + } + } + + /// Force unlock the [`Mutex`]. + /// + /// # Safety + /// + /// This is *extremely* unsafe if the lock is not held by the current + /// thread. However, this can be useful in some instances for exposing + /// the lock to FFI that doesn’t know how to deal with RAII. + pub unsafe fn force_unlock(&self) { + let owner_id = self.owner_id.swap(0, Ordering::Release); + let current_id = api::ax_current_task_id(); + assert_eq!( + owner_id, current_id, + "Thread({}) tried to release mutex it doesn't own", + current_id, + ); + // wake up one waiting thread. + api::ax_wait_queue_wake(&self.wq, 1); + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the [`Mutex`] mutably, and a mutable reference is guaranteed to be exclusive in + /// Rust, no actual locking needs to take place -- the mutable borrow statically guarantees no locks exist. As + /// such, this is a 'zero-cost' operation. + #[inline(always)] + pub fn get_mut(&mut self) -> &mut T { + // We know statically that there are no other references to `self`, so + // there's no need to lock the inner mutex. + unsafe { &mut *self.data.get() } + } +} + +impl Default for Mutex { + #[inline(always)] + fn default() -> Self { + Self::new(Default::default()) + } +} + +impl fmt::Debug for Mutex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.try_lock() { + Some(guard) => write!(f, "Mutex {{ data: ") + .and_then(|()| (*guard).fmt(f)) + .and_then(|()| write!(f, "}}")), + None => write!(f, "Mutex {{ }}"), + } + } +} + +impl<'a, T: ?Sized> Deref for MutexGuard<'a, T> { + type Target = T; + #[inline(always)] + fn deref(&self) -> &T { + // We know statically that only we are referencing data + unsafe { &*self.data } + } +} + +impl<'a, T: ?Sized> DerefMut for MutexGuard<'a, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut T { + // We know statically that only we are referencing data + unsafe { &mut *self.data } + } +} + +impl<'a, T: ?Sized + fmt::Debug> fmt::Debug for MutexGuard<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> { + /// The dropping of the [`MutexGuard`] will release the lock it was created from. + fn drop(&mut self) { + unsafe { self.lock.force_unlock() } + } +} diff --git a/ulib/axstd/src/thread/mod.rs b/ulib/axstd/src/thread/mod.rs new file mode 100644 index 000000000..452d2cc5e --- /dev/null +++ b/ulib/axstd/src/thread/mod.rs @@ -0,0 +1,41 @@ +//! Native threads. + +#[cfg(feature = "multitask")] +mod multi; +#[cfg(feature = "multitask")] +pub use multi::*; + +use arceos_api::task as api; + +/// Current thread gives up the CPU time voluntarily, and switches to another +/// ready thread. +/// +/// For single-threaded configuration (`multitask` feature is disabled), we just +/// relax the CPU and wait for incoming interrupts. +pub fn yield_now() { + api::ax_yield_now(); +} + +/// Exits the current thread. +/// +/// For single-threaded configuration (`multitask` feature is disabled), +/// it directly terminates the main thread and shutdown. +pub fn exit(exit_code: i32) -> ! { + api::ax_exit(exit_code); +} + +/// Current thread is going to sleep for the given duration. +/// +/// If one of `multitask` or `irq` features is not enabled, it uses busy-wait +/// instead. +pub fn sleep(dur: core::time::Duration) { + sleep_until(arceos_api::time::ax_current_time() + dur); +} + +/// Current thread is going to sleep, it will be woken up at the given deadline. +/// +/// If one of `multitask` or `irq` features is not enabled, it uses busy-wait +/// instead. +pub fn sleep_until(deadline: arceos_api::time::AxTimeValue) { + api::ax_sleep_until(deadline); +} diff --git a/ulib/axstd/src/thread/multi.rs b/ulib/axstd/src/thread/multi.rs new file mode 100644 index 000000000..5ac8c3e5d --- /dev/null +++ b/ulib/axstd/src/thread/multi.rs @@ -0,0 +1,189 @@ +//! Thread APIs for multi-threading configuration. + +extern crate alloc; + +use crate::io; +use alloc::{string::String, sync::Arc}; +use core::{cell::UnsafeCell, num::NonZeroU64}; + +use arceos_api::task::{self as api, AxTaskHandle}; +use axerrno::ax_err_type; + +/// A unique identifier for a running thread. +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +pub struct ThreadId(NonZeroU64); + +/// A handle to a thread. +pub struct Thread { + id: ThreadId, +} + +impl ThreadId { + /// This returns a numeric identifier for the thread identified by this + /// `ThreadId`. + pub fn as_u64(&self) -> NonZeroU64 { + self.0 + } +} + +impl Thread { + fn from_id(id: u64) -> Self { + Self { + id: ThreadId(NonZeroU64::new(id).unwrap()), + } + } + + /// Gets the thread's unique identifier. + pub fn id(&self) -> ThreadId { + self.id + } +} + +/// Thread factory, which can be used in order to configure the properties of +/// a new thread. +/// +/// Methods can be chained on it in order to configure it. +#[derive(Debug)] +pub struct Builder { + // A name for the thread-to-be, for identification in panic messages + name: Option, + // The size of the stack for the spawned thread in bytes + stack_size: Option, +} + +impl Builder { + /// Generates the base configuration for spawning a thread, from which + /// configuration methods can be chained. + pub const fn new() -> Builder { + Builder { + name: None, + stack_size: None, + } + } + + /// Names the thread-to-be. + pub fn name(mut self, name: String) -> Builder { + self.name = Some(name); + self + } + + /// Sets the size of the stack (in bytes) for the new thread. + pub fn stack_size(mut self, size: usize) -> Builder { + self.stack_size = Some(size); + self + } + + /// Spawns a new thread by taking ownership of the `Builder`, and returns an + /// [`io::Result`] to its [`JoinHandle`]. + /// + /// The spawned thread may outlive the caller (unless the caller thread + /// is the main thread; the whole process is terminated when the main + /// thread finishes). The join handle can be used to block on + /// termination of the spawned thread. + pub fn spawn(self, f: F) -> io::Result> + where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, + { + unsafe { self.spawn_unchecked(f) } + } + + unsafe fn spawn_unchecked(self, f: F) -> io::Result> + where + F: FnOnce() -> T, + F: Send + 'static, + T: Send + 'static, + { + let name = self.name.unwrap_or_default(); + let stack_size = self + .stack_size + .unwrap_or(arceos_api::config::TASK_STACK_SIZE); + + let my_packet = Arc::new(Packet { + result: UnsafeCell::new(None), + }); + let their_packet = my_packet.clone(); + + let main = move || { + let ret = f(); + // SAFETY: `their_packet` as been built just above and moved by the + // closure (it is an Arc<...>) and `my_packet` will be stored in the + // same `JoinHandle` as this closure meaning the mutation will be + // safe (not modify it and affect a value far away). + unsafe { *their_packet.result.get() = Some(ret) }; + drop(their_packet); + }; + + let task = api::ax_spawn(main, name, stack_size); + Ok(JoinHandle { + thread: Thread::from_id(task.id()), + native: task, + packet: my_packet, + }) + } +} + +/// Gets a handle to the thread that invokes it. +pub fn current() -> Thread { + let id = api::ax_current_task_id(); + Thread::from_id(id) +} + +/// Spawns a new thread, returning a [`JoinHandle`] for it. +/// +/// The join handle provides a [`join`] method that can be used to join the +/// spawned thread. +/// +/// The default task name is an empty string. The default thread stack size is +/// [`arceos_api::config::TASK_STACK_SIZE`]. +/// +/// [`join`]: JoinHandle::join +pub fn spawn(f: F) -> JoinHandle +where + F: FnOnce() -> T + Send + 'static, + T: Send + 'static, +{ + Builder::new().spawn(f).expect("failed to spawn thread") +} + +struct Packet { + result: UnsafeCell>, +} + +unsafe impl Sync for Packet {} + +/// An owned permission to join on a thread (block on its termination). +/// +/// A `JoinHandle` *detaches* the associated thread when it is dropped, which +/// means that there is no longer any handle to the thread and no way to `join` +/// on it. +pub struct JoinHandle { + native: AxTaskHandle, + thread: Thread, + packet: Arc>, +} + +unsafe impl Send for JoinHandle {} +unsafe impl Sync for JoinHandle {} + +impl JoinHandle { + /// Extracts a handle to the underlying thread. + pub fn thread(&self) -> &Thread { + &self.thread + } + + /// Waits for the associated thread to finish. + /// + /// This function will return immediately if the associated thread has + /// already finished. + pub fn join(mut self) -> io::Result { + api::ax_wait_for_exit(self.native).ok_or_else(|| ax_err_type!(BadState))?; + Arc::get_mut(&mut self.packet) + .unwrap() + .result + .get_mut() + .take() + .ok_or_else(|| ax_err_type!(BadState)) + } +} diff --git a/ulib/axstd/src/time.rs b/ulib/axstd/src/time.rs new file mode 100644 index 000000000..3b6b5929b --- /dev/null +++ b/ulib/axstd/src/time.rs @@ -0,0 +1,97 @@ +//! Temporal quantification. + +use arceos_api::time::AxTimeValue; +use core::ops::{Add, AddAssign, Sub, SubAssign}; + +pub use core::time::Duration; + +/// A measurement of a monotonically nondecreasing clock. +/// Opaque and useful only with [`Duration`]. +#[derive(Clone, Copy)] +pub struct Instant(AxTimeValue); + +impl Instant { + /// Returns an instant corresponding to "now". + pub fn now() -> Instant { + Instant(arceos_api::time::ax_current_time()) + } + + /// Returns the amount of time elapsed from another instant to this one, + /// or zero duration if that instant is later than this one. + /// + /// # Panics + /// + /// Previous rust versions panicked when `earlier` was later than `self`. Currently this + /// method saturates. Future versions may reintroduce the panic in some circumstances. + pub fn duration_since(&self, earlier: Instant) -> Duration { + self.0.checked_sub(earlier.0).unwrap_or_default() + } + + /// Returns the amount of time elapsed since this instant was created. + /// + /// # Panics + /// + /// Previous rust versions panicked when the current time was earlier than self. Currently this + /// method returns a Duration of zero in that case. Future versions may reintroduce the panic. + pub fn elapsed(&self) -> Duration { + Instant::now() - *self + } + + /// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be represented as + /// `Instant` (which means it's inside the bounds of the underlying data structure), `None` + /// otherwise. + pub fn checked_add(&self, duration: Duration) -> Option { + self.0.checked_add(duration).map(Instant) + } + + /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be represented as + /// `Instant` (which means it's inside the bounds of the underlying data structure), `None` + /// otherwise. + pub fn checked_sub(&self, duration: Duration) -> Option { + self.0.checked_sub(duration).map(Instant) + } +} + +impl Add for Instant { + type Output = Instant; + + /// # Panics + /// + /// This function may panic if the resulting point in time cannot be represented by the + /// underlying data structure. + fn add(self, other: Duration) -> Instant { + self.checked_add(other) + .expect("overflow when adding duration to instant") + } +} + +impl AddAssign for Instant { + fn add_assign(&mut self, other: Duration) { + *self = *self + other; + } +} + +impl Sub for Instant { + type Output = Instant; + + fn sub(self, other: Duration) -> Instant { + self.checked_sub(other) + .expect("overflow when subtracting duration from instant") + } +} + +impl SubAssign for Instant { + fn sub_assign(&mut self, other: Duration) { + *self = *self - other; + } +} + +impl Sub for Instant { + type Output = Duration; + + /// Returns the amount of time elapsed from another instant to this one, + /// or zero duration if that instant is later than this one. + fn sub(self, other: Instant) -> Duration { + self.duration_since(other) + } +}

+

Hello, ArceOS

+