diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 916c19a28..fb1a97e37 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,13 +12,17 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ env.rust-toolchain }} components: rust-src, clippy, rustfmt + - name: Install gcc multilib + run: sudo apt update && sudo apt install -y gcc-multilib - name: Clippy for the default target - run: make clippy + run: make clippy - name: Clippy for x86_64 run: make clippy ARCH=x86_64 - name: Clippy for riscv64 @@ -37,6 +41,8 @@ jobs: arch: [x86_64, riscv64, aarch64] steps: - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions-rs/toolchain@v1 with: profile: minimal @@ -47,6 +53,8 @@ jobs: crate: cargo-binutils version: latest use-tool-cache: true + - name: Install gcc multilib + run: sudo apt update && sudo apt install -y gcc-multilib # musl toolchain is also needed in non-musl apps # because we need to build opensbi under riscv64 - uses: ./.github/workflows/actions/setup-musl diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index dee04f8fa..68e607319 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,10 +16,14 @@ jobs: default-branch: ${{ format('refs/heads/{0}', github.event.repository.default_branch) }} steps: - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ env.rust-toolchain }} + - name: Install gcc multilib + run: sudo apt update && sudo apt install -y gcc-multilib - name: Build docs continue-on-error: ${{ github.ref != env.default-branch && github.event_name != 'pull_request' }} run: make doc_check_missing diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2c546118f..1cdb06752 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,11 +11,13 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ env.rust-toolchain }} - components: rust-src + components: rust-src, llvm-tools-preview - name: Run unit tests run: make unittest_no_fail_fast @@ -28,16 +30,20 @@ jobs: arch: [x86_64, riscv64, aarch64] steps: - uses: actions/checkout@v3 + with: + submodules: recursive - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ env.rust-toolchain }} - components: rust-src + components: rust-src, llvm-tools-preview - uses: actions-rs/install@v0.1 with: crate: cargo-binutils version: latest use-tool-cache: true + - name: Install gcc multilib + run: sudo apt update && sudo apt install -y gcc-multilib - uses: ./.github/workflows/actions/setup-qemu with: qemu-version: ${{ env.qemu-version }} diff --git a/.gitmodules b/.gitmodules index 633fd15b6..18e97ea37 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "patches/opensbi"] path = patches/opensbi url = https://github.com/Sssssaltyfish/opensbi.git +[submodule "crates/lwip_rust/depend/lwip"] + path = crates/lwip_rust/depend/lwip + url = https://github.com/lwip-tcpip/lwip.git diff --git a/Cargo.lock b/Cargo.lock index e63a553f3..859e50f6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,12 +92,12 @@ dependencies = [ "axerrno", "axio", "axlog", - "axnet", "ruxconfig", "ruxdisplay", "ruxfeat", "ruxfs", "ruxhal", + "ruxnet", "ruxruntime", "ruxtask", ] @@ -196,24 +196,6 @@ dependencies = [ "spinlock", ] -[[package]] -name = "axnet" -version = "0.1.0" -dependencies = [ - "axerrno", - "axio", - "axsync", - "cfg-if", - "driver_net", - "lazy_init", - "log", - "ruxdriver", - "ruxhal", - "ruxtask", - "smoltcp", - "spin 0.9.8", -] - [[package]] name = "axstd" version = "0.1.0" @@ -258,6 +240,29 @@ dependencies = [ "volatile 0.2.7", ] +[[package]] +name = "bindgen" +version = "0.65.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.32", + "which", +] + [[package]] name = "bindgen" version = "0.66.1" @@ -357,6 +362,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -514,7 +520,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -535,7 +541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -593,6 +599,22 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "cstr_core" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd98742e4fdca832d40cab219dc2e3048de17d873248f83f17df47c1bea70956" +dependencies = [ + "cty", + "memchr", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + [[package]] name = "defmt" version = "0.3.5" @@ -933,6 +955,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.10.5" @@ -960,6 +991,15 @@ dependencies = [ "volatile 0.3.0", ] +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -1040,6 +1080,14 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +[[package]] +name = "lwip_rust" +version = "0.1.0" +dependencies = [ + "bindgen 0.65.1", + "cc", +] + [[package]] name = "managed" version = "0.8.0" @@ -1271,6 +1319,18 @@ dependencies = [ "syn 2.0.32", ] +[[package]] +name = "printf-compat" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b002af28ffe3d3d67202ae717810a28125a494d5396debc43de01ee136ac404" +dependencies = [ + "bitflags 1.3.2", + "cstr_core", + "cty", + "itertools 0.9.0", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1487,12 +1547,13 @@ name = "rux9p" version = "0.1.0" dependencies = [ "axfs_vfs", - "axnet", + "axsync", "driver_9p", "driver_common", "log", "ruxdriver", "ruxfs", + "ruxnet", "spin 0.9.8", ] @@ -1551,13 +1612,13 @@ version = "0.1.0" dependencies = [ "axalloc", "axlog", - "axnet", "axsync", "rux9p", "ruxdisplay", "ruxdriver", "ruxfs", "ruxhal", + "ruxnet", "ruxruntime", "ruxtask", "spinlock", @@ -1631,6 +1692,7 @@ dependencies = [ "spinlock", "static_assertions", "tock-registers", + "tty", "x2apic", "x86", "x86_64", @@ -1642,7 +1704,7 @@ version = "0.1.0" dependencies = [ "axerrno", "axio", - "bindgen", + "bindgen 0.66.1", "ruxfeat", "ruxos_posix_api", ] @@ -1661,6 +1723,28 @@ dependencies = [ "ruxos_posix_api", ] +[[package]] +name = "ruxnet" +version = "0.1.0" +dependencies = [ + "axerrno", + "axio", + "axlog", + "axsync", + "cfg-if", + "cty", + "driver_net", + "lazy_init", + "log", + "lwip_rust", + "printf-compat", + "ruxdriver", + "ruxhal", + "ruxtask", + "smoltcp", + "spin 0.9.8", +] + [[package]] name = "ruxos-display-basic-painting" version = "0.1.0" @@ -1686,14 +1770,14 @@ dependencies = [ "axerrno", "axio", "axlog", - "axnet", "axsync", - "bindgen", + "bindgen 0.66.1", "bitflags 2.4.0", "cfg-if", "crate_interface", "elf", "flatten_objects", + "lazy_init", "lazy_static", "memory_addr", "page_table", @@ -1703,6 +1787,7 @@ dependencies = [ "ruxfs", "ruxfutex", "ruxhal", + "ruxnet", "ruxruntime", "ruxtask", "spin 0.9.8", @@ -1716,7 +1801,6 @@ version = "0.1.0" dependencies = [ "axalloc", "axlog", - "axnet", "axsync", "cfg-if", "crate_interface", @@ -1731,7 +1815,9 @@ dependencies = [ "ruxfs", "ruxfutex", "ruxhal", + "ruxnet", "ruxtask", + "tty", ] [[package]] @@ -2034,6 +2120,15 @@ dependencies = [ "winnow", ] +[[package]] +name = "tty" +version = "0.0.1" +dependencies = [ + "lazy_init", + "log", + "spinlock", +] + [[package]] name = "tuple_for_each" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7c76a8fdb..c1500efeb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "crates/flatten_objects", "crates/lazy_init", "crates/linked_list", + "crates/lwip_rust", "crates/page_table", "crates/page_table_entry", "crates/percpu", @@ -33,10 +34,11 @@ members = [ "crates/spinlock", "crates/timer_list", "crates/tuple_for_each", + "crates/tty", "modules/axalloc", "modules/axlog", - "modules/axnet", + "modules/ruxnet", "modules/axsync", "modules/rux9p", "modules/ruxconfig", diff --git a/Makefile b/Makefile index 8e4cceddb..97b46532b 100644 --- a/Makefile +++ b/Makefile @@ -121,19 +121,30 @@ ifeq ($(ARCH), x86_64) ACCEL ?= $(if $(findstring -microsoft, $(shell uname -r | tr '[:upper:]' '[:lower:]')),n,y) PLATFORM_NAME ?= x86_64-qemu-q35 TARGET := x86_64-unknown-none + ifeq ($(findstring fp_simd,$(FEATURES)),) + TARGET_CFLAGS := -mno-sse + endif BUS := pci else ifeq ($(ARCH), riscv64) ACCEL ?= n PLATFORM_NAME ?= riscv64-qemu-virt TARGET := riscv64gc-unknown-none-elf + ifeq ($(findstring fp_simd,$(FEATURES)),) + TARGET_CFLAGS := -mabi=lp64d + endif else ifeq ($(ARCH), aarch64) ACCEL ?= n PLATFORM_NAME ?= aarch64-qemu-virt TARGET := aarch64-unknown-none-softfloat + ifeq ($(findstring fp_simd,$(FEATURES)),) + TARGET_CFLAGS := -mgeneral-regs-only + endif else $(error "ARCH" must be one of "x86_64", "riscv64", or "aarch64") endif +export TARGET_CC = $(CC) +export TARGET_CFLAGS export RUX_ARCH=$(ARCH) export RUX_PLATFORM=$(PLATFORM_NAME) export RUX_SMP=$(SMP) diff --git a/api/arceos_api/Cargo.toml b/api/arceos_api/Cargo.toml index 9be613762..b65cb69d9 100644 --- a/api/arceos_api/Cargo.toml +++ b/api/arceos_api/Cargo.toml @@ -18,7 +18,8 @@ alloc = ["dep:axalloc", "ruxfeat/alloc"] paging = ["alloc", "ruxfeat/paging"] multitask = ["ruxtask/multitask", "ruxfeat/multitask"] fs = ["dep:ruxfs", "ruxfeat/fs"] -net = ["dep:axnet", "ruxfeat/net"] +net = ["dep:ruxnet", "ruxfeat/net"] +lwip = ["ruxfeat/lwip"] display = ["dep:ruxdisplay", "ruxfeat/display"] myfs = ["ruxfeat/myfs"] @@ -37,5 +38,5 @@ ruxhal = { path = "../../modules/ruxhal" } axalloc = { path = "../../modules/axalloc", optional = true } ruxtask = { path = "../../modules/ruxtask", optional = true } ruxfs = { path = "../../modules/ruxfs", optional = true } -axnet = { path = "../../modules/axnet", optional = true } +ruxnet = { path = "../../modules/ruxnet", optional = true } ruxdisplay = { path = "../../modules/ruxdisplay", optional = true } diff --git a/api/arceos_api/src/imp/net.rs b/api/arceos_api/src/imp/net.rs index 16920dc7a..7cb3e5e6b 100644 --- a/api/arceos_api/src/imp/net.rs +++ b/api/arceos_api/src/imp/net.rs @@ -9,7 +9,7 @@ use crate::io::AxPollState; use axerrno::AxResult; -use axnet::{UdpSocket, TcpSocket}; +use ruxnet::{UdpSocket, TcpSocket}; use core::net::{IpAddr, SocketAddr}; /// A handle to a TCP socket. @@ -47,7 +47,7 @@ pub fn ax_tcp_bind(socket: &AxTcpSocketHandle, addr: SocketAddr) -> AxResult { socket.0.bind(addr) } -pub fn ax_tcp_listen(socket: &AxTcpSocketHandle, _backlog: usize) -> AxResult { +pub fn ax_tcp_listen(socket: &mut AxTcpSocketHandle, _backlog: usize) -> AxResult { socket.0.listen() } @@ -131,10 +131,10 @@ pub fn ax_udp_poll(socket: &AxUdpSocketHandle) -> AxResult { //////////////////////////////////////////////////////////////////////////////// pub fn ax_dns_query(domain_name: &str) -> AxResult> { - axnet::dns_query(domain_name) + ruxnet::dns_query(domain_name) } pub fn ax_poll_interfaces() -> AxResult { - axnet::poll_interfaces(); + ruxnet::poll_interfaces(); Ok(()) } diff --git a/api/arceos_api/src/lib.rs b/api/arceos_api/src/lib.rs index dc829d4b2..0e2e0e1f6 100644 --- a/api/arceos_api/src/lib.rs +++ b/api/arceos_api/src/lib.rs @@ -262,7 +262,7 @@ pub mod net { /// 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; + pub fn ax_tcp_listen(socket: &mut AxTcpSocketHandle, _backlog: usize) -> AxResult; /// Accepts a new connection on the TCP socket. /// /// This function will block the calling thread until a new TCP connection diff --git a/api/ruxfeat/Cargo.toml b/api/ruxfeat/Cargo.toml index f4aaf4d56..7926cee2f 100644 --- a/api/ruxfeat/Cargo.toml +++ b/api/ruxfeat/Cargo.toml @@ -27,7 +27,7 @@ irq = ["ruxhal/irq", "ruxruntime/irq", "ruxtask?/irq"] rtc = ["ruxhal/rtc", "ruxruntime/rtc"] # Memory -alloc = ["axalloc", "ruxruntime/alloc", "ruxfs/alloc"] +alloc = ["axalloc", "ruxruntime/alloc", "ruxfs/alloc", "ruxhal/alloc"] alloc-tlsf = ["axalloc/tlsf"] alloc-slab = ["axalloc/slab"] alloc-buddy = ["axalloc/buddy"] @@ -35,25 +35,42 @@ paging = ["alloc", "ruxhal/paging", "ruxruntime/paging"] tls = ["alloc", "ruxhal/tls", "ruxruntime/tls", "ruxtask?/tls"] # Multi-threading and scheduler -multitask = ["alloc", "ruxtask/multitask", "axsync/multitask", "ruxruntime/multitask"] +multitask = [ + "alloc", + "ruxtask/multitask", + "axsync/multitask", + "ruxruntime/multitask", +] sched_fifo = ["ruxtask/sched_fifo"] sched_rr = ["ruxtask/sched_rr", "irq"] sched_cfs = ["ruxtask/sched_cfs", "irq"] # File system -fs = ["alloc", "dep:ruxfs", "ruxruntime/fs"] +fs = ["alloc", "dep:ruxfs", "ruxruntime/fs"] blkfs = ["ruxdriver/virtio-blk", "ruxruntime/blkfs"] myfs = ["ruxfs?/myfs"] 9pfs = [] # Networking -net = ["alloc", "ruxdriver/virtio-net", "dep:axnet", "ruxruntime/net"] +net = ["alloc", "ruxdriver/virtio-net", "dep:ruxnet", "ruxruntime/net"] +lwip = ["ruxnet/lwip"] +smoltcp = ["ruxnet/smoltcp"] # Display -display = ["alloc", "ruxdriver/virtio-gpu", "dep:ruxdisplay", "ruxruntime/display"] +display = [ + "alloc", + "ruxdriver/virtio-gpu", + "dep:ruxdisplay", + "ruxruntime/display", +] # 9P -virtio-9p = ["9pfs", "ruxdriver/virtio-9p", "rux9p/virtio-9p", "ruxruntime/virtio-9p"] +virtio-9p = [ + "9pfs", + "ruxdriver/virtio-9p", + "rux9p/virtio-9p", + "ruxruntime/virtio-9p", +] net-9p = ["9pfs", "net", "rux9p/net-9p", "ruxruntime/net-9p"] # Device drivers @@ -71,6 +88,8 @@ log-level-info = ["axlog/log-level-info"] log-level-debug = ["axlog/log-level-debug"] log-level-trace = ["axlog/log-level-trace"] +tty = ["ruxhal/tty", "ruxruntime/tty", "alloc", "irq"] + [dependencies] ruxruntime = { path = "../../modules/ruxruntime" } ruxhal = { path = "../../modules/ruxhal" } @@ -79,7 +98,7 @@ axalloc = { path = "../../modules/axalloc", optional = true } ruxdriver = { path = "../../modules/ruxdriver", optional = true } ruxfs = { path = "../../modules/ruxfs", optional = true } rux9p = { path = "../../modules/rux9p", optional = true } -axnet = { path = "../../modules/axnet", optional = true } +ruxnet = { path = "../../modules/ruxnet", optional = true } ruxdisplay = { path = "../../modules/ruxdisplay", optional = true } axsync = { path = "../../modules/axsync", optional = true } ruxtask = { path = "../../modules/ruxtask", optional = true } diff --git a/api/ruxos_posix_api/Cargo.toml b/api/ruxos_posix_api/Cargo.toml index 937f83474..6ae5c1cea 100644 --- a/api/ruxos_posix_api/Cargo.toml +++ b/api/ruxos_posix_api/Cargo.toml @@ -23,7 +23,7 @@ paging = ["alloc", "ruxfeat/paging"] multitask = ["ruxfeat/multitask", "ruxtask/multitask", "dep:ruxfutex"] fd = ["alloc"] fs = ["dep:ruxfs", "ruxfeat/fs", "fd"] -net = ["dep:axnet", "ruxfeat/net", "fd"] +net = ["dep:ruxnet", "ruxfeat/net", "fd"] signal = ["ruxruntime/signal"] pipe = ["fd"] select = ["fd"] @@ -48,7 +48,7 @@ ruxfutex = { path = "../../modules/ruxfutex", optional = true } axalloc = { path = "../../modules/axalloc", optional = true } ruxtask = { path = "../../modules/ruxtask", optional = true } ruxfs = { path = "../../modules/ruxfs", optional = true } -axnet = { path = "../../modules/axnet", optional = true } +ruxnet = { path = "../../modules/ruxnet", optional = true } # Other crates axio = { path = "../../crates/axio" } @@ -66,5 +66,7 @@ cfg-if = "1.0" elf = { version = "0.7", default-features = false } bitflags = "2.2" +lazy_init = { path = "../../crates/lazy_init" } + [build-dependencies] bindgen = { version = "0.66" } diff --git a/api/ruxos_posix_api/build.rs b/api/ruxos_posix_api/build.rs index fe71136be..a5b17661b 100644 --- a/api/ruxos_posix_api/build.rs +++ b/api/ruxos_posix_api/build.rs @@ -129,6 +129,7 @@ typedef struct {{ "PROT_.+", "MS_.+", "MREMAP_.+", + "GRND_.*", ]; #[derive(Debug)] diff --git a/api/ruxos_posix_api/ctypes.h b/api/ruxos_posix_api/ctypes.h index 857e547b9..6298ce6c3 100644 --- a/api/ruxos_posix_api/ctypes.h +++ b/api/ruxos_posix_api/ctypes.h @@ -31,3 +31,5 @@ #include #include #include + +#include \ No newline at end of file diff --git a/api/ruxos_posix_api/src/imp/fs.rs b/api/ruxos_posix_api/src/imp/fs.rs index 5c55e2e18..89019d865 100644 --- a/api/ruxos_posix_api/src/imp/fs.rs +++ b/api/ruxos_posix_api/src/imp/fs.rs @@ -7,8 +7,8 @@ * See the Mulan PSL v2 for more details. */ -use alloc::sync::Arc; -use core::ffi::{c_char, c_int, c_long, c_void}; +use alloc::{borrow::Cow, string::String, sync::Arc}; +use core::ffi::{c_char, c_int, c_long, c_void, CStr}; use axerrno::{LinuxError, LinuxResult}; use axio::{PollState, SeekFrom}; @@ -20,7 +20,7 @@ use ruxfs::{ }; use super::fd_ops::get_file_like; -use crate::{ctypes, utils::char_ptr_to_str}; +use crate::ctypes; use alloc::vec::Vec; pub struct File { @@ -203,40 +203,40 @@ fn flags_to_options(flags: c_int, _mode: ctypes::mode_t) -> OpenOptions { /// 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); + let filename = char_ptr_to_absolute_path(filename); debug!("sys_open <= {:?} {:#o} {:#o}", filename, flags, mode); syscall_body!(sys_open, { let options = flags_to_options(flags, mode); - let file = ruxfs::fops::File::open(filename?, &options)?; + let file = ruxfs::fops::File::open(&filename?, &options)?; File::new(file).add_to_fd_table() }) } /// Open a file under a specific dir pub fn sys_openat(fd: usize, path: *const c_char, flags: c_int, mode: ctypes::mode_t) -> c_int { - let path = char_ptr_to_str(path); + let path = char_ptr_to_absolute_path(path); let fd: c_int = fd as c_int; debug!("sys_openat <= {}, {:?}, {:#o} {:#o}", fd, path, flags, mode); syscall_body!(sys_openat, { let options = flags_to_options(flags, mode); if (flags as u32) & ctypes::O_DIRECTORY != 0 { let dir = if fd == ctypes::AT_FDCWD { - ruxfs::fops::Directory::open_dir(path?, &options)? + ruxfs::fops::Directory::open_dir(&path?, &options)? } else { Directory::from_fd(fd)? .inner .lock() - .open_dir_at(path?, &options)? + .open_dir_at(&path?, &options)? }; Directory::new(dir).add_to_fd_table() } else { let file = if fd == ctypes::AT_FDCWD { - ruxfs::fops::File::open(path?, &options)? + ruxfs::fops::File::open(&path?, &options)? } else { Directory::from_fd(fd)? .inner .lock() - .open_file_at(path?, &options)? + .open_file_at(&path?, &options)? }; File::new(file).add_to_fd_table() } @@ -320,7 +320,7 @@ pub unsafe fn sys_fdatasync(fd: c_int) -> c_int { /// /// Return 0 if success. pub unsafe fn sys_stat(path: *const c_char, buf: *mut core::ffi::c_void) -> c_int { - let path = char_ptr_to_str(path); + let path = char_ptr_to_absolute_path(path); debug!("sys_stat <= {:?} {:#x}", path, buf as usize); syscall_body!(sys_stat, { if buf.is_null() { @@ -328,7 +328,7 @@ pub unsafe fn sys_stat(path: *const c_char, buf: *mut core::ffi::c_void) -> c_in } let mut options = OpenOptions::new(); options.read(true); - let file = ruxfs::fops::File::open(path?, &options)?; + let file = ruxfs::fops::File::open(&path?, &options)?; let st: ctypes::stat = File::new(file).stat()?.into(); #[cfg(not(feature = "musl"))] @@ -401,7 +401,7 @@ pub fn sys_fstat(fd: c_int, kst: *mut core::ffi::c_void) -> c_int { /// /// 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); + let path = char_ptr_to_absolute_path(path); debug!("sys_lstat <= {:?} {:#x}", path, buf as usize); syscall_body!(sys_lstat, { if buf.is_null() { @@ -419,7 +419,7 @@ pub unsafe fn sys_newfstatat( kst: *mut ctypes::kstat, flag: c_int, ) -> c_int { - let path = char_ptr_to_str(path); + let path = char_ptr_to_absolute_path(path); debug!( "sys_newfstatat <= fd: {}, path: {:?}, flag: {:x}", _fd, path, flag @@ -430,7 +430,7 @@ pub unsafe fn sys_newfstatat( } let mut options = OpenOptions::new(); options.read(true); - let file = ruxfs::fops::File::open(path?, &options)?; + let file = ruxfs::fops::File::open(&path?, &options)?; let st = File::new(file).stat()?; unsafe { (*kst).st_dev = st.st_dev; @@ -473,10 +473,10 @@ pub fn sys_getcwd(buf: *mut c_char, size: usize) -> c_int { /// 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)?; + let old_path = char_ptr_to_absolute_path(old)?; + let new_path = char_ptr_to_absolute_path(new)?; debug!("sys_rename <= old: {:?}, new: {:?}", old_path, new_path); - ruxfs::api::rename(old_path, new_path)?; + ruxfs::api::rename(&old_path, &new_path)?; Ok(0) }) } @@ -485,8 +485,8 @@ pub fn sys_rename(old: *const c_char, new: *const c_char) -> c_int { /// /// TODO: only support `oldfd`, `newfd` equals to AT_FDCWD pub fn sys_renameat(oldfd: c_int, old: *const c_char, newfd: c_int, new: *const c_char) -> c_int { - let old_path = char_ptr_to_str(old); - let new_path = char_ptr_to_str(new); + let old_path = char_ptr_to_absolute_path(old); + let new_path = char_ptr_to_absolute_path(new); debug!( "sys_renameat <= oldfd: {}, old: {:?}, newfd: {}, new: {:?}", oldfd, old_path, newfd, new_path @@ -494,7 +494,7 @@ pub fn sys_renameat(oldfd: c_int, old: *const c_char, newfd: c_int, new: *const assert_eq!(oldfd, ctypes::AT_FDCWD as c_int); assert_eq!(newfd, ctypes::AT_FDCWD as c_int); syscall_body!(sys_renameat, { - ruxfs::api::rename(old_path?, new_path?)?; + ruxfs::api::rename(&old_path?, &new_path?)?; Ok(0) }) } @@ -502,9 +502,9 @@ pub fn sys_renameat(oldfd: c_int, old: *const c_char, newfd: c_int, new: *const /// Remove a directory, which must be empty pub fn sys_rmdir(pathname: *const c_char) -> c_int { syscall_body!(sys_rmdir, { - let path = char_ptr_to_str(pathname)?; + let path = char_ptr_to_absolute_path(pathname)?; debug!("sys_rmdir <= path: {:?}", path); - ruxfs::api::remove_dir(path)?; + ruxfs::api::remove_dir(&path)?; Ok(0) }) } @@ -512,9 +512,9 @@ pub fn sys_rmdir(pathname: *const c_char) -> c_int { /// Removes a file from the filesystem. pub fn sys_unlink(pathname: *const c_char) -> c_int { syscall_body!(sys_unlink, { - let path = char_ptr_to_str(pathname)?; + let path = char_ptr_to_absolute_path(pathname)?; debug!("sys_unlink <= path: {:?}", path); - ruxfs::api::remove_file(path)?; + ruxfs::api::remove_file(&path)?; Ok(0) }) } @@ -524,7 +524,7 @@ pub fn sys_unlinkat(fd: c_int, pathname: *const c_char, flags: c_int) -> c_int { debug!( "sys_unlinkat <= fd: {}, pathname: {:?}, flags: {}", fd, - char_ptr_to_str(pathname), + char_ptr_to_absolute_path(pathname), flags ); if flags as u32 & ctypes::AT_REMOVEDIR != 0 { @@ -537,9 +537,9 @@ pub fn sys_unlinkat(fd: c_int, pathname: *const c_char, flags: c_int) -> c_int { pub fn sys_mkdir(pathname: *const c_char, mode: ctypes::mode_t) -> c_int { // TODO: implement mode syscall_body!(sys_mkdir, { - let path = char_ptr_to_str(pathname)?; + let path = char_ptr_to_absolute_path(pathname)?; debug!("sys_mkdir <= path: {:?}, mode: {:?}", path, mode); - ruxfs::api::create_dir(path)?; + ruxfs::api::create_dir(&path)?; Ok(0) }) } @@ -551,7 +551,7 @@ pub fn sys_mkdirat(fd: c_int, pathname: *const c_char, mode: ctypes::mode_t) -> debug!( "sys_mkdirat <= fd: {}, pathname: {:?}, mode: {:x?}", fd, - char_ptr_to_str(pathname), + char_ptr_to_absolute_path(pathname), mode ); sys_mkdir(pathname, mode) @@ -568,7 +568,7 @@ pub fn sys_fchownat( debug!( "sys_fchownat <= fd: {}, path: {:?}, uid: {}, gid: {}, flag: {}", fd, - char_ptr_to_str(path), + char_ptr_to_absolute_path(path), uid, gid, flag @@ -584,7 +584,7 @@ pub fn sys_readlinkat( buf: *mut c_char, bufsize: usize, ) -> usize { - let path = char_ptr_to_str(pathname); + let path = char_ptr_to_absolute_path(pathname); debug!( "sys_readlinkat <= path = {:?}, fd = {:}, buf = {:p}, bufsize = {:}", path, fd, buf, bufsize @@ -682,7 +682,7 @@ pub unsafe fn sys_preadv( /// The mode is either the value F_OK, for the existence of the file, /// or a mask consisting of the bitwise OR of one or more of R_OK, W_OK, and X_OK, for the read, write, execute permissions. pub fn sys_faccessat(dirfd: c_int, pathname: *const c_char, mode: c_int, flags: c_int) -> c_int { - let path = char_ptr_to_str(pathname).unwrap(); + let path = char_ptr_to_absolute_path(pathname).unwrap(); debug!( "sys_faccessat <= dirfd {} path {} mode {} flags {}", dirfd, path, mode, flags @@ -690,17 +690,49 @@ pub fn sys_faccessat(dirfd: c_int, pathname: *const c_char, mode: c_int, flags: syscall_body!(sys_faccessat, { let mut options = OpenOptions::new(); options.read(true); - let _file = ruxfs::fops::File::open(path, &options)?; + let _file = ruxfs::fops::File::open(&path, &options)?; Ok(0) }) } /// changes the current working directory to the directory specified in path. pub fn sys_chdir(path: *const c_char) -> c_int { - let p = char_ptr_to_str(path).unwrap(); + let p = char_ptr_to_absolute_path(path).unwrap(); debug!("sys_chdir <= path: {}", p); syscall_body!(sys_chdir, { - set_current_dir(p)?; + set_current_dir(&p)?; Ok(0) }) } + +/// from char_ptr get absolute_path_str +pub fn char_ptr_to_absolute_path<'a>(ptr: *const c_char) -> LinuxResult> { + if ptr.is_null() { + return Err(LinuxError::EFAULT); + } + + let path = unsafe { + let cstr = CStr::from_ptr(ptr); + cstr.to_str().map_err(|_| LinuxError::EINVAL)? + }; + + if path.starts_with("..") { + let stripped = path.strip_prefix("..").unwrap(); + let mut cwd = ruxfs::api::current_dir()?; + if let Some(index) = cwd.rfind('/') { + cwd.truncate(index); + if let Some(index) = cwd.rfind('/') { + cwd.truncate(index); + } + } + let absolute_path: String = cwd + stripped; + Ok(Cow::Owned(absolute_path)) + } else if path.starts_with('.') { + let stripped = path.strip_prefix('.').unwrap(); + let cwd = ruxfs::api::current_dir()?; + let absolute_path: String = cwd + stripped; + Ok(Cow::Owned(absolute_path)) + } else { + Ok(Cow::Borrowed(path)) + } +} diff --git a/api/ruxos_posix_api/src/imp/getrandom.rs b/api/ruxos_posix_api/src/imp/getrandom.rs index 8187bccc9..61cafb103 100644 --- a/api/ruxos_posix_api/src/imp/getrandom.rs +++ b/api/ruxos_posix_api/src/imp/getrandom.rs @@ -157,8 +157,11 @@ pub unsafe extern "C" fn sys_getrandom(buf: *mut c_void, buflen: size_t, flags: if buf.is_null() { return Err(LinuxError::EFAULT); } - if flags != 0 { - return Err(LinuxError::EINVAL); + + match flags as _ { + crate::ctypes::GRND_NONBLOCK => {} + crate::ctypes::GRND_RANDOM => {} + _ => return Err(LinuxError::EINVAL), } // fill the buffer 8 bytes at a time first, then fill the remaining bytes let buflen_mod = buflen % (core::mem::size_of::() / core::mem::size_of::()); diff --git a/api/ruxos_posix_api/src/imp/io_mpx/epoll.rs b/api/ruxos_posix_api/src/imp/io_mpx/epoll.rs index 570b7a192..12bc3b16e 100644 --- a/api/ruxos_posix_api/src/imp/io_mpx/epoll.rs +++ b/api/ruxos_posix_api/src/imp/io_mpx/epoll.rs @@ -219,8 +219,7 @@ pub unsafe fn sys_epoll_wait( let epoll_instance = EpollInstance::from_fd(epfd)?; loop { #[cfg(feature = "net")] - axnet::poll_interfaces(); - + ruxnet::poll_interfaces(); let poll_all_res = epoll_instance.poll_all(events); let mut events_num = 0; match poll_all_res { @@ -242,7 +241,6 @@ pub unsafe fn sys_epoll_wait( } Err(_) => {} } - if events_num > 0 { return Ok(events_num as c_int); } diff --git a/api/ruxos_posix_api/src/imp/io_mpx/poll.rs b/api/ruxos_posix_api/src/imp/io_mpx/poll.rs index 595639ef3..d9aa4ff70 100644 --- a/api/ruxos_posix_api/src/imp/io_mpx/poll.rs +++ b/api/ruxos_posix_api/src/imp/io_mpx/poll.rs @@ -73,7 +73,7 @@ pub unsafe fn sys_poll(fds: *mut ctypes::pollfd, nfds: ctypes::nfds_t, timeout: .then(|| current_time() + Duration::from_millis(timeout as u64)); loop { #[cfg(feature = "net")] - axnet::poll_interfaces(); + ruxnet::poll_interfaces(); let fds_num = poll_all(fds)?; if fds_num > 0 { return Ok(fds_num as c_int); diff --git a/api/ruxos_posix_api/src/imp/io_mpx/select.rs b/api/ruxos_posix_api/src/imp/io_mpx/select.rs index 2631e7f1f..f8b9f383f 100644 --- a/api/ruxos_posix_api/src/imp/io_mpx/select.rs +++ b/api/ruxos_posix_api/src/imp/io_mpx/select.rs @@ -144,7 +144,7 @@ pub unsafe fn sys_select( loop { #[cfg(feature = "net")] - axnet::poll_interfaces(); + ruxnet::poll_interfaces(); let res = fd_sets.poll_all(readfds, writefds, exceptfds)?; if res > 0 { return Ok(res); diff --git a/api/ruxos_posix_api/src/imp/ioctl.rs b/api/ruxos_posix_api/src/imp/ioctl.rs index 245a3d358..4838772c9 100644 --- a/api/ruxos_posix_api/src/imp/ioctl.rs +++ b/api/ruxos_posix_api/src/imp/ioctl.rs @@ -7,7 +7,7 @@ * See the Mulan PSL v2 for more details. */ -use crate::imp::fd_ops::get_file_like; +use crate::{imp::fd_ops::get_file_like, sys_getpgid}; use axerrno::LinuxError; use core::ffi::c_int; @@ -46,14 +46,18 @@ pub fn sys_ioctl(fd: c_int, request: usize, data: usize) -> c_int { } Ok(0) } - TCGETS | TIOCSPGRP => { + TCGETS => { + debug!("sys_ioctl: tty TCGETS"); + Ok(0) + } + TIOCSPGRP => { warn!("stdout pretend to be tty"); Ok(0) } TIOCGPGRP => { warn!("stdout TIOCGPGRP, pretend to be have a tty process group."); unsafe { - *(data as *mut u32) = 0; + *(data as *mut u32) = sys_getpgid(0) as _; } Ok(0) } diff --git a/api/ruxos_posix_api/src/imp/mmap/api.rs b/api/ruxos_posix_api/src/imp/mmap/api.rs index 97b76c22a..9a671d160 100644 --- a/api/ruxos_posix_api/src/imp/mmap/api.rs +++ b/api/ruxos_posix_api/src/imp/mmap/api.rs @@ -46,14 +46,6 @@ pub fn sys_mmap( syscall_body!(sys_mmap, { // transform C-type into rust-type let start = start as usize; - let len = VirtAddr::from(len).align_up_4k().as_usize(); - if !VirtAddr::from(start).is_aligned(PAGE_SIZE_4K) || len == 0 { - error!( - "mmap failed because start:0x{:x} is not aligned or len:0x{:x} == 0", - start, len - ); - return Err(LinuxError::EINVAL); - } let prot = prot as u32; let flags = flags as u32; let fid = fd; @@ -75,6 +67,22 @@ pub fn sys_mmap( fid }; + // align len to PAGE_SIZE_4K depending on `MAP_ANONYMOUS` or not. + let len = if fid < 0 { + VirtAddr::from(len).align_up_4k().as_usize() + } else { + VirtAddr::from(len).as_usize() + }; + + // check if `start` is aligned to `PAGE_SIZE_4K`or len is large than 0. + if !VirtAddr::from(start).is_aligned(PAGE_SIZE_4K) || len == 0 { + error!( + "mmap failed because start:0x{:x} is not aligned or len:0x{:x} == 0", + start, len + ); + return Err(LinuxError::EINVAL); + } + let mut new = Vma::new(fid, offset, prot, flags); let mut vma_map = VMA_MAP.lock(); let addr_condition = if start == 0 { None } else { Some(start) }; @@ -103,7 +111,7 @@ pub fn sys_munmap(start: *mut c_void, len: ctypes::size_t) -> c_int { syscall_body!(sys_munmap, { // transform C-type into rust-type let start = start as usize; - let end = VirtAddr::from(start + len).align_up_4k().as_usize(); + let end = VirtAddr::from(start + len).as_usize(); if !VirtAddr::from(start).is_aligned(PAGE_SIZE_4K) || len == 0 { error!( @@ -171,7 +179,7 @@ pub fn sys_munmap(start: *mut c_void, len: ctypes::size_t) -> c_int { } // delete the mapped and swapped page. - release_pages_mapped(start, end); + release_pages_mapped(start, end, true); #[cfg(feature = "fs")] release_pages_swaped(start, end); @@ -191,7 +199,7 @@ pub fn sys_mprotect(start: *mut c_void, len: ctypes::size_t, prot: c_int) -> c_i syscall_body!(sys_mprotect, { // transform C-type into rust-type let start = start as usize; - let end = VirtAddr::from(start + len).align_up_4k().as_usize(); + let end = VirtAddr::from(start + len).as_usize(); if !VirtAddr::from(start).is_aligned(PAGE_SIZE_4K) || len == 0 { return Err(LinuxError::EINVAL); } @@ -293,7 +301,7 @@ pub fn sys_msync(start: *mut c_void, len: ctypes::size_t, flags: c_int) -> c_int #[cfg(feature = "fs")] { let start = start as usize; - let end = VirtAddr::from(start + len).align_up_4k().as_usize(); + let end = VirtAddr::from(start + len).as_usize(); if !VirtAddr::from(start).is_aligned(PAGE_SIZE_4K) || len == 0 { return Err(LinuxError::EINVAL); } @@ -444,7 +452,7 @@ pub fn sys_mremap( old_vma.end_addr = new_end; // delete the mapped and swapped page outside of new vma. - release_pages_mapped(new_end, old_end); + release_pages_mapped(new_end, old_end, false); #[cfg(feature = "fs")] release_pages_swaped(new_end, old_end); diff --git a/api/ruxos_posix_api/src/imp/mmap/utils.rs b/api/ruxos_posix_api/src/imp/mmap/utils.rs index ab97573f6..ff4729a67 100644 --- a/api/ruxos_posix_api/src/imp/mmap/utils.rs +++ b/api/ruxos_posix_api/src/imp/mmap/utils.rs @@ -28,8 +28,8 @@ use ruxhal::{ // use `used_fs` instead of `#[cfg(feature = "fs")]{}` to cancel the scope of code. #[cfg(feature = "fs")] macro_rules! used_fs { - ($($code:tt)*) => {$($code)*}; -} + ($($code:tt)*) => {$($code)*}; + } #[cfg(not(feature = "fs"))] macro_rules! used_fs { @@ -204,9 +204,11 @@ pub(crate) fn find_free_region( } // Search free region on the top of VMA_LISTS first. + const ALIGN_PAGE_MASK: usize = !(PAGE_SIZE_4K - 1); if let Some((_, last_vma)) = vma_map.last_key_value() { - if VMA_END - last_vma.end_addr >= len { - return Some(last_vma.end_addr); + let end_boundry = (last_vma.end_addr + PAGE_SIZE_4K) & ALIGN_PAGE_MASK; + if (VMA_END - end_boundry) & ALIGN_PAGE_MASK >= len { + return Some(end_boundry); } } else if VMA_END >= VMA_START + len { return Some(VMA_START); @@ -274,7 +276,7 @@ pub(crate) fn snatch_fixed_region( } // delete the mapped and swapped page. - release_pages_mapped(start, end); + release_pages_mapped(start, end, true); #[cfg(feature = "fs")] release_pages_swaped(start, end); @@ -283,14 +285,16 @@ pub(crate) fn snatch_fixed_region( /// release the range of [start, end) in mem_map /// take care of AA-deadlock, this function should not be used after `MEM_MAP` is used. -pub(crate) fn release_pages_mapped(start: usize, end: usize) { +pub(crate) fn release_pages_mapped(start: usize, end: usize, writeback: bool) { let mut memory_map = MEM_MAP.lock(); let mut removing_vaddr = Vec::new(); for (&vaddr, _page_info) in memory_map.range(start..end) { #[cfg(feature = "fs")] - if let Some((file, offset, size)) = _page_info { - let src = vaddr as *mut u8; - write_into(file, src, *offset as u64, *size); + if writeback { + if let Some((file, offset, size)) = _page_info { + let src = vaddr as *mut u8; + write_into(file, src, *offset as u64, *size); + } } if pte_unmap_page(VirtAddr::from(vaddr)).is_err() { panic!("Release page failed when munmapping!"); diff --git a/api/ruxos_posix_api/src/imp/net.rs b/api/ruxos_posix_api/src/imp/net.rs index 144f1c80c..ef8465aba 100644 --- a/api/ruxos_posix_api/src/imp/net.rs +++ b/api/ruxos_posix_api/src/imp/net.rs @@ -14,9 +14,9 @@ use core::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; use axerrno::{LinuxError, LinuxResult}; use axio::PollState; -use axnet::{TcpSocket, UdpSocket}; use axsync::Mutex; use ruxfdtable::{FileLike, RuxStat}; +use ruxnet::{TcpSocket, UdpSocket}; use crate::ctypes; use crate::utils::char_ptr_to_str; @@ -514,7 +514,7 @@ pub unsafe fn sys_getaddrinfo( if let Ok(a) = domain.parse::() { vec![a] } else { - axnet::dns_query(domain)? + ruxnet::dns_query(domain)? } } else { vec![Ipv4Addr::LOCALHOST.into()] diff --git a/api/ruxos_posix_api/src/imp/stdio.rs b/api/ruxos_posix_api/src/imp/stdio.rs index 1a87669c7..73af31e31 100644 --- a/api/ruxos_posix_api/src/imp/stdio.rs +++ b/api/ruxos_posix_api/src/imp/stdio.rs @@ -19,41 +19,37 @@ use { core::sync::atomic::{AtomicBool, Ordering}, }; -fn console_read_bytes() -> Option { - let ret = ruxhal::console::getchar().map(|c| if c == b'\r' { b'\n' } else { c }); - if let Some(c) = ret { - let _ = console_write_bytes(&[c]); - } - ret -} - -fn console_write_bytes(buf: &[u8]) -> AxResult { - ruxhal::console::write_bytes(buf); - Ok(buf.len()) -} - struct StdinRaw; struct StdoutRaw; +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "alloc")] +static STDIO_TTY_NAME: lazy_init::LazyInit = lazy_init::LazyInit::new(); +#[cfg(not(feature = "alloc"))] +static STDIO_TTY_NAME: &str = "dummy"; + +fn get_stdio_tty_name() -> &'static str { + #[cfg(feature = "alloc")] + { + if !STDIO_TTY_NAME.is_init() { + let name = ruxhal::get_all_device_names().first().unwrap().clone(); + STDIO_TTY_NAME.init_by(name); + } + } + &STDIO_TTY_NAME +} + 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) + Ok(ruxhal::tty_read(buf, get_stdio_tty_name())) } } impl Write for StdoutRaw { fn write(&mut self, buf: &[u8]) -> AxResult { - console_write_bytes(buf) + Ok(ruxhal::tty_write(buf, get_stdio_tty_name())) } fn flush(&mut self) -> AxResult { diff --git a/apps/c/busybox/.gitignore b/apps/c/busybox/.gitignore new file mode 100644 index 000000000..e3ce33a96 --- /dev/null +++ b/apps/c/busybox/.gitignore @@ -0,0 +1,10 @@ +ruxgo_bld +compile_commands.json +.cache +/rootfs/bin/* +/rootfs/lib/* +/rootfs/dev +/rootfs/etc +/rootfs/proc +/rootfs/sys +/rootfs/tmp diff --git a/apps/c/busybox/README.md b/apps/c/busybox/README.md new file mode 100644 index 000000000..570cf1646 --- /dev/null +++ b/apps/c/busybox/README.md @@ -0,0 +1,24 @@ +# busybox + +## Quick Start + +1. Compile `busybox` or get its ELF binary (using Musl), then copy to `rootfs/bin`. + +2. Copy the Musl dyanmic linker to `rootfs/lib`. + +3. modify `axbuild.mk`, like: + +```makefile +app-objs=main.o + +ARGS = /bin/busybox,ls +ENVS = +V9P_PATH=${APP}/rootfs +``` + +4. Run + +```sh +# in the RuxOS main directory. +make run ARCH=aarch64 A=apps/c/busybox V9P=y MUSL=y +``` diff --git a/apps/c/busybox/axbuild.mk b/apps/c/busybox/axbuild.mk new file mode 100644 index 000000000..433b3b546 --- /dev/null +++ b/apps/c/busybox/axbuild.mk @@ -0,0 +1,5 @@ +app-objs=main.o + +ARGS = /bin/busybox,sh +ENVS = +V9P_PATH=${APP}/rootfs \ No newline at end of file diff --git a/apps/c/busybox/config_linux.toml b/apps/c/busybox/config_linux.toml new file mode 100644 index 000000000..61ba40629 --- /dev/null +++ b/apps/c/busybox/config_linux.toml @@ -0,0 +1,34 @@ +[build] +compiler = "gcc" +app = "./busybox" + +[os] +name = "ruxos" +services = [ + "alloc", + "paging", + "musl", + "multitask", + "fs", + "pipe", + "poll", + "rtc", + "virtio-9p", + "irq", + "signal", +] +ulib = "ruxmusl" +develop = "y" + +[os.platform] +name = "aarch64-qemu-virt" +mode = "release" +log = "debug" + + +[os.platform.qemu] +memory = "2g" +v9p = "y" +v9p_path = "./rootfs" +args = "/bin/busybox,sh" +qemu_log = "y" diff --git a/apps/c/busybox/features.txt b/apps/c/busybox/features.txt new file mode 100644 index 000000000..0b7506227 --- /dev/null +++ b/apps/c/busybox/features.txt @@ -0,0 +1,11 @@ +paging +alloc +irq +musl +multitask +fs +pipe +poll +rtc +signal +virtio-9p \ No newline at end of file diff --git a/apps/c/busybox/main.c b/apps/c/busybox/main.c new file mode 100644 index 000000000..cb479e391 --- /dev/null +++ b/apps/c/busybox/main.c @@ -0,0 +1,7 @@ +#include +#include + +int main(int argc, char** argv, char**envp) { + execv(argv[0], argv); + return 0; +} \ No newline at end of file diff --git a/apps/c/cpp/.gitignore b/apps/c/cpp_benchmark/.gitignore similarity index 100% rename from apps/c/cpp/.gitignore rename to apps/c/cpp_benchmark/.gitignore diff --git a/apps/c/cpp/axbuild.mk b/apps/c/cpp_benchmark/axbuild.mk similarity index 100% rename from apps/c/cpp/axbuild.mk rename to apps/c/cpp_benchmark/axbuild.mk diff --git a/apps/c/cpp/features.txt b/apps/c/cpp_benchmark/features.txt similarity index 100% rename from apps/c/cpp/features.txt rename to apps/c/cpp_benchmark/features.txt diff --git a/apps/c/cpp/std_benchmark.patch b/apps/c/cpp_benchmark/std_benchmark.patch similarity index 100% rename from apps/c/cpp/std_benchmark.patch rename to apps/c/cpp_benchmark/std_benchmark.patch diff --git a/apps/c/cpp_helloworld/axbuild.mk b/apps/c/cpp_helloworld/axbuild.mk new file mode 100644 index 000000000..402aefbec --- /dev/null +++ b/apps/c/cpp_helloworld/axbuild.mk @@ -0,0 +1,20 @@ +ARCH ?= x86_64 +C_COMPILER := $(shell which $(CC)) +CXX_COMPILER := $(shell which $(CC)) +AR := $(shell which $(AR)) +RANLIB := $(shell which $(RANLIB)) +CROSS_COMPILE_PATH := $(shell dirname $(C_COMPILER))/.. + +app-objs := main.o + +$(APP)/$(app-objs): $(APP)/axbuild.mk + $(CXX_COMPILER) -c -o $(app-objs) $(APP)/main.cpp + $(LD) -o $(app-objs) -nostdlib -static -no-pie -r -e main \ + $(app-objs) \ + $(CROSS_COMPILE_PATH)/*-linux-musl/lib/libstdc++.a \ + $(CROSS_COMPILE_PATH)/lib/gcc/*-linux-musl/*/libgcc_eh.a + +clean_c:: + rm -rf $(APP)/$(app-objs) + +.PHONY: $(APP)/$(app-objs) clean_c diff --git a/apps/c/cpp_helloworld/features.txt b/apps/c/cpp_helloworld/features.txt new file mode 100644 index 000000000..662a64968 --- /dev/null +++ b/apps/c/cpp_helloworld/features.txt @@ -0,0 +1,6 @@ +alloc +paging +irq +multitask +fs +random-hw diff --git a/apps/c/cpp_helloworld/main.cpp b/apps/c/cpp_helloworld/main.cpp new file mode 100644 index 000000000..9a69aca63 --- /dev/null +++ b/apps/c/cpp_helloworld/main.cpp @@ -0,0 +1,12 @@ +#include + +using namespace std; + +// This is a dummy function to avoid linker errors as this example should be a static executable. +void* __dso_handle = NULL; + +int main(int argc, char* argv[]) { + cout << "Hello, wolrd!" << endl; + return 0; +} + diff --git a/apps/c/dl/axbuild.mk b/apps/c/dl/axbuild.mk index 55cc2d0f4..8c1a982aa 100644 --- a/apps/c/dl/axbuild.mk +++ b/apps/c/dl/axbuild.mk @@ -3,4 +3,5 @@ app-objs=main.o ARGS = /bin/hello ENVS = V9P_PATH=${APP}/rootfs + # make run ARCH=aarch64 A=apps/c/dl V9P=y MUSL=y LOG=debug \ No newline at end of file diff --git a/apps/c/httpserver/httpserver.c b/apps/c/httpserver/httpserver.c index 1e7de2b68..b552b16e1 100644 --- a/apps/c/httpserver/httpserver.c +++ b/apps/c/httpserver/httpserver.c @@ -96,4 +96,4 @@ int main() return -1; } return 0; -} +} \ No newline at end of file diff --git a/apps/c/libc-bench/expect_warn.out b/apps/c/libc-bench/expect_warn.out new file mode 100644 index 000000000..7ac262316 --- /dev/null +++ b/apps/c/libc-bench/expect_warn.out @@ -0,0 +1,29 @@ +b_malloc_sparse (0) +b_malloc_bubble (0) +b_malloc_tiny1 (0) +b_malloc_tiny2 (0) +b_malloc_big1 (0) +b_malloc_big2 (0) +b_malloc_thread_stress (0) +b_malloc_thread_local (0) +b_string_strstr ("abcdefghijklmnopqrstuvwxyz") +b_string_strstr ("azbycxdwevfugthsirjqkplomn") +b_string_strstr ("aaaaaaaaaaaaaacccccccccccc") +b_string_strstr ("aaaaaaaaaaaaaaaaaaaaaaaaac") +b_string_strstr ("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac") +b_string_memset (0) +b_string_strchr (0) +b_string_strlen (0) +b_pthread_createjoin_serial1 (0) +b_pthread_createjoin_serial2 (0) +b_pthread_create_serial1 (0) +b_pthread_uselesslock (0) +b_pthread_createjoin_minimal1 (0) +b_pthread_createjoin_minimal2 (0) +b_utf8_bigbuf (0) +b_utf8_onebyone (0) +b_stdio_putcgetc (0) +b_stdio_putcgetc_unlocked (0) +b_regex_compile ("(a|b|c)*d*b") +b_regex_search ("(a|b|c)*d*b") +b_regex_search ("a{25}b") \ No newline at end of file diff --git a/apps/c/udpserver/apps_udpserver.md b/apps/c/udpserver/apps_udpserver.md index 87a5ae68a..453a311aa 100644 --- a/apps/c/udpserver/apps_udpserver.md +++ b/apps/c/udpserver/apps_udpserver.md @@ -1,7 +1,7 @@ # INTRODUCTION | App | Extra modules | Enabled features | Description | |-|-|-|-| -| [udpserver](../apps/c/udpserver) | axalloc, axnet, axdriver, axtask | alloc, paging, net, multitask | UDP server test| +| [udpserver](../apps/c/udpserver) | axalloc, ruxnet, axdriver, axtask | alloc, paging, net, multitask | UDP server test| # RUN ``` bash diff --git a/crates/driver_net/src/ixgbe.rs b/crates/driver_net/src/ixgbe.rs index 27fd4363a..3fe98b4c6 100644 --- a/crates/driver_net/src/ixgbe.rs +++ b/crates/driver_net/src/ixgbe.rs @@ -15,7 +15,7 @@ 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}; +use crate::{EthernetAddress, NetBuf, NetBufPool, NetBufPtr, NetDriverOps}; extern crate alloc; @@ -88,6 +88,16 @@ impl NetDriverOps for IxgbeNic) -> DevResult { + Ok(()) + } + + ///TODO + fn prepare_tx_buffer(&self, _: &mut NetBuf, _: usize) -> DevResult { + Ok(()) + } + 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); diff --git a/crates/driver_net/src/lib.rs b/crates/driver_net/src/lib.rs index 6ceb95e47..6fd20c600 100644 --- a/crates/driver_net/src/lib.rs +++ b/crates/driver_net/src/lib.rs @@ -14,6 +14,9 @@ #![feature(const_slice_from_raw_parts_mut)] #![feature(box_into_inner)] +extern crate alloc; +use alloc::sync::Arc; + #[cfg(feature = "ixgbe")] /// ixgbe NIC device driver. pub mod ixgbe; @@ -46,6 +49,16 @@ pub trait NetDriverOps: BaseDriverOps { /// Size of the transmit queue. fn tx_queue_size(&self) -> usize; + /// Fills the receive queue with buffers. + /// + /// It should be called once when the driver is initialized. + fn fill_rx_buffers(&mut self, buf_pool: &Arc) -> DevResult; + + /// Prepares a buffer for transmitting. + /// + /// e.g., fill the header of the packet. + fn prepare_tx_buffer(&self, tx_buf: &mut NetBuf, packet_len: usize) -> DevResult; + /// Gives back the `rx_buf` to the receive queue for later receiving. /// /// `rx_buf` should be the same as the one returned by diff --git a/crates/driver_virtio/src/net.rs b/crates/driver_virtio/src/net.rs index 3569738c1..abd0e0f47 100644 --- a/crates/driver_virtio/src/net.rs +++ b/crates/driver_virtio/src/net.rs @@ -117,6 +117,21 @@ impl NetDriverOps for VirtIoNetDev) -> DevResult { + for (i, rx_buf_place) in self.rx_buffers.iter_mut().enumerate() { + let mut rx_buf = buf_pool.alloc_boxed().ok_or(DevError::NoMemory)?; + // Safe because the buffer lives as long as the queue. + let token = unsafe { + self.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); + } + Ok(()) + } + 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`, @@ -135,6 +150,19 @@ impl NetDriverOps for VirtIoNetDev DevResult { + let hdr_len = self + .inner + .fill_buffer_header(tx_buf.raw_buf_mut()) + .or(Err(DevError::InvalidParam))?; + if hdr_len + pkt_len > tx_buf.capacity() { + return Err(DevError::InvalidParam); + } + tx_buf.set_header_len(hdr_len); + tx_buf.set_packet_len(pkt_len); + 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] diff --git a/crates/lwip_rust/.gitignore b/crates/lwip_rust/.gitignore new file mode 100644 index 000000000..e2862f469 --- /dev/null +++ b/crates/lwip_rust/.gitignore @@ -0,0 +1,2 @@ +/target +/src/bindings.rs \ No newline at end of file diff --git a/crates/lwip_rust/Cargo.toml b/crates/lwip_rust/Cargo.toml new file mode 100644 index 000000000..1b3c74b07 --- /dev/null +++ b/crates/lwip_rust/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "lwip_rust" +version = "0.1.0" +edition = "2021" +authors = ["HaoWen Liu "] +description = "Rust wrapper of lwip net stack" +license = "Mulan PSL v2" +homepage = "https://github.com/syswonder/ruxos" +repository = "https://github.com/syswonder/ruxos/tree/main/crates/lwip_rust" + +[build-dependencies] +bindgen = "0.65.1" +cc = { version = "1.0.79", features = ["parallel"] } diff --git a/crates/lwip_rust/build.rs b/crates/lwip_rust/build.rs new file mode 100644 index 000000000..1fcdf0c3e --- /dev/null +++ b/crates/lwip_rust/build.rs @@ -0,0 +1,84 @@ +use std::path::PathBuf; + +fn main() { + println!("cargo:rustc-link-lib=lwip"); + println!("cargo:rerun-if-changed=custom"); + println!("cargo:rerun-if-changed=depend"); + println!("cargo:rerun-if-changed=wrapper.h"); + + let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + let clippy_args = std::env::var("CLIPPY_ARGS"); + + // Not build with clippy or doc + if target_os == "none" && clippy_args.is_err() { + compile_lwip(); + } + generate_lwip_bindings(); +} + +fn generate_lwip_bindings() { + let bindings = bindgen::Builder::default() + .use_core() + .header("wrapper.h") + .clang_arg("-I./depend/lwip/src/include") + .clang_arg("-I./custom") + .clang_arg("-Wno-everything") + .layout_tests(false) + .parse_callbacks(Box::new(bindgen::CargoCallbacks)) + .generate() + .expect("Unable to generate bindings"); + + let out_path = PathBuf::from("src"); + bindings + .write_to_file(out_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); +} + +fn compile_lwip() { + let mut base_config = cc::Build::new(); + + base_config + .include("depend/lwip/src/include") + .include("custom"); + + base_config + .file("depend/lwip/src/api/err.c") + .file("depend/lwip/src/core/init.c") + .file("depend/lwip/src/core/def.c") + .file("depend/lwip/src/core/dns.c") + .file("depend/lwip/src/core/inet_chksum.c") + .file("depend/lwip/src/core/ip.c") + .file("depend/lwip/src/core/mem.c") + .file("depend/lwip/src/core/memp.c") + .file("depend/lwip/src/core/netif.c") + .file("depend/lwip/src/core/pbuf.c") + .file("depend/lwip/src/core/raw.c") + .file("depend/lwip/src/core/stats.c") + .file("depend/lwip/src/core/sys.c") + .file("depend/lwip/src/core/altcp.c") + .file("depend/lwip/src/core/altcp_alloc.c") + .file("depend/lwip/src/core/altcp_tcp.c") + .file("depend/lwip/src/core/tcp.c") + .file("depend/lwip/src/core/tcp_in.c") + .file("depend/lwip/src/core/tcp_out.c") + .file("depend/lwip/src/core/timeouts.c") + .file("depend/lwip/src/core/udp.c") + .file("depend/lwip/src/core/ipv4/autoip.c") + .file("depend/lwip/src/core/ipv4/dhcp.c") + .file("depend/lwip/src/core/ipv4/etharp.c") + .file("depend/lwip/src/core/ipv4/icmp.c") + .file("depend/lwip/src/core/ipv4/igmp.c") + .file("depend/lwip/src/core/ipv4/ip4_frag.c") + .file("depend/lwip/src/core/ipv4/ip4.c") + .file("depend/lwip/src/core/ipv4/ip4_addr.c") + .file("depend/lwip/src/netif/ethernet.c") + .file("custom/custom_pool.c"); + + base_config + .warnings(true) + .flag("-static") + .flag("-no-pie") + .flag("-fno-builtin") + .flag("-ffreestanding") + .compile("liblwip.a"); +} diff --git a/crates/lwip_rust/custom/arch/cc.h b/crates/lwip_rust/custom/arch/cc.h new file mode 100644 index 000000000..081b74f58 --- /dev/null +++ b/crates/lwip_rust/custom/arch/cc.h @@ -0,0 +1,37 @@ +#ifndef __ARCH_CC_H__ +#define __ARCH_CC_H__ + +#ifndef _SSIZE_T_DEFINED +typedef long ssize_t; +#define _SSIZE_T_DEFINED +#endif + +#define LWIP_NO_INTTYPES_H 1 +#define U8_F "hhu" +#define S8_F "hhd" +#define X8_F "hhx" +#define U16_F "hu" +#define S16_F "hd" +#define X16_F "hx" +#define U32_F "u" +#define S32_F "d" +#define X32_F "x" +#define SZT_F "zu" + +#define LWIP_NO_LIMITS_H 1 +#define LWIP_NO_CTYPE_H 1 + +#define SSIZE_MAX INT_MAX +#define LWIP_NO_UNISTD_H 1 + +#define LWIP_PLATFORM_DIAG(x) \ + do { \ + } while (0) + +#define LWIP_PLATFORM_ASSERT(x) \ + do { \ + } while (0) + +#define LWIP_RAND() (rand()) + +#endif /* __ARCH_CC_H__ */ \ No newline at end of file diff --git a/crates/lwip_rust/custom/custom_pool.c b/crates/lwip_rust/custom/custom_pool.c new file mode 100644 index 000000000..da2181b58 --- /dev/null +++ b/crates/lwip_rust/custom/custom_pool.c @@ -0,0 +1,30 @@ +#include "custom_pool.h" +#include "lwip/memp.h" +#include "lwip/pbuf.h" +#include "lwip/stats.h" + +#define RX_POOL_SIZE 128 + +LWIP_MEMPOOL_DECLARE(RX_POOL, RX_POOL_SIZE, sizeof(rx_custom_pbuf_t), "Zero-copy RX PBUF pool") + +void rx_custom_pbuf_init(void) +{ + LWIP_MEMPOOL_INIT(RX_POOL); +} + +struct pbuf *rx_custom_pbuf_alloc(pbuf_free_custom_fn custom_free_function, void *buf, void *dev, + u16_t length, void *payload_mem, u16_t payload_mem_len) +{ + rx_custom_pbuf_t *my_pbuf = (rx_custom_pbuf_t *)LWIP_MEMPOOL_ALLOC(RX_POOL); + my_pbuf->p.custom_free_function = custom_free_function; + my_pbuf->buf = buf; + my_pbuf->dev = dev; + struct pbuf *p = + pbuf_alloced_custom(PBUF_RAW, length, PBUF_REF, &my_pbuf->p, payload_mem, payload_mem_len); + return p; +} + +void rx_custom_pbuf_free(rx_custom_pbuf_t *p) +{ + LWIP_MEMPOOL_FREE(RX_POOL, p); +} diff --git a/crates/lwip_rust/custom/custom_pool.h b/crates/lwip_rust/custom/custom_pool.h new file mode 100644 index 000000000..66ccde732 --- /dev/null +++ b/crates/lwip_rust/custom/custom_pool.h @@ -0,0 +1,18 @@ + +#ifndef __CUSTOM_POOL_H__ +#define __CUSTOM_POOL_H__ + +#include "lwip/pbuf.h" + +typedef struct rx_custom_pbuf_t { + struct pbuf_custom p; + void *buf; + void *dev; +} rx_custom_pbuf_t; + +void rx_custom_pbuf_init(void); +struct pbuf *rx_custom_pbuf_alloc(pbuf_free_custom_fn custom_free_function, void *buf, void *dev, + u16_t length, void *payload_mem, u16_t payload_mem_len); +void rx_custom_pbuf_free(rx_custom_pbuf_t *p); + +#endif /* __CUSTOM_POOL_H__ */ \ No newline at end of file diff --git a/crates/lwip_rust/custom/lwipopts.h b/crates/lwip_rust/custom/lwipopts.h new file mode 100644 index 000000000..fefe19fce --- /dev/null +++ b/crates/lwip_rust/custom/lwipopts.h @@ -0,0 +1,129 @@ +#ifndef __LWIPOPTS_H__ +#define __LWIPOPTS_H__ + +#define NO_SYS 1 + +/* + ------------------------------------ + ------------ Functions ------------ + ------------------------------------ +*/ +#define IP_DEFAULT_TTL 64 +#define LWIP_ETHERNET 1 +#define LWIP_ARP 1 +#define ARP_QUEUEING 0 +#define IP_FORWARD 0 +#define LWIP_ICMP 1 +#define LWIP_RAW 0 +#define LWIP_DHCP 0 +#define LWIP_AUTOIP 0 +#define LWIP_SNMP 0 +#define LWIP_IGMP 0 +#define LWIP_DNS 1 +#define LWIP_UDP 1 +#define LWIP_UDPLITE 0 +#define LWIP_TCP 1 +#define LWIP_CALLBACK_API 1 +#define LWIP_NETIF_API 0 +#define LWIP_NETIF_LOOPBACK 0 +#define LWIP_HAVE_LOOPIF 1 +#define LWIP_HAVE_SLIPIF 0 +#define LWIP_NETCONN 0 +#define LWIP_SOCKET 0 +#define PPP_SUPPORT 0 +#define LWIP_IPV4 1 + +// Enable SO_REUSEADDR +#define SO_REUSE 1 + +/* + ------------------------------------ + ------ Memory and Performance ------ + ------------------------------------ +*/ + +// Important performance options +// Smaller values increase performance +// Larger values increase simultaneously active TCP connections limit +#define MEMP_NUM_TCP_PCB 5 + +// Memory options +#define MEM_SIZE (1 * 1024 * 1024) +#define MEMP_NUM_TCP_SEG 128 +#define MEMP_NUM_PBUF 32 +#define PBUF_POOL_SIZE 32 + +// Tcp options +#define TCP_MSS 1460 +#define TCP_WND (32 * TCP_MSS) +#define TCP_SND_BUF (16 * TCP_MSS) + +// Disable checksum checks +#define CHECKSUM_CHECK_IP 0 +#define CHECKSUM_CHECK_UDP 0 +#define CHECKSUM_CHECK_TCP 0 +#define CHECKSUM_CHECK_ICMP 0 +#define CHECKSUM_CHECK_ICMP6 0 + +// Other performance options +#define LWIP_CHECKSUM_ON_COPY 1 +#define SYS_LIGHTWEIGHT_PROT 0 + +/* + ------------------------------------ + ---------- Debug options ---------- + ------------------------------------ +*/ + +#define LWIP_DEBUG 0 +#define LWIP_DBG_TYPES_ON LWIP_DBG_OFF +#define LWIP_DBG_MIN_LEVEL LWIP_DBG_LEVEL_ALL + +#define ETHARP_DEBUG LWIP_DBG_OFF +#define NETIF_DEBUG LWIP_DBG_ON +#define PBUF_DEBUG LWIP_DBG_OFF +#define API_LIB_DEBUG LWIP_DBG_ON +#define API_MSG_DEBUG LWIP_DBG_ON +#define SOCKETS_DEBUG LWIP_DBG_ON +#define ICMP_DEBUG LWIP_DBG_ON +#define IGMP_DEBUG LWIP_DBG_ON +#define INET_DEBUG LWIP_DBG_ON +#define IP_DEBUG LWIP_DBG_ON +#define IP_REASS_DEBUG LWIP_DBG_ON +#define RAW_DEBUG LWIP_DBG_ON +#define MEM_DEBUG LWIP_DBG_ON +#define MEMP_DEBUG LWIP_DBG_ON +#define SYS_DEBUG LWIP_DBG_ON +#define TIMERS_DEBUG LWIP_DBG_OFF +#define TCP_DEBUG LWIP_DBG_ON +#define TCP_INPUT_DEBUG LWIP_DBG_ON +#define TCP_FR_DEBUG LWIP_DBG_ON +#define TCP_RTO_DEBUG LWIP_DBG_ON +#define TCP_CWND_DEBUG LWIP_DBG_ON +#define TCP_WND_DEBUG LWIP_DBG_ON +#define TCP_OUTPUT_DEBUG LWIP_DBG_ON +#define TCP_RST_DEBUG LWIP_DBG_ON +#define TCP_QLEN_DEBUG LWIP_DBG_ON +#define UDP_DEBUG LWIP_DBG_ON +#define TCPIP_DEBUG LWIP_DBG_ON +#define SLIP_DEBUG LWIP_DBG_ON +#define DHCP_DEBUG LWIP_DBG_ON +#define AUTOIP_DEBUG LWIP_DBG_ON +#define ACD_DEBUG LWIP_DBG_ON +#define DNS_DEBUG LWIP_DBG_ON + +#define LWIP_STATS 0 +#define LWIP_STATS_DISPLAY 0 +#define LWIP_PERF 0 + +/* + ------------------------------------ + ----------- Memory check ----------- + ------------------------------------ +*/ +#define MEMP_OVERFLOW_CHECK 0 +#define MEMP_SANITY_CHECK 0 +#define MEM_OVERFLOW_CHECK 0 +#define MEM_SANITY_CHECK 0 + +#endif /* __LWIPOPTS_H__ */ \ No newline at end of file diff --git a/crates/lwip_rust/depend/lwip b/crates/lwip_rust/depend/lwip new file mode 160000 index 000000000..86c9f7999 --- /dev/null +++ b/crates/lwip_rust/depend/lwip @@ -0,0 +1 @@ +Subproject commit 86c9f7999150199374d7697fd2eed62dcd9b1afa diff --git a/crates/lwip_rust/src/lib.rs b/crates/lwip_rust/src/lib.rs new file mode 100644 index 000000000..77a366662 --- /dev/null +++ b/crates/lwip_rust/src/lib.rs @@ -0,0 +1,7 @@ +#![no_std] +#![allow(dead_code)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(missing_docs)] +pub mod bindings; diff --git a/crates/lwip_rust/wrapper.h b/crates/lwip_rust/wrapper.h new file mode 100644 index 000000000..eeac68cf2 --- /dev/null +++ b/crates/lwip_rust/wrapper.h @@ -0,0 +1,13 @@ +#include "depend/lwip/src/include/lwip/def.h" +#include "depend/lwip/src/include/lwip/dns.h" +#include "depend/lwip/src/include/lwip/etharp.h" +#include "depend/lwip/src/include/lwip/init.h" +#include "depend/lwip/src/include/lwip/ip4_addr.h" +#include "depend/lwip/src/include/lwip/ip_addr.h" +#include "depend/lwip/src/include/lwip/netif.h" +#include "depend/lwip/src/include/lwip/tcp.h" +#include "depend/lwip/src/include/lwip/timeouts.h" +#include "depend/lwip/src/include/lwip/udp.h" +#include "depend/lwip/src/include/netif/ethernet.h" + +#include "custom/custom_pool.h" diff --git a/crates/tty/Cargo.toml b/crates/tty/Cargo.toml new file mode 100644 index 000000000..8d8da252c --- /dev/null +++ b/crates/tty/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tty" +version = "0.0.1" +edition = "2021" + +[dependencies] +spinlock = { path = "../spinlock" } +lazy_init = { path = "../lazy_init" } +log = "0.4" diff --git a/crates/tty/src/buffer.rs b/crates/tty/src/buffer.rs new file mode 100644 index 000000000..2c63d2324 --- /dev/null +++ b/crates/tty/src/buffer.rs @@ -0,0 +1,149 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] 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. + */ + +//! functions for tty buffer. +//! Drivers should fill the buffer by functions below. +//! then the data will be passed to line discipline for processing. + +/// tty buffer size. +const TTY_BUF_SIZE: usize = 4096; + +/// ring buffer. +#[derive(Debug)] +struct RingBuffer { + /// data. + buf: [u8; TTY_BUF_SIZE], + + /// the first element or empty slot if buffer is empty. + head: usize, + + /// the first empty slot. + tail: usize, + + /// number of elements. + len: usize, +} + +/// tty buffer. +/// TODO: use flip buffer. +#[derive(Debug)] +pub struct TtyBuffer { + /// use ring buffer to save chars. + buffer: spinlock::SpinNoIrq, +} + +impl TtyBuffer { + pub fn new() -> Self { + Self { + buffer: spinlock::SpinNoIrq::new(RingBuffer { + buf: [0u8; TTY_BUF_SIZE], + head: 0, + tail: 0, + len: 0, + }), + } + } + + /// get `index`th element without changing buffer. + pub fn see(&self, index: usize) -> u8 { + let buf = self.buffer.lock(); + if index < buf.len { + buf.buf[(index + buf.head) % TTY_BUF_SIZE] + } else { + 0 + } + } + + /// push a char to tail. + pub fn push(&self, ch: u8) { + let mut buf = self.buffer.lock(); + if buf.len != TTY_BUF_SIZE { + buf.len += 1; + let idx = buf.tail; + buf.buf[idx] = ch; + buf.tail = (buf.tail + 1) % TTY_BUF_SIZE; + } + } + + /// delete and return the heading char. + pub fn pop(&self) -> u8 { + self.delete(0) + } + + /// insert `ch` to `index`th position. + pub fn insert(&self, ch: u8, index: usize) { + let mut buf = self.buffer.lock(); + // if not full and index is right + if buf.len != TTY_BUF_SIZE && index <= buf.len { + // shift buffer[index..move_len+index] one slot right. + let move_len = buf.len - index; + let mut i = buf.tail; + for _ in 0..move_len { + i -= 1; + buf.buf[(i + 1) % TTY_BUF_SIZE] = buf.buf[i % TTY_BUF_SIZE]; + } + // insert + let idx = (buf.head + index) % TTY_BUF_SIZE; + buf.buf[idx] = ch; + buf.len += 1; + buf.tail = (buf.tail + 1) % TTY_BUF_SIZE; + } + } + + /// delete and return the `index`th element. + pub fn delete(&self, index: usize) -> u8 { + let mut buf = self.buffer.lock(); + // if not empty and index is right + if buf.len != 0 && index < buf.len { + let move_len = buf.len - index; + let mut i = index + buf.head; + + // save retval + let ret = buf.buf[i % TTY_BUF_SIZE]; + + // copy move_len elements from buffer[index+head] to buffer[index+head-1]; + for _ in 0..move_len { + buf.buf[i % TTY_BUF_SIZE] = buf.buf[(i + 1) % TTY_BUF_SIZE]; + i += 1; + } + + // len -= 1 + buf.len -= 1; + buf.tail -= 1; + ret + } else { + 0 + } + } + + /// get current length of buffer. + pub fn len(&self) -> usize { + self.buffer.lock().len + } +} + +/// a buffer for echo of line discipline. +/// additionally saving the cursor position. +#[derive(Debug)] +pub struct EchoBuffer { + /// chars buffer. + pub buffer: TtyBuffer, + + /// current column of cursor. + pub col: usize, +} + +impl EchoBuffer { + pub fn new() -> Self { + Self { + buffer: TtyBuffer::new(), + col: 0, + } + } +} diff --git a/crates/tty/src/constant.rs b/crates/tty/src/constant.rs new file mode 100644 index 000000000..9a134764f --- /dev/null +++ b/crates/tty/src/constant.rs @@ -0,0 +1,29 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] 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. + */ + +pub const LF: u8 = b'\n'; +pub const CR: u8 = b'\r'; + +pub const DEL: u8 = b'\x7f'; +pub const BS: u8 = b'\x08'; + +pub const SPACE: u8 = b' '; + +/// escape +pub const ESC: u8 = 27; +/// [ +pub const LEFT_BRACKET: u8 = 91; + +/// an arrow char is `ARROW_PREFIX` + `UP/DOWN/RIGHT/LEFT` +pub const ARROW_PREFIX: [u8; 2] = [ESC, LEFT_BRACKET]; + +// const UP: u8 = 65; +// const DOWN: u8 = 66; +pub const RIGHT: u8 = 67; +pub const LEFT: u8 = 68; diff --git a/crates/tty/src/driver.rs b/crates/tty/src/driver.rs new file mode 100644 index 000000000..dfe106a31 --- /dev/null +++ b/crates/tty/src/driver.rs @@ -0,0 +1,167 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] 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. + */ + +//! the first thing a driver should do is registering itself by `register_driver()`, +//! which will allocate an index for this driver. +//! +//! then, driver should register every device it has by `register_device()`, +//! which will allocate an index for this device. + +use crate::tty::TtyStruct; +use alloc::collections::BTreeMap; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::{vec, vec::Vec}; +use lazy_init::LazyInit; +use spinlock::SpinNoIrq; + +/// all tty drivers. +/// only be written when registering a driver. +pub(super) static ALL_DRIVERS: LazyInit>>> = LazyInit::new(); + +/// the operations a tty driver must implement. +/// passed by driver when registering itself. +#[derive(Debug)] +pub struct TtyDriverOps { + /// push a char to device. + pub putchar: fn(u8), +} + +/// tty driver. +#[derive(Debug)] +pub struct TtyDriver { + /// driver operations. + pub ops: TtyDriverOps, + + /// driver's devices. + /// TODO: maybe use rwlock for dynamicly adding devices is better. + ttys: SpinNoIrq>>, + + /// index of driver. + index: usize, + + /// name of driver. + name: String, +} + +impl TtyDriver { + pub fn new(ops: TtyDriverOps, name: &str) -> Self { + Self { + ops, + ttys: SpinNoIrq::new(BTreeMap::new()), + index: 0, + name: String::from(name), + } + } + + /// add a device, return its index, -1 means failure. + fn add_one_device(&self, tty: Arc) -> isize { + let mut index = 0; + if let Some(k) = self.ttys.lock().last_key_value() { + index = *k.0; + } + + // set index of device + tty.set_index(index); + + // set name of device + let mut name = self.name.clone(); + name.push(core::char::from_digit(index as _, 16).unwrap()); + tty.set_name(&name); + + // save this device + self.ttys.lock().insert(index, tty.clone()); + + // return device's index + index as _ + } + + pub fn name(&self) -> String { + self.name.clone() + } + + pub fn index(&self) -> usize { + self.index + } + + /// get all devices' name + pub fn get_all_device_names(&self) -> Vec { + let mut ret = vec![]; + for (_, tty) in self.ttys.lock().iter() { + ret.push(tty.name()); + } + ret + } + + /// get device + pub fn get_device_by_name(&self, name: &str) -> Option> { + for (_, tty) in self.ttys.lock().iter() { + if tty.name() == name { + return Some(tty.clone()); + } + } + None + } + + /// get device + pub fn get_device_by_index(&self, index: usize) -> Option> { + self.ttys.lock().get(&index).cloned() + } +} + +pub fn init() { + ALL_DRIVERS.init_by(SpinNoIrq::new(vec![])); +} + +/// get driver by index. +pub fn get_driver_by_index(index: usize) -> Option> { + let lock = ALL_DRIVERS.lock(); + for driver in lock.iter() { + if driver.index == index { + return Some(driver.clone()); + } + } + None +} + +/// called by driver to register itself. +/// return driver's index. +pub fn register_driver(ops: TtyDriverOps, name: &str) -> usize { + // create a tty driver structure + let mut driver = TtyDriver::new(ops, name); + + // lock + let mut lock = ALL_DRIVERS.lock(); + + // grant an index to the driver + let index = lock.len(); + driver.index = index; + + // push + lock.push(Arc::new(driver)); + + // return index + index +} + +/// called by driver to register device. +/// return device's index, or -1 on failure. +pub fn register_device(driver_index: usize) -> isize { + let mut index = -1; + // if driver is found + if let Some(driver) = get_driver_by_index(driver_index) { + // create a tty structure + let tty = Arc::new(TtyStruct::new(driver.clone())); + + // save this structure + index = driver.add_one_device(tty.clone()); + crate::tty::add_one_device(tty.clone()); + } + index +} diff --git a/crates/tty/src/ldisc.rs b/crates/tty/src/ldisc.rs new file mode 100644 index 000000000..e7394ac42 --- /dev/null +++ b/crates/tty/src/ldisc.rs @@ -0,0 +1,221 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] 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. + */ + +//! TTY line discipline process all incoming and outgoing chars from/to a tty device. +//! the currently implemented line discipline is N_TTY. +//! line disciplines are registered when a device is registered. + +use alloc::sync::Arc; +use spinlock::SpinNoIrq; + +use crate::{ + buffer::{EchoBuffer, TtyBuffer}, + tty::TtyStruct, +}; + +/// tty line discipline. +#[derive(Debug)] +pub struct TtyLdisc { + /// chars that can be read by kernel. + read_buf: TtyBuffer, + + /// chars being echoed on the screen. + echo_buf: SpinNoIrq, + + /// chars from driver, and not yet been processed. + rec_buf: TtyBuffer, +} + +/// implement N_TTY. +impl TtyLdisc { + pub fn new() -> Self { + Self { + read_buf: TtyBuffer::new(), + echo_buf: SpinNoIrq::new(EchoBuffer::new()), + rec_buf: TtyBuffer::new(), + } + } + + /// kernel reads data. + pub fn read(&self, buf: &mut [u8]) -> usize { + let read_buf = &self.read_buf; + + // len of this reading + let len = buf.len().min(read_buf.len()); + + // return if nothing can be read + if len == 0 { + return 0; + } + + // copy data from read_buf to `buf` + for ch in buf.iter_mut().take(len) { + *ch = read_buf.pop(); + } + + len + } + + /// driver sends data from device for processing and echoing. + /// running in irq. + pub fn receive_buf(&self, tty: Arc, buf: &[u8]) { + use crate::constant::*; + + let rec_buf = &self.rec_buf; + + // save data to receive buffer + for ch in buf { + rec_buf.push(*ch); + } + + // process chars in receive buffer + while rec_buf.len() > 0 { + let ch = rec_buf.see(0); + + // if char may be arrow char + if ch == ARROW_PREFIX[0] { + // no enough len, just break, waitting for next time + if rec_buf.len() < 3 { + break; + } + + // enough len, but not a arrow char, just ignore + if rec_buf.see(1) != ARROW_PREFIX[1] { + rec_buf.pop(); + rec_buf.pop(); + break; + } + + // it is an arrow char, get it + rec_buf.pop(); + rec_buf.pop(); + let ch = rec_buf.pop(); + + // deal with arrow char + match ch { + LEFT => { + let mut lock = self.echo_buf.lock(); + // if can go left + if lock.col > 0 { + self.write(tty.clone(), &[ARROW_PREFIX[0], ARROW_PREFIX[1], ch]); + lock.col -= 1; + } + } + RIGHT => { + let mut lock = self.echo_buf.lock(); + // if can go right + if lock.col < lock.buffer.len() { + self.write(tty.clone(), &[ARROW_PREFIX[0], ARROW_PREFIX[1], ch]); + lock.col += 1; + } + } + _ => { + // it is UP/DOWN, just ignore + } + } + // not a arrow char, handle it as a normal char + } else { + let ch = rec_buf.pop(); + match ch { + CR | LF => { + // always '\n' + let ch = LF; + + // echo + self.write(tty.clone(), &[ch]); + + // push this char to echo buffer + let mut lock = self.echo_buf.lock(); + lock.buffer.push(ch); + + // copy echo buffer to read buffer + // FIXME: currently will push all data to read_buf + let len = lock.buffer.len(); + for _ in 0..len { + self.read_buf.push(lock.buffer.pop()); + } + + // echo buffer's column is set to 0 + lock.col = 0; + } + BS | DEL => { + let mut lock = self.echo_buf.lock(); + let col = lock.col; + let len = lock.buffer.len(); + // if can delete + if col > 0 { + // perform a backspace + self.write(tty.clone(), &[BS, SPACE, BS]); + + // if cursor is not on the rightmost + if col != len { + for i in col..len { + let ch = lock.buffer.see(i); + self.write(tty.clone(), &[ch]); + } + self.write(tty.clone(), &[SPACE]); + for _ in 0..(len - col + 1) { + self.write( + tty.clone(), + &[ARROW_PREFIX[0], ARROW_PREFIX[1], LEFT], + ); + } + } + + // modify echo buffer + lock.buffer.delete(col - 1); + lock.col -= 1; + } + } + _ => { + // process normal chars. + let mut echo_buf = self.echo_buf.lock(); + let col = echo_buf.col; + let len = echo_buf.buffer.len(); + + // echo + self.write(tty.clone(), &[ch]); + + // if cursor is not on the rightmost + if col != len { + for i in col..len { + self.write(tty.clone(), &[echo_buf.buffer.see(i)]); + } + for _ in 0..(len - col) { + self.write(tty.clone(), &[ARROW_PREFIX[0], ARROW_PREFIX[1], LEFT]); + } + } + + // modify echo buffer + echo_buf.buffer.insert(ch, col); + echo_buf.col += 1; + } + } + } + } + } + + /// kernel writes data to device. + pub fn write(&self, tty: Arc, buf: &[u8]) -> usize { + let mut len = 0; + let driver = tty.driver(); + for ch in buf { + len += 1; + // call driver's method + (driver.ops.putchar)(*ch); + } + len + } +} + +impl Default for TtyLdisc { + fn default() -> Self { + Self::new() + } +} diff --git a/crates/tty/src/lib.rs b/crates/tty/src/lib.rs new file mode 100644 index 000000000..e6e30a134 --- /dev/null +++ b/crates/tty/src/lib.rs @@ -0,0 +1,72 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] 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. + */ + +//! Init +//! +//! firstly, a driver registers itself to get its index. +//! next, the driver registers all devices it found to get their indices. +//! +//! Read +//! +//! when a device receives data, it will cause a irq. +//! then the driver sends the data to tty layer using their indices. +//! finally, kernel will get the data using the device's name. +//! +//! Write +//! +//! kernel writes data to a device using its name. + +#![no_std] + +extern crate alloc; + +mod buffer; +mod constant; +mod driver; +mod ldisc; +mod tty; + +use driver::get_driver_by_index; + +pub use driver::{register_device, register_driver, TtyDriverOps}; +pub use tty::{get_all_device_names, get_device_by_name}; + +/// called by driver when irq, to send data from hardware. +pub fn tty_receive_buf(driver_index: usize, device_index: usize, buf: &[u8]) { + // check the validation of index + if let Some(driver) = get_driver_by_index(driver_index) { + if let Some(tty) = driver.get_device_by_index(device_index) { + tty.ldisc().receive_buf(tty.clone(), buf); + } + } +} + +/// called by kernel to read a tty device. +pub fn tty_read(buf: &mut [u8], dev_name: &str) -> usize { + if let Some(tty) = get_device_by_name(dev_name) { + tty.ldisc().read(buf) + } else { + 0 + } +} + +/// called by kernel to write a tty device. +pub fn tty_write(buf: &[u8], dev_name: &str) -> usize { + if let Some(tty) = get_device_by_name(dev_name) { + tty.ldisc().write(tty.clone(), buf) + } else { + 0 + } +} + +/// init +pub fn init() { + driver::init(); + tty::init(); +} diff --git a/crates/tty/src/tty.rs b/crates/tty/src/tty.rs new file mode 100644 index 000000000..2a54c2164 --- /dev/null +++ b/crates/tty/src/tty.rs @@ -0,0 +1,104 @@ +/* Copyright (c) [2023] [Syswonder Community] + * [Ruxos] 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. + */ + +use core::sync::atomic::AtomicUsize; + +use alloc::{string::String, sync::Arc, vec, vec::Vec}; +use lazy_init::LazyInit; +use spinlock::SpinNoIrq; + +use crate::{driver::TtyDriver, ldisc::TtyLdisc}; + +/// all registered devices. +pub(super) static ALL_DEVICES: LazyInit>>> = LazyInit::new(); + +/// tty device. +#[derive(Debug)] +pub struct TtyStruct { + /// driver of device. + driver: Arc, + + /// device's line discipline. + ldisc: Arc, + + /// index of device. + index: AtomicUsize, + + /// name of device. + name: SpinNoIrq, +} + +impl TtyStruct { + pub fn new(driver: Arc) -> Self { + Self { + driver: driver.clone(), + ldisc: Arc::new(TtyLdisc::new()), + index: AtomicUsize::new(0), + name: SpinNoIrq::new(String::new()), + } + } + + /// get tty line discipline. + pub fn ldisc(&self) -> Arc { + self.ldisc.clone() + } + + /// set device index. + pub fn set_index(&self, index: usize) { + self.index + .store(index, core::sync::atomic::Ordering::Relaxed); + } + + /// set name of device + pub fn set_name(&self, name: &str) { + let mut lock = self.name.lock(); + lock.clone_from(&String::from(name)); + } + + /// Convert a tty structure into a name, reflecting the kernel naming policy. + pub fn name(&self) -> String { + self.name.lock().clone() + } + + /// get device's driver. + pub fn driver(&self) -> Arc { + self.driver.clone() + } +} + +/// called by kernel to get a device. +pub fn get_device_by_name(name: &str) -> Option> { + let lock = ALL_DEVICES.lock(); + for tty in lock.iter() { + if tty.name() == name { + return Some(tty.clone()); + } + } + None +} + +/// called by kernel to get all devices' names. +/// usually used in init to get the view of tty. +pub fn get_all_device_names() -> Vec { + let mut ret = vec![]; + let alldev = ALL_DEVICES.lock(); + for dev in alldev.iter() { + ret.push(dev.name()); + } + ret +} + +/// save a device when registered. +pub fn add_one_device(tty: Arc) { + ALL_DEVICES.lock().push(tty); +} + +pub fn init() { + ALL_DEVICES.init_by(SpinNoIrq::new(vec![])); +} diff --git a/doc/README.md b/doc/README.md index 43706d0b6..18b742254 100644 --- a/doc/README.md +++ b/doc/README.md @@ -14,7 +14,7 @@ * [ruxfs](../modules/ruxfs): Ruxos filesystem module. * [ruxhal](../modules/ruxhal): Ruxos hardware abstraction layer, provides unified APIs for platform-specific operations. * [axlog](../modules/axlog): Macros for multi-level formatted logging used by Ruxos. -* [axnet](../modules/axnet): Ruxos network module. +* [ruxnet](../modules/ruxnet): Ruxos network module. * [ruxruntime](../modules/ruxruntime): Runtime library of Ruxos. * [axsync](../modules/axsync): Ruxos synchronization primitives. * [ruxtask](../modules/ruxtask): Ruxos task management module. @@ -62,10 +62,10 @@ | [sleep](../apps/task/sleep/) | axalloc, ruxtask | alloc, paging, multitask, sched_fifo, irq | Thread sleeping test | | [priority](../apps/task/priority/) | axalloc, ruxtask | alloc, paging, multitask, sched_cfs | Thread priority test | | [shell](../apps/fs/shell/) | axalloc, ruxdriver, ruxfs | alloc, paging, fs | A simple shell that responds to filesystem operations | -| [httpclient](../apps/net/httpclient/) | axalloc, ruxdriver, axnet | alloc, paging, net | A simple client that sends an HTTP request and then prints the response | -| [echoserver](../apps/net/echoserver/) | axalloc, ruxdriver, axnet, ruxtask | alloc, paging, net, multitask | A multi-threaded TCP server that reverses messages sent by the client | -| [httpserver](../apps/net/httpserver/) | axalloc, ruxdriver, axnet, ruxtask | alloc, paging, net, multitask | A multi-threaded HTTP server that serves a static web page | -| [udpserver](../apps/net/udpserver/) | axalloc, ruxdriver, axnet | alloc, paging, net | A simple echo server using UDP protocol | +| [httpclient](../apps/net/httpclient/) | axalloc, ruxdriver, ruxnet | alloc, paging, net | A simple client that sends an HTTP request and then prints the response | +| [echoserver](../apps/net/echoserver/) | axalloc, ruxdriver, ruxnet, ruxtask | alloc, paging, net, multitask | A multi-threaded TCP server that reverses messages sent by the client | +| [httpserver](../apps/net/httpserver/) | axalloc, ruxdriver, ruxnet, ruxtask | alloc, paging, net, multitask | A multi-threaded HTTP server that serves a static web page | +| [udpserver](../apps/net/udpserver/) | axalloc, ruxdriver, ruxnet | alloc, paging, net | A simple echo server using UDP protocol | ## Applications (C) | App | Extra modules | Enabled features | Description | @@ -73,8 +73,8 @@ | [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, ruxdriver, ruxfs | alloc, paging, fp_simd, fs | Porting of [SQLite3](https://sqlite.org/index.html) | -| [iperf](../apps/c/iperf/) | axalloc, ruxdriver, ruxfs, axnet | alloc, paging, fp_simd, fs, net, select | Porting of [iPerf3](https://iperf.fr/) | -| [redis](../apps/c/redis/) | axalloc, ruxdriver, ruxtask, ruxfs, axnet | alloc, paging, fp_simd, irq, multitask, fs, net, pipe, epoll | Porting of [Redis](https://redis.io/) | +| [iperf](../apps/c/iperf/) | axalloc, ruxdriver, ruxfs, ruxnet | alloc, paging, fp_simd, fs, net, select | Porting of [iPerf3](https://iperf.fr/) | +| [redis](../apps/c/redis/) | axalloc, ruxdriver, ruxtask, ruxfs, ruxnet | alloc, paging, fp_simd, irq, multitask, fs, net, pipe, epoll | Porting of [Redis](https://redis.io/) | ## Dependencies @@ -112,7 +112,7 @@ M[ruxconfig] N[axalloc] O[ruxtask] P[ruxdriver] -Q[axnet] +Q[ruxnet] Q1[ruxdisplay] M1[ruxfs] end diff --git a/doc/build.md b/doc/build.md index 7ae32634f..00f8af46e 100644 --- a/doc/build.md +++ b/doc/build.md @@ -20,7 +20,7 @@ What happens when "make A=apps/net/httpserver ARCH=aarch64 LOG=info NET=y SMP=1 - After checking axstd and arceos-api, the following three features were found: - `paging = ["ruxruntime/paging"]` - `multitask = ["ruxruntime/multitask", "ruxtask/multitask", "axsync/multitask"]` - - `net = ["ruxruntime/net", "dep:axnet"]` + - `net = ["ruxruntime/net", "dep:ruxnet"]` This involves modules such as ruxruntime, ruxtask, 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: diff --git a/doc/figures/ruxos.svg b/doc/figures/ruxos.svg index 7d06cf785..5ab10969c 100644 --- a/doc/figures/ruxos.svg +++ b/doc/figures/ruxos.svg @@ -1,4 +1,4 @@ -
ruxos modules
ruxos modules
Basic OS Components as Rust Crates
Basic OS Components as Rust Crates
User Apps
User Apps
Hypervisor
Hypervisor
axnet
axnet
ruxtask
ruxtask
ruxconfig
ruxconfig
ruxruntime
ruxruntime
ruxhal
ruxhal
ruxdriver
ruxdriver
allocator
allocator
axalloc
axalloc
scheduler
scheduler
page_table
page_table
linked_list
linked_list
driver_blk
driver_blk
ruxdisplay
ruxdisplay
smoltcp
smoltcp
buddy
buddy
slab
slab
FIFO
FIFO
CFS
CFS
ixgbe
ixgbe
driver_virtio
driver_virtio
driver_net
driver_net
ruxfs
ruxfs
page_table_entry
page_table_entry
ruxos API
ruxos API
ruxos_posix_api
ruxos_posix_api
arceos_api
arceos_api
ruxos ulib
ruxos ulib
axstd
axstd
ruxlibc
ruxlibc
rust std
rust std
Rust App
Rust App
C App
C App
Rust std App
Rust std App
C App
C App
ruxmusl
ruxmusl
ruxfeat
ruxfeat
feature selection
feature selection
rux9p
rux9p
driver_9p
driver_9p
Text is not SVG - cannot display
\ No newline at end of file +
ruxos modules
ruxos modules
Basic OS Components as Rust Crates
Basic OS Components as Rust Crates
User Apps
User Apps
Hypervisor
Hypervisor
ruxnet
ruxnet
ruxtask
ruxtask
ruxconfig
ruxconfig
ruxruntime
ruxruntime
ruxhal
ruxhal
ruxdriver
ruxdriver
allocator
allocator
axalloc
axalloc
scheduler
scheduler
page_table
page_table
linked_list
linked_list
driver_blk
driver_blk
ruxdisplay
ruxdisplay
smoltcp
smoltcp
buddy
buddy
slab
slab
FIFO
FIFO
CFS
CFS
ixgbe
ixgbe
driver_virtio
driver_virtio
driver_net
driver_net
ruxfs
ruxfs
page_table_entry
page_table_entry
ruxos API
ruxos API
ruxos_posix_api
ruxos_posix_api
arceos_api
arceos_api
ruxos ulib
ruxos ulib
axstd
axstd
ruxlibc
ruxlibc
rust std
rust std
Rust App
Rust App
C App
C App
Rust std App
Rust std App
C App
C App
ruxmusl
ruxmusl
ruxfeat
ruxfeat
feature selection
feature selection
rux9p
rux9p
driver_9p
driver_9p
Text is not SVG - cannot display
\ No newline at end of file diff --git a/doc/init.md b/doc/init.md index ab7834302..8da4aa474 100644 --- a/doc/init.md +++ b/doc/init.md @@ -15,7 +15,7 @@ graph TD; B --> ruxtask::init_scheduler; B --> ruxdriver::init_drivers; B --> Q[ruxfs::init_filesystems]; - B --> axnet::init_network; + B --> ruxnet::init_network; B --> ruxdisplay::init_display; B --> init_interrupt; B --> mp::start_secondary_cpus; diff --git a/modules/rux9p/Cargo.toml b/modules/rux9p/Cargo.toml index e24320b2a..7304aff45 100644 --- a/modules/rux9p/Cargo.toml +++ b/modules/rux9p/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/syswonder/ruxos/tree/main/modules/rux9p" [features] virtio-9p = ["ruxdriver/virtio-9p", "ruxdriver/virtio-9p"] -net-9p = ["axnet", "driver_common", "ruxdriver/dyn" , "ruxdriver/_9p"] +net-9p = ["ruxnet", "driver_common", "ruxdriver/dyn" , "ruxdriver/_9p"] need_auth = [] [dependencies] @@ -18,8 +18,9 @@ log = "0.4" spin = "0.9" driver_9p = { path = "../../crates/driver_9p"} axfs_vfs = { path = "../../crates/axfs_vfs"} +axsync = { path = "../axsync" } driver_common = { path = "../../crates/driver_common", optional = true} ruxfs = { path = "../ruxfs"} -axnet = { path = "../axnet", optional = true} +ruxnet = { path = "../ruxnet", optional = true} ruxdriver = { path = "../ruxdriver"} \ No newline at end of file diff --git a/modules/rux9p/src/drv.rs b/modules/rux9p/src/drv.rs index 131aa8cb1..82951fb3e 100644 --- a/modules/rux9p/src/drv.rs +++ b/modules/rux9p/src/drv.rs @@ -135,6 +135,7 @@ impl Drv9pOps { let mut request = _9PReq::new(_9PType::Tfsync); let mut response_buffer: [u8; _9P_MAX_PSIZE as usize] = [0; _9P_MAX_PSIZE as usize]; request.write_u32(fid); + request.write_u32(0_u32); //0: full data sync; 1: file data only. request.finish(); self.request(&request.buffer, &mut response_buffer) } diff --git a/modules/rux9p/src/fs.rs b/modules/rux9p/src/fs.rs index 2cc87c4b4..9ca29f73b 100644 --- a/modules/rux9p/src/fs.rs +++ b/modules/rux9p/src/fs.rs @@ -351,7 +351,7 @@ impl VfsNodeOps for CommonNode { ("", src_path) }; - let (dst_prefixs, new_name) = if let Some(dst_sindex) = src_path.rfind('/') { + let (dst_prefixs, new_name) = if let Some(dst_sindex) = dst_path.rfind('/') { (&dst_path[..dst_sindex], &dst_path[dst_sindex + 1..]) } else { ("", dst_path) diff --git a/modules/rux9p/src/netdev.rs b/modules/rux9p/src/netdev.rs index 07c78865b..730466a75 100644 --- a/modules/rux9p/src/netdev.rs +++ b/modules/rux9p/src/netdev.rs @@ -6,14 +6,15 @@ * 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. */ -use axnet::TcpSocket; +use axsync::Mutex; use core::net::{IpAddr, Ipv4Addr, SocketAddr}; use driver_9p::_9pDriverOps; use driver_common::{BaseDriverOps, DeviceType}; use log::*; +use ruxnet::TcpSocket; pub struct Net9pDev { - socket: TcpSocket, + socket: Mutex, srv_addr: SocketAddr, } @@ -27,7 +28,7 @@ impl Net9pDev { } }; Self { - socket: TcpSocket::new(), + socket: Mutex::new(TcpSocket::new()), srv_addr: SocketAddr::new(ip_addr, port), } } @@ -47,7 +48,7 @@ impl _9pDriverOps for Net9pDev { // initialize self(e.g. setup TCP connection) fn init(&self) -> Result<(), u8> { info!("9P client connecting to {:?}", self.srv_addr); - match self.socket.connect(self.srv_addr) { + match self.socket.lock().connect(self.srv_addr) { Ok(_) => { info!("net9p connected successfully"); Ok(()) @@ -61,7 +62,7 @@ impl _9pDriverOps for Net9pDev { // send bytes of inputs as request and receive get answer in outputs fn send_with_recv(&mut self, inputs: &[u8], outputs: &mut [u8]) -> Result { - match self.socket.send(inputs) { + match self.socket.lock().send(inputs) { Ok(length) => { debug!("net9p send successfully,length = {}", length); } @@ -70,7 +71,7 @@ impl _9pDriverOps for Net9pDev { return Err(0); } } - match self.socket.recv(outputs, 0) { + match self.socket.lock().recv(outputs, 0) { Ok(length) => { debug!("net9p recv successfully,length = {}", length); Ok(length as u32) diff --git a/modules/ruxdriver/src/dummy.rs b/modules/ruxdriver/src/dummy.rs index ba13b8f13..f048ef60c 100644 --- a/modules/ruxdriver/src/dummy.rs +++ b/modules/ruxdriver/src/dummy.rs @@ -13,6 +13,7 @@ #![allow(dead_code)] use super::prelude::*; +use alloc::sync::Arc; use cfg_if::cfg_if; cfg_if! { @@ -34,6 +35,8 @@ cfg_if! { fn can_receive(&self) -> bool { false } fn rx_queue_size(&self) -> usize { 0 } fn tx_queue_size(&self) -> usize { 0 } + fn fill_rx_buffers(&mut self, _: &Arc) -> DevResult { Err(DevError::Unsupported) } + fn prepare_tx_buffer(&self, _: &mut NetBuf, _: usize) -> DevResult { Err(DevError::Unsupported) } 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) } diff --git a/modules/ruxdriver/src/lib.rs b/modules/ruxdriver/src/lib.rs index 32343e0e7..ad3d32ce8 100644 --- a/modules/ruxdriver/src/lib.rs +++ b/modules/ruxdriver/src/lib.rs @@ -71,8 +71,6 @@ #[macro_use] extern crate log; - -#[cfg(feature = "dyn")] extern crate alloc; #[macro_use] diff --git a/modules/ruxhal/Cargo.toml b/modules/ruxhal/Cargo.toml index 7705c7ea8..7b4ea29fa 100644 --- a/modules/ruxhal/Cargo.toml +++ b/modules/ruxhal/Cargo.toml @@ -41,6 +41,7 @@ percpu = { path = "../../crates/percpu" } memory_addr = "0.1.0" handler_table = "0.1.0" crate_interface = "0.1.1" +tty = { path = "../../crates/tty", optional = true } [target.'cfg(target_arch = "x86_64")'.dependencies] x86 = "0.52" @@ -51,7 +52,7 @@ raw-cpuid = "11.0" [target.'cfg(any(target_arch = "riscv32", target_arch = "riscv64"))'.dependencies] riscv = "0.10" sbi-rt = { version = "0.0.3", features = ["legacy"] } -dtb = {path = "../../crates/dtb" } +dtb = { path = "../../crates/dtb" } [target.'cfg(target_arch = "aarch64")'.dependencies] aarch64-cpu = "9.3" @@ -59,7 +60,7 @@ tock-registers = "0.8" arm_gic = { path = "../../crates/arm_gic" } arm_pl011 = { path = "../../crates/arm_pl011" } dw_apb_uart = { path = "../../crates/dw_apb_uart" } -dtb = {path = "../../crates/dtb" } +dtb = { path = "../../crates/dtb" } [build-dependencies] ruxconfig = { path = "../ruxconfig" } diff --git a/modules/ruxhal/src/lib.rs b/modules/ruxhal/src/lib.rs index fcb78a72d..7ecc3b81b 100644 --- a/modules/ruxhal/src/lib.rs +++ b/modules/ruxhal/src/lib.rs @@ -94,3 +94,55 @@ pub use self::platform::platform_init_secondary; /// so we should save cmdline in a buf before this memory is set free #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub static mut COMLINE_BUF: [u8; 256] = [0; 256]; + +#[allow(unused)] +/// read a tty device specified by its name. +pub fn tty_read(buf: &mut [u8], dev_name: &str) -> usize { + #[cfg(not(feature = "tty"))] + { + let mut read_len = 0; + while read_len < buf.len() { + if let Some(c) = console::getchar().map(|c| if c == b'\r' { b'\n' } else { c }) { + buf[read_len] = c; + read_len += 1; + } else { + break; + } + } + read_len + } + + #[cfg(feature = "tty")] + { + tty::tty_read(buf, dev_name) + } +} + +#[cfg(feature = "alloc")] +extern crate alloc; + +/// get all tty devices' names. +#[cfg(feature = "alloc")] +pub fn get_all_device_names() -> alloc::vec::Vec { + #[cfg(feature = "tty")] + { + tty::get_all_device_names() + } + #[cfg(not(feature = "tty"))] + { + alloc::vec![alloc::string::String::from("notty")] + } +} + +/// write a tty device specified by its name. +pub fn tty_write(buf: &[u8], _dev_name: &str) -> usize { + #[cfg(feature = "tty")] + { + tty::tty_write(buf, _dev_name) + } + #[cfg(not(feature = "tty"))] + { + console::write_bytes(buf); + return buf.len(); + } +} diff --git a/modules/ruxhal/src/platform/aarch64_common/pl011.rs b/modules/ruxhal/src/platform/aarch64_common/pl011.rs index 577371850..a311f9834 100644 --- a/modules/ruxhal/src/platform/aarch64_common/pl011.rs +++ b/modules/ruxhal/src/platform/aarch64_common/pl011.rs @@ -100,10 +100,24 @@ pub fn init_early() { UART.inner.lock().init(); } +#[cfg(feature = "tty")] +static DRIVER_INDEX: lazy_init::LazyInit = lazy_init::LazyInit::new(); +#[cfg(feature = "tty")] +static DEV_INDEX: lazy_init::LazyInit = lazy_init::LazyInit::new(); + /// Set UART IRQ Enable pub fn init() { #[cfg(feature = "irq")] { + #[cfg(feature = "tty")] + { + let ops = tty::TtyDriverOps { putchar }; + let driver_index = tty::register_driver(ops, "ttyS"); + let dev_index = tty::register_device(driver_index); + assert_ne!(dev_index, -1); + DRIVER_INDEX.init_by(driver_index); + DEV_INDEX.init_by(dev_index as _); + } crate::irq::register_handler(crate::platform::irq::UART_IRQ_NUM, irq_handler); crate::irq::set_enable(crate::platform::irq::UART_IRQ_NUM, true); } @@ -116,8 +130,22 @@ pub fn irq_handler() { let is_receive_interrupt = dev.is_receive_interrupt(); if is_receive_interrupt { dev.ack_interrupts(); + #[cfg(not(feature = "tty"))] while let Some(c) = dev.getchar() { UART.buffer.lock().push(c); } + #[cfg(feature = "tty")] + { + let mut buf = [0u8; 128]; + let mut len = 0; + + while let Some(c) = dev.getchar() { + buf[len] = c; + len += 1; + } + let drv_idx = *DRIVER_INDEX.try_get().unwrap(); + let dev_idx = *DEV_INDEX.try_get().unwrap(); + tty::tty_receive_buf(drv_idx, dev_idx, &buf[..len]); + } } } diff --git a/modules/axnet/Cargo.toml b/modules/ruxnet/Cargo.toml similarity index 74% rename from modules/axnet/Cargo.toml rename to modules/ruxnet/Cargo.toml index 7f40b6bf0..3c81a2510 100644 --- a/modules/axnet/Cargo.toml +++ b/modules/ruxnet/Cargo.toml @@ -1,15 +1,16 @@ [package] -name = "axnet" +name = "ruxnet" version = "0.1.0" edition = "2021" -authors = ["Yuekai Jia ", "ChengXiang Qi "] +authors = ["Yuekai Jia ", "ChengXiang Qi ", "HaoWen Liu "] 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" +repository = "https://github.com/rcore-os/arceos/tree/main/modules/ruxnet" +documentation = "https://rcore-os.github.io/arceos/ruxnet/index.html" [features] +lwip = ["dep:lwip_rust"] smoltcp = [] default = ["smoltcp"] @@ -19,11 +20,15 @@ cfg-if = "1.0" spin = "0.9" driver_net = { path = "../../crates/driver_net" } lazy_init = { path = "../../crates/lazy_init" } +lwip_rust = { path = "../../crates/lwip_rust", optional = true } +printf-compat = { version = "0.1", default-features = false, optional = true } axerrno = { path = "../../crates/axerrno" } ruxhal = { path = "../ruxhal" } axsync = { path = "../axsync" } +axlog = { path = "../axlog" } ruxtask = { path = "../ruxtask" } ruxdriver = { path = "../ruxdriver", features = ["net"] } +cty = { version = "0.2.2", optional = true } axio = { path = "../../crates/axio" } [dependencies.smoltcp] diff --git a/modules/axnet/src/lib.rs b/modules/ruxnet/src/lib.rs similarity index 72% rename from modules/axnet/src/lib.rs rename to modules/ruxnet/src/lib.rs index e744facda..a8414b105 100644 --- a/modules/axnet/src/lib.rs +++ b/modules/ruxnet/src/lib.rs @@ -27,23 +27,34 @@ //! [smoltcp]: https://github.com/smoltcp-rs/smoltcp #![no_std] +#![feature(c_variadic)] #![feature(ip_in_core)] +#![feature(ip_bits)] #![feature(new_uninit)] +#![feature(inherent_associated_types)] #[macro_use] extern crate log; extern crate alloc; cfg_if::cfg_if! { - if #[cfg(feature = "smoltcp")] { + if #[cfg(feature = "lwip")] { + mod lwip_impl; + use lwip_impl as net_impl; + pub use lwip_impl::{IpAddr, Ipv4Addr, SocketAddr}; + } + else if #[cfg(feature = "smoltcp")] { mod smoltcp_impl; use smoltcp_impl as net_impl; + pub use self::net_impl::{bench_receive, bench_transmit}; + } + else { + error!("No network stack is selected"); } } 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 ruxdriver::{prelude::*, AxDeviceContainer}; @@ -54,5 +65,14 @@ pub fn init_network(mut net_devs: AxDeviceContainer) { let dev = net_devs.take_one().expect("No NIC device found!"); info!(" use NIC 0: {:?}", dev.device_name()); + cfg_if::cfg_if! { + if #[cfg(feature = "lwip")] { + info!(" net stack: lwip"); + } else if #[cfg(feature = "smoltcp")] { + info!(" net stack: smoltcp"); + } else { + compile_error!("No network stack is selected"); + } + } net_impl::init(dev); } diff --git a/modules/ruxnet/src/lwip_impl/addr.rs b/modules/ruxnet/src/lwip_impl/addr.rs new file mode 100644 index 000000000..0cf41a737 --- /dev/null +++ b/modules/ruxnet/src/lwip_impl/addr.rs @@ -0,0 +1,325 @@ +use core::{ + fmt::{self, Error}, + str::FromStr, +}; + +use lwip_rust::bindings::{ + ip4_addr_t, ip_addr_t, lwip_ip_addr_type_IPADDR_TYPE_V4, lwip_ip_addr_type_IPADDR_TYPE_V6, +}; + +use core::net::{ + Ipv4Addr as CoreIpv4Addr, Ipv6Addr as CoreIpv6Addr, SocketAddr as CoreSocketAddr, SocketAddrV4, + SocketAddrV6, +}; + +/// Mac Address +#[derive(Clone, Copy, Debug, Default)] +pub struct MacAddr(pub [u8; 6]); + +/// IP Address, either IPv4 or IPv6 +#[derive(Clone, Copy, Debug)] +pub enum IpAddr { + /// IPv4 Address + Ipv4(Ipv4Addr), + + /// IPv6 Address + Ipv6(Ipv6Addr), +} + +/// IPv4 Address (host byte order) +#[derive(Clone, Copy, Debug, Default)] +pub struct Ipv4Addr(pub [u8; 4]); + +/// IPv6 Address +#[derive(Clone, Copy, Debug, Default)] +pub struct Ipv6Addr { + /// Address in host byte order + pub addr: [u32; 4usize], + + /// Zone identifier + pub zone: u8, +} + +/// Socket Address (IP Address + Port) +#[derive(Clone, Copy, Debug)] +pub struct SocketAddr { + /// IP Address + pub addr: IpAddr, + + /// Port + pub port: u16, +} + +impl MacAddr { + /// Create a new MacAddr from a byte array + pub fn from_bytes(bytes: &[u8]) -> MacAddr { + let mut addr = [0u8; 6]; + addr.copy_from_slice(bytes); + MacAddr(addr) + } +} + +impl fmt::Display for MacAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{:02x}-{:02x}-{:02x}-{:02x}-{:02x}-{:02x}", + self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5] + ) + } +} + +impl IpAddr { + /// Get the IP Address as a byte array + pub fn as_bytes(&self) -> &[u8] { + match self { + IpAddr::Ipv4(Ipv4Addr(addr)) => unsafe { &addr[..] }, + _ => panic!("IPv6 not supported"), + } + } +} + +impl From for IpAddr { + fn from(addr: Ipv4Addr) -> IpAddr { + IpAddr::Ipv4(addr) + } +} + +impl From for IpAddr { + fn from(addr: Ipv6Addr) -> IpAddr { + IpAddr::Ipv6(addr) + } +} + +impl FromStr for IpAddr { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut parts = s.split('.'); + let mut addr: u32 = 0; + for i in 0..4 { + let part = parts.next().ok_or(())?; + let part = part.parse::().map_err(|_| ())?; + addr |= (part as u32) << (8 * i); + } + Ok(IpAddr::Ipv4(Ipv4Addr(addr.to_be_bytes()))) + } +} + +impl From for ip_addr_t { + fn from(val: IpAddr) -> Self { + match val { + IpAddr::Ipv4(Ipv4Addr(addr)) => ip_addr_t { + addr: Ipv4Addr(addr).to_u32(), + }, + _ => panic!("IPv6 not supported"), + } + } +} + +impl From for IpAddr { + #[allow(non_upper_case_globals)] + fn from(addr: ip_addr_t) -> IpAddr { + IpAddr::Ipv4(Ipv4Addr(unsafe { addr.addr.to_be_bytes() })) + } +} + +impl fmt::Display for IpAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + IpAddr::Ipv4(ipv4_addr) => write!(f, "{ipv4_addr}"), + _ => panic!("IPv6 not supported"), + } + } +} + +impl Ipv4Addr { + /// Construct an IPv4 address from parts. + pub fn new(a0: u8, a1: u8, a2: u8, a3: u8) -> Ipv4Addr { + Self::from_bytes(&[a0, a1, a2, a3]) + } + + /// Create a new Ipv4Addr from a byte array + pub fn from_bytes(bytes: &[u8]) -> Ipv4Addr { + let mut addr: u32 = 0; + for (i, &b) in bytes.iter().enumerate().take(4) { + addr |= (b as u32) << (8 * i); + } + Ipv4Addr(addr.to_be_bytes()) + } + + ///convert Ipv4Addr to u32 + pub fn to_u32(&self) -> u32 { + let mut addr: u32 = 0; + for (i, &b) in self.0.iter().enumerate().take(4) { + addr |= (b as u32) << (8 * i); + } + addr + } +} + +impl fmt::Display for Ipv4Addr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let bytes = self.to_u32(); + write!( + f, + "{}.{}.{}.{}", + bytes & 0xff, + (bytes >> 8) & 0xff, + (bytes >> 16) & 0xff, + (bytes >> 24) & 0xff + ) + } +} + +// Reference: https://github.com/smoltcp-rs/smoltcp/blob/9027825c16c9c3fbadb7663e56d64b590fc95d5a/src/wire/ipv6.rs#L247-L306 +// Modified to use [u32; 4] instead of [u8; 16] +impl fmt::Display for Ipv6Addr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // The string representation of an IPv6 address should + // collapse a series of 16 bit sections that evaluate + // to 0 to "::" + // + // See https://tools.ietf.org/html/rfc4291#section-2.2 + // for details. + enum State { + Head, + HeadBody, + Tail, + TailBody, + } + let mut words = [0u16; 8]; + for i in 0..4 { + words[i * 2] = ((self.addr[i] & 0xffff) as u16).swap_bytes(); + words[i * 2 + 1] = (((self.addr[i] >> 16) & 0xffff) as u16).swap_bytes(); + } + let mut state = State::Head; + for word in words.iter() { + state = match (*word, &state) { + // Once a u16 equal to zero write a double colon and + // skip to the next non-zero u16. + (0, &State::Head) | (0, &State::HeadBody) => { + write!(f, "::")?; + State::Tail + } + // Continue iterating without writing any characters until + // we hit a non-zero value. + (0, &State::Tail) => State::Tail, + // When the state is Head or Tail write a u16 in hexadecimal + // without the leading colon if the value is not 0. + (_, &State::Head) => { + write!(f, "{word:x}")?; + State::HeadBody + } + (_, &State::Tail) => { + write!(f, "{word:x}")?; + State::TailBody + } + // Write the u16 with a leading colon when parsing a value + // that isn't the first in a section + (_, &State::HeadBody) | (_, &State::TailBody) => { + write!(f, ":{word:x}")?; + state + } + } + } + Ok(()) + } +} + +impl From for CoreSocketAddr { + fn from(socket_addr: SocketAddr) -> Self { + match socket_addr.addr { + IpAddr::Ipv4(ipv4_addr) => { + let octets = ipv4_addr.0; + let std_ipv4 = CoreIpv4Addr::new(octets[0], octets[1], octets[2], octets[3]); + CoreSocketAddr::V4(SocketAddrV4::new(std_ipv4, socket_addr.port)) + } + _ => panic!("IPv6 not supported"), + } + } +} + +impl From for SocketAddr { + fn from(std_socket_addr: CoreSocketAddr) -> Self { + match std_socket_addr { + CoreSocketAddr::V4(v4_addr) => { + let octets = v4_addr.ip().octets(); + let ipv4_addr = Ipv4Addr(u32::from_be_bytes(octets).to_be_bytes()); + SocketAddr { + addr: IpAddr::Ipv4(ipv4_addr), + port: v4_addr.port(), + } + } + _ => panic!("IPv6 not supported"), + } + } +} + +impl FromStr for SocketAddr { + type Err = (); + + fn from_str(s: &str) -> Result { + let mut parts = s.split(':'); + let addr = parts.next().ok_or(())?.parse::()?; + let port = parts.next().ok_or(())?.parse::().map_err(|_| ())?; + Ok(SocketAddr { addr, port }) + } +} + +impl SocketAddr { + /// Create a new SocketAddr from an IpAddr and a port + pub fn new(addr: IpAddr, port: u16) -> SocketAddr { + SocketAddr { addr, port } + } +} + +impl From<(IpAddr, u16)> for SocketAddr { + fn from((addr, port): (IpAddr, u16)) -> SocketAddr { + SocketAddr { addr, port } + } +} + +impl From<(Ipv4Addr, u16)> for SocketAddr { + fn from((addr, port): (Ipv4Addr, u16)) -> SocketAddr { + SocketAddr { + addr: IpAddr::Ipv4(addr), + port, + } + } +} + +impl From<(Ipv6Addr, u16)> for SocketAddr { + fn from((addr, port): (Ipv6Addr, u16)) -> SocketAddr { + SocketAddr { + addr: IpAddr::Ipv6(addr), + port, + } + } +} + +impl fmt::Display for SocketAddr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}:{}", self.addr, self.port) + } +} + +/// Convert a mask to a prefix length +pub fn mask_to_prefix(mask: IpAddr) -> Result { + match mask { + IpAddr::Ipv4(Ipv4Addr(mask)) => { + let mut mask = Ipv4Addr(mask).to_u32(); + let mut prefix = 0; + while mask & (1 << 31) != 0 { + prefix += 1; + mask <<= 1; + } + if mask != 0 { + Err(Error) + } else { + Ok(prefix) + } + } + _ => panic!("IPv6 not supported"), + } +} diff --git a/modules/ruxnet/src/lwip_impl/dns.rs b/modules/ruxnet/src/lwip_impl/dns.rs new file mode 100644 index 000000000..829775745 --- /dev/null +++ b/modules/ruxnet/src/lwip_impl/dns.rs @@ -0,0 +1,104 @@ +use super::driver::lwip_loop_once; +use crate::{IpAddr, Ipv4Addr}; +use alloc::{ffi::CString, vec, vec::Vec}; +use axerrno::{ax_err, AxResult}; +use core::{ + ffi::{c_char, c_void, CStr}, + str::FromStr, +}; +use lwip_rust::bindings::{ + dns_gethostbyname, dns_setserver, err_enum_t_ERR_ARG, err_enum_t_ERR_INPROGRESS, + err_enum_t_ERR_OK, err_enum_t_ERR_VAL, ip_addr_t, +}; +use ruxtask::yield_now; + +use super::LWIP_MUTEX; + +struct DnsQueryEntry { + ipaddr: Option, + finished: bool, +} + +extern "C" fn dns_found_callback( + name: *const c_char, + ipaddr: *const ip_addr_t, + callback_arg: *mut c_void, +) { + trace!( + "[dns_found_callback]: name_ptr={:?} ipaddr_ptr={:?}", + name, + ipaddr + ); + let res = callback_arg as *mut DnsQueryEntry; + unsafe { + (*res).finished = true; + (*res).ipaddr = if ipaddr.is_null() { + None + } else { + debug!( + "DNS found: name={} ipaddr={}", + CStr::from_ptr(name as *mut c_char).to_str().unwrap(), + IpAddr::from(*ipaddr) + ); + Some((*ipaddr).into()) + }; + } +} + +/// Public function for DNS query. +pub fn resolve_socket_addr(name: &str) -> AxResult> { + let guard = LWIP_MUTEX.lock(); + unsafe { + dns_setserver( + 0, + &IpAddr::from_str("8.8.8.8").unwrap().into() as *const ip_addr_t, + ) + }; + + let mut addr: ip_addr_t = IpAddr::Ipv4(Ipv4Addr(0u32.to_be_bytes())).into(); + let mut query_entry = DnsQueryEntry { + ipaddr: None, + finished: false, + }; + let name = CString::new(name).unwrap(); + let res = unsafe { + dns_gethostbyname( + name.as_ptr(), + &mut addr as *mut ip_addr_t, + Some(dns_found_callback), + &mut query_entry as *mut DnsQueryEntry as *mut c_void, + ) as i32 + }; + drop(guard); + + #[allow(non_upper_case_globals)] + match res { + err_enum_t_ERR_OK => Ok(vec![addr.into()]), + err_enum_t_ERR_INPROGRESS => loop { + lwip_loop_once(); + if query_entry.finished { + break if query_entry.ipaddr.is_some() { + Ok(vec![query_entry.ipaddr.unwrap()]) + } else { + ax_err!(NotFound, "LWIP dns not found") + }; + } + yield_now(); + }, + err_enum_t_ERR_ARG => ax_err!( + InvalidInput, + "LWIP dns client not initialized or invalid hostname" + ), + err_enum_t_ERR_VAL => ax_err!( + InvalidInput, + "LWIP dns client error, perhaps dns server not configured" + ), + _ => ax_err!(InvalidInput, "LWIP dns client error"), + } +} + +/// Public function for DNS query. +pub fn dns_query(name: &str) -> AxResult> { + let empty_vec = alloc::vec::Vec::new(); + Ok(empty_vec) +} diff --git a/modules/ruxnet/src/lwip_impl/driver.rs b/modules/ruxnet/src/lwip_impl/driver.rs new file mode 100644 index 000000000..164ea7289 --- /dev/null +++ b/modules/ruxnet/src/lwip_impl/driver.rs @@ -0,0 +1,270 @@ +use super::LWIP_MUTEX; +use crate::{ + net_impl::addr::{mask_to_prefix, MacAddr}, + IpAddr, +}; +use alloc::{boxed::Box, collections::VecDeque, sync::Arc}; +#[cfg(feature = "irq")] +use axdriver::register_interrupt_handler; +use axsync::Mutex; +use core::{cell::RefCell, ffi::c_void}; +use driver_net::{DevError, NetBuf, NetBufBox, NetBufPool, NetBufPtr}; +use lazy_init::LazyInit; +use lwip_rust::bindings::{ + err_enum_t_ERR_MEM, err_enum_t_ERR_OK, err_t, etharp_output, ethernet_input, ip4_addr_t, + lwip_htonl, lwip_init, netif, netif_add, netif_set_default, netif_set_link_up, netif_set_up, + pbuf, pbuf_free, rx_custom_pbuf_alloc, rx_custom_pbuf_free, rx_custom_pbuf_init, + rx_custom_pbuf_t, sys_check_timeouts, NETIF_FLAG_BROADCAST, NETIF_FLAG_ETHARP, + NETIF_FLAG_ETHERNET, +}; +use ruxdriver::prelude::*; + +const RX_BUF_QUEUE_SIZE: usize = 64; + +const NET_BUF_LEN: usize = 1526; +const NET_BUF_POOL_SIZE: usize = 128; + +struct NetifWrapper(netif); +unsafe impl Send for NetifWrapper {} + +struct DeviceWrapper { + inner: RefCell, // use `RefCell` is enough since it's wrapped in `Mutex` in `InterfaceWrapper`. + rx_buf_queue: VecDeque, +} + +impl DeviceWrapper { + fn new(inner: AxNetDevice) -> Self { + Self { + inner: RefCell::new(inner), + rx_buf_queue: VecDeque::with_capacity(RX_BUF_QUEUE_SIZE), + } + } + + fn poll(&mut self) { + while self.rx_buf_queue.len() < RX_BUF_QUEUE_SIZE { + match self.inner.borrow_mut().receive() { + Ok(bufptr) => unsafe { + self.rx_buf_queue.push_back(NetBuf::from_buf_ptr(bufptr)); + }, + Err(DevError::Again) => break, // TODO: better method to avoid error type conversion + Err(err) => { + warn!("receive failed: {:?}", err); + break; + } + } + } + } + + fn receive(&mut self) -> Option { + self.rx_buf_queue.pop_front() + } + + #[cfg(feature = "irq")] + fn ack_interrupt(&mut self) -> bool { + unsafe { self.inner.as_ptr().as_mut().unwrap().ack_interrupt() } + } +} + +struct InterfaceWrapper { + name: &'static str, + dev: Arc>, + netif: Mutex, +} + +impl InterfaceWrapper { + pub fn name(&self) -> &str { + self.name + } + + pub fn poll(&self) { + self.dev.lock().poll(); + loop { + let buf_receive = self.dev.lock().receive(); + if let Some(buf) = buf_receive { + trace!("RECV {} bytes: {:02X?}", buf.packet().len(), buf.packet()); + + let length = buf.packet().len(); + let payload_mem = buf.packet().as_ptr() as *mut _; + let payload_mem_len = buf.capacity() as u16; + let p = unsafe { + rx_custom_pbuf_alloc( + Some(pbuf_free_custom), + Box::into_raw(buf) as *mut _, + Arc::into_raw(self.dev.clone()) as *mut _, + length as u16, + payload_mem, + payload_mem_len, + ) + }; + + debug!("ethernet_input"); + let mut netif = self.netif.lock(); + unsafe { + let res = netif.0.input.unwrap()(p, &mut netif.0); + if (res as i32) != err_enum_t_ERR_OK { + warn!("ethernet_input failed: {:?}", res); + pbuf_free(p); + } + } + } else { + break; + } + } + } + + #[cfg(feature = "irq")] + pub fn ack_interrupt(&self) { + unsafe { &mut *self.dev.as_mut_ptr() }.ack_interrupt(); + } +} + +extern "C" fn pbuf_free_custom(p: *mut pbuf) { + trace!("pbuf_free_custom: {:x?}", p); + let p = p as *mut rx_custom_pbuf_t; + let buf = unsafe { Box::from_raw((*p).buf as *mut NetBuf) }; + let dev = unsafe { Arc::from_raw((*p).dev as *const Mutex) }; + match dev + .lock() + .inner + .borrow_mut() + .recycle_rx_buffer(NetBuf::into_buf_ptr(buf)) + { + Ok(_) => (), + Err(err) => { + warn!("recycle_rx_buffer failed: {:?}", err); + } + }; + unsafe { + rx_custom_pbuf_free(p); + }; +} + +extern "C" fn ethif_init(netif: *mut netif) -> err_t { + trace!("ethif_init"); + unsafe { + (*netif).name[0] = 'e' as i8; + (*netif).name[1] = 'n' as i8; + (*netif).num = 0; + + (*netif).output = Some(etharp_output); + (*netif).linkoutput = Some(ethif_output); + + (*netif).mtu = 1500; + (*netif).flags = 0; + (*netif).flags = (NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET) as u8; + } + err_enum_t_ERR_OK as err_t +} + +extern "C" fn ethif_output(netif: *mut netif, p: *mut pbuf) -> err_t { + trace!("ethif_output"); + let ethif = unsafe { &mut *((*netif).state as *mut _ as *mut InterfaceWrapper) }; + let dev_wrapper = ethif.dev.lock(); + let mut dev = dev_wrapper.inner.borrow_mut(); + + if dev.can_transmit() { + unsafe { + let tot_len = unsafe { (*p).tot_len }; + let mut tx_buf = *NetBuf::from_buf_ptr(dev.alloc_tx_buffer(tot_len.into()).unwrap()); + dev.prepare_tx_buffer(&mut tx_buf, tot_len.into()).unwrap(); + + // Copy pbuf chain to tx_buf + let mut offset = 0; + let mut q = p; + while !q.is_null() { + let len = unsafe { (*q).len } as usize; + let payload = unsafe { (*q).payload }; + let payload = unsafe { core::slice::from_raw_parts(payload as *const u8, len) }; + tx_buf.packet_mut()[offset..offset + len].copy_from_slice(payload); + offset += len; + q = unsafe { (*q).next }; + } + + trace!( + "SEND {} bytes: {:02X?}", + tx_buf.packet().len(), + tx_buf.packet() + ); + dev.transmit(NetBuf::into_buf_ptr(Box::new(tx_buf))) + .unwrap(); + err_enum_t_ERR_OK as err_t + } + } else { + error!("[ethif_output] dev can't transmit"); + err_enum_t_ERR_MEM as err_t + } +} + +static ETH0: LazyInit = LazyInit::new(); + +/// 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() { + ETH0.poll(); +} + +fn ip4_addr_gen(a: u8, b: u8, c: u8, d: u8) -> ip4_addr_t { + ip4_addr_t { + addr: unsafe { + lwip_htonl(((a as u32) << 24) | ((b as u32) << 16) | ((c as u32) << 8) | (d as u32)) + }, + } +} + +pub fn init(mut net_dev: AxNetDevice) { + LWIP_MUTEX.init_by(Mutex::new(0)); + let _guard = LWIP_MUTEX.lock(); + + let ipaddr: ip4_addr_t = ip4_addr_gen(10, 0, 2, 15); // QEMU user networking default IP + let netmask: ip4_addr_t = ip4_addr_gen(255, 255, 255, 0); + let gw: ip4_addr_t = ip4_addr_gen(10, 0, 2, 2); // QEMU user networking gateway + + let dev = net_dev; + let mut netif: netif = unsafe { core::mem::zeroed() }; + netif.hwaddr_len = 6; + netif.hwaddr = dev.mac_address().0; + + ETH0.init_by(InterfaceWrapper { + name: "eth0", + dev: Arc::new(Mutex::new(DeviceWrapper::new(dev))), + netif: Mutex::new(NetifWrapper(netif)), + }); + + unsafe { + lwip_init(); + rx_custom_pbuf_init(); + netif_add( + &mut ETH0.netif.lock().0, + &ipaddr, + &netmask, + &gw, + Ð0 as *const _ as *mut c_void, + Some(ethif_init), + Some(ethernet_input), + ); + netif_set_link_up(&mut ETH0.netif.lock().0); + netif_set_up(&mut ETH0.netif.lock().0); + netif_set_default(&mut ETH0.netif.lock().0); + } + + info!("created net interface {:?}:", ETH0.name()); + info!( + " ether: {}", + MacAddr::from_bytes(Ð0.netif.lock().0.hwaddr) + ); + let ip = IpAddr::from(ETH0.netif.lock().0.ip_addr); + let mask = mask_to_prefix(IpAddr::from(ETH0.netif.lock().0.netmask)).unwrap(); + info!(" ip: {}/{}", ip, mask); + info!(" gateway: {}", IpAddr::from(ETH0.netif.lock().0.gw)); +} + +pub fn lwip_loop_once() { + let guard = LWIP_MUTEX.lock(); + unsafe { + ETH0.poll(); + sys_check_timeouts(); + } + drop(guard); +} diff --git a/modules/ruxnet/src/lwip_impl/mod.rs b/modules/ruxnet/src/lwip_impl/mod.rs new file mode 100644 index 000000000..ccc72eca6 --- /dev/null +++ b/modules/ruxnet/src/lwip_impl/mod.rs @@ -0,0 +1,26 @@ +mod addr; +mod dns; +mod driver; +mod tcp; +mod udp; + +pub use self::addr::{IpAddr, Ipv4Addr, SocketAddr}; +pub use self::dns::{dns_query, resolve_socket_addr}; +pub use self::driver::{init, poll_interfaces}; +pub use self::tcp::TcpSocket; +pub use self::udp::UdpSocket; +use core::ffi::c_uint; +use ruxhal::time::current_time; + +use axsync::Mutex; +use lazy_init::LazyInit; + +static LWIP_MUTEX: LazyInit> = LazyInit::new(); + +const RECV_QUEUE_LEN: usize = 16; +const ACCEPT_QUEUE_LEN: usize = 16; + +#[no_mangle] +extern "C" fn sys_now() -> c_uint { + current_time().as_millis() as c_uint +} diff --git a/modules/ruxnet/src/lwip_impl/tcp.rs b/modules/ruxnet/src/lwip_impl/tcp.rs new file mode 100644 index 000000000..2c621f0eb --- /dev/null +++ b/modules/ruxnet/src/lwip_impl/tcp.rs @@ -0,0 +1,495 @@ +use crate::{ + net_impl::{driver::lwip_loop_once, ACCEPT_QUEUE_LEN, RECV_QUEUE_LEN}, + IpAddr, SocketAddr, +}; +use alloc::{boxed::Box, collections::VecDeque}; +use axerrno::{ax_err, AxError, AxResult}; +use axio::PollState; +use axsync::Mutex; +use core::cell::UnsafeCell; +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use core::{ffi::c_void, pin::Pin, ptr::null_mut}; +use lwip_rust::bindings::{ + err_enum_t_ERR_MEM, err_enum_t_ERR_OK, err_enum_t_ERR_USE, err_enum_t_ERR_VAL, err_t, + ip_addr_t, pbuf, pbuf_free, tcp_accept, tcp_arg, tcp_bind, tcp_close, tcp_connect, + tcp_listen_with_backlog, tcp_new, tcp_output, tcp_pcb, tcp_recv, tcp_recved, tcp_state_CLOSED, + tcp_state_LISTEN, tcp_write, TCP_DEFAULT_LISTEN_BACKLOG, TCP_MSS, +}; +use ruxtask::yield_now; + +use super::LWIP_MUTEX; + +struct TcpPcbPointer(Mutex<*mut tcp_pcb>); + +unsafe impl Sync for TcpPcbPointer {} + +unsafe impl Send for TcpPcbPointer {} +impl TcpPcbPointer { + unsafe fn get(&self) -> *mut tcp_pcb { + *self.0.lock() + } + + unsafe fn set(&self, new_pcb: *mut tcp_pcb) { + *self.0.lock() = new_pcb; + } +} +struct PbuffPointer(*mut pbuf); +unsafe impl Send for PbuffPointer {} + +struct TcpSocketInner { + nonblock: AtomicBool, + remote_closed: bool, + connect_result: UnsafeCell, + recv_queue: Mutex>, + accept_queue: Mutex>, +} + +/// A TCP socket that provides POSIX-like APIs. +pub struct TcpSocket { + pcb: TcpPcbPointer, + inner: Pin>, +} + +extern "C" fn connect_callback(arg: *mut c_void, _tpcb: *mut tcp_pcb, err: err_t) -> err_t { + debug!("[TcpSocket] connect_callback: {:#?}", err); + let socket_inner = unsafe { &mut *(arg as *mut TcpSocketInner) }; + socket_inner.connect_result = err.into(); + err +} + +extern "C" fn recv_callback( + arg: *mut c_void, + _tpcb: *mut tcp_pcb, + p: *mut pbuf, + err: err_t, +) -> err_t { + debug!("[TcpSocket] recv_callback: {:#?}", err); + if err != 0 { + error!("[TcpSocket][recv_callback] err: {:#?}", err); + return err; + } + let socket_inner = unsafe { &mut *(arg as *mut TcpSocketInner) }; + if p.is_null() { + debug!("[TcpSocket][recv_callback] p is null, remote close"); + socket_inner.remote_closed = true; + } else { + debug!( + "[TcpSocket][recv_callback] p is not null, len: {}, tot_len: {}", + unsafe { (*p).len }, + unsafe { (*p).tot_len } + ); + socket_inner + .recv_queue + .lock() + .push_back((PbuffPointer(p), 0)); + debug!( + "[TcpSocket][recv_callback] recv_queue len: {}", + socket_inner.recv_queue.lock().len() + ); + } + 0 +} + +extern "C" fn accept_callback(arg: *mut c_void, newpcb: *mut tcp_pcb, err: err_t) -> err_t { + if err != 0 { + debug!("[TcpSocket][accept_callback] err: {:#?}", err); + return err; + } + let socket_inner = unsafe { &mut *(arg as *mut TcpSocketInner) }; + let mut socket = TcpSocket { + pcb: TcpPcbPointer(Mutex::new(newpcb)), + inner: Box::pin(TcpSocketInner { + nonblock: AtomicBool::new(false), + remote_closed: false, + connect_result: 0.into(), + recv_queue: Mutex::new(VecDeque::with_capacity(RECV_QUEUE_LEN)), + accept_queue: Mutex::new(VecDeque::new()), + }), + }; + unsafe { + tcp_arg( + socket.pcb.get(), + socket.inner.as_mut().get_mut() as *mut _ as *mut c_void, + ); + tcp_recv(socket.pcb.get(), Some(recv_callback)); + } + socket_inner.accept_queue.lock().push_back(socket); + debug!( + "[TcpSocket][accept_callback] accept_queue len: {}", + socket_inner.accept_queue.lock().len() + ); + 0 +} + +impl TcpSocket { + /// Creates a new TCP socket. + pub fn new() -> Self { + let guard = LWIP_MUTEX.lock(); + let mut socket = Self { + pcb: TcpPcbPointer(Mutex::new(unsafe { tcp_new() })), + inner: Box::pin(TcpSocketInner { + nonblock: AtomicBool::new(false), + remote_closed: false, + connect_result: 0.into(), + recv_queue: Mutex::new(VecDeque::new()), + accept_queue: Mutex::new(VecDeque::with_capacity(ACCEPT_QUEUE_LEN)), + }), + }; + unsafe { + tcp_arg( + socket.pcb.get(), + socket.inner.as_mut().get_mut() as *mut _ as *mut c_void, + ); + } + drop(guard); + socket + } + + /// Returns the local address and port, or + /// [`Err(NotConnected)`](AxError::NotConnected) if not connected. + pub fn local_addr(&self) -> AxResult { + if unsafe { self.pcb.get().is_null() } { + Err(AxError::NotConnected) + } else { + let guard = LWIP_MUTEX.lock(); + let addr = unsafe { (*self.pcb.get()).local_ip }; + let port = unsafe { (*self.pcb.get()).local_port }; + drop(guard); + trace!( + "[TcpSocket] local_addr: {:#?}:{:#?}", + IpAddr::from(addr), + port + ); + Ok(SocketAddr { + addr: addr.into(), + port, + } + .into()) + } + } + + /// Returns the remote address and port, or + /// [`Err(NotConnected)`](AxError::NotConnected) if not connected. + pub fn peer_addr(&self) -> AxResult { + if unsafe { self.pcb.get().is_null() } { + Err(AxError::NotConnected) + } else { + let guard = LWIP_MUTEX.lock(); + let addr = unsafe { (*self.pcb.get()).remote_ip }; + let port = unsafe { (*self.pcb.get()).remote_port }; + drop(guard); + trace!( + "[TcpSocket] peer_addr: {:#?}:{:#?}", + IpAddr::from(addr), + port + ); + Ok(SocketAddr { + addr: addr.into(), + port, + } + .into()) + } + } + + /// Returns whether this socket is in nonblocking mode. + #[inline] + pub fn is_nonblocking(&self) -> bool { + self.inner.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. + pub fn set_nonblocking(&self, nonblocking: bool) { + self.inner.nonblock.store(nonblocking, Ordering::Release); + } + + /// Connects to the given address and port. + /// + /// The local port is generated automatically. + pub fn connect(&self, caddr: core::net::SocketAddr) -> AxResult { + let addr = SocketAddr::from(caddr); + debug!("[TcpSocket] connect to {:#?}", addr); + let ip_addr: ip_addr_t = addr.addr.into(); + unsafe { + self.inner.connect_result.get().write(1); + } + + // lock lwip + let guard = LWIP_MUTEX.lock(); + unsafe { + debug!("[TcpSocket] set recv_callback"); + tcp_recv(self.pcb.get(), Some(recv_callback)); + + debug!("[TcpSocket] tcp_connect"); + #[allow(non_upper_case_globals)] + match tcp_connect(self.pcb.get(), &ip_addr, addr.port, Some(connect_callback)) as i32 { + err_enum_t_ERR_OK => {} + err_enum_t_ERR_VAL => { + return ax_err!(InvalidInput, "LWIP [tcp_connect] Invalid input."); + } + _ => { + return ax_err!(Unsupported, "LWIP [tcp_connect] Failed."); + } + }; + } + drop(guard); + + // wait for connect + debug!("[TcpSocket] wait for connect"); + lwip_loop_once(); + #[allow(clippy::while_immutable_condition)] + while unsafe { self.inner.connect_result.get().read() == 1 } { + yield_now(); + lwip_loop_once(); + } + debug!("[TcpSocket] connect result: {}", unsafe { + self.inner.connect_result.get().read() + }); + + if unsafe { self.inner.connect_result.get().read() == 0 } { + Ok(()) + } else { + ax_err!(Unsupported, "LWIP [connect_result] Unsupported") + } + } + + /// 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, caddr: core::net::SocketAddr) -> AxResult { + let addr = SocketAddr::from(caddr); + debug!("[TcpSocket] bind to {:#?}", addr); + let guard = LWIP_MUTEX.lock(); + unsafe { + #[allow(non_upper_case_globals)] + match tcp_bind(self.pcb.get(), &addr.addr.into(), addr.port) as i32 { + err_enum_t_ERR_OK => {} + err_enum_t_ERR_USE => { + return ax_err!(AddrInUse, "LWIP [tcp_bind] Port already in use."); + } + err_enum_t_ERR_VAL => { + return ax_err!( + InvalidInput, + "LWIP [tcp_bind] The PCB is not in a valid state." + ); + } + _ => { + return ax_err!(Unsupported, "LWIP [tcp_bind] Failed."); + } + }; + } + drop(guard); + Ok(()) + } + + /// 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(&mut self) -> AxResult { + let guard = LWIP_MUTEX.lock(); + unsafe { + if (*self.pcb.get()).state == tcp_state_CLOSED { + let listen_res = + tcp_listen_with_backlog(self.pcb.get(), TCP_DEFAULT_LISTEN_BACKLOG as u8); + self.pcb.set(listen_res); + tcp_arg( + self.pcb.get(), + self.inner.as_mut().get_mut() as *mut _ as *mut c_void, + ); + tcp_accept(self.pcb.get(), Some(accept_callback)); + } + } + drop(guard); + // TODO: check if listen failed + Ok(()) + } + + /// 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 { + loop { + lwip_loop_once(); + let mut accept_queue = self.inner.accept_queue.lock(); + if accept_queue.len() != 0 { + return Ok(accept_queue.pop_front().unwrap()); + } + drop(accept_queue); + if self.is_nonblocking() { + return Err(AxError::WouldBlock); + } else { + yield_now(); + } + } + } + + /// Close the connection. + pub fn shutdown(&self) -> AxResult { + if unsafe { !self.pcb.get().is_null() } { + unsafe { + let _guard = LWIP_MUTEX.lock(); + tcp_arg(self.pcb.get(), null_mut()); + if (*self.pcb.get()).state == tcp_state_LISTEN { + tcp_accept(self.pcb.get(), None); + } else { + tcp_recv(self.pcb.get(), None); + } + + warn!("[TcpSocket] tcp_close"); + #[allow(non_upper_case_globals)] + match tcp_close(self.pcb.get()) as i32 { + err_enum_t_ERR_OK => {} + e => { + error!("LWIP tcp_close failed: {}", e); + return ax_err!(Unsupported, "LWIP [tcp_close] failed"); + } + } + } + unsafe { + self.pcb.set(null_mut()); + } + lwip_loop_once(); + Ok(()) + } else { + Err(AxError::NotConnected) + } + } + + /// Receives data from the socket, stores it in the given buffer. + pub fn recv(&self, buf: &mut [u8], flags: i32) -> AxResult { + loop { + if self.inner.remote_closed { + return Ok(0); + } + lwip_loop_once(); + let mut recv_queue = self.inner.recv_queue.lock(); + let res = if recv_queue.len() == 0 { + Ok(0) + } else { + let (p, offset) = recv_queue.pop_front().unwrap(); + let p = p.0; + let len = unsafe { (*p).len as usize }; + let tot_len = unsafe { (*p).tot_len as usize }; + if len != tot_len { + // TODO: pbuf chain + error!("[TcpSocket] recv pbuf len != tot_len"); + return ax_err!(Unsupported, "LWIP [recv] pbuf len != tot_len"); + } + let payload = unsafe { (*p).payload }; + let payload = unsafe { core::slice::from_raw_parts_mut(payload as *mut u8, len) }; + + let copy_len = core::cmp::min(len - offset, buf.len()); + buf[0..copy_len].copy_from_slice(&payload[offset..offset + copy_len]); + if offset + copy_len < len { + recv_queue.push_front((PbuffPointer(p), offset + copy_len)); + } else { + let guard = LWIP_MUTEX.lock(); + unsafe { + pbuf_free(p); + tcp_recved(self.pcb.get(), len as u16); + } + drop(guard); + } + + Ok(copy_len) + }; + drop(recv_queue); + match res { + Ok(0) => { + if self.is_nonblocking() { + return Err(AxError::WouldBlock); + } else { + yield_now(); + } + } + Ok(len) => { + trace!("[TcpSocket] recv done (len: {}): {:?}", len, &buf[0..len]); + return Ok(len); + } + Err(e) => { + return Err(e); + } + }; + } + } + + /// Transmits data in the given buffer. + pub fn send(&self, buf: &[u8]) -> AxResult { + trace!("[TcpSocket] send (len = {})", buf.len()); + let copy_len = core::cmp::min(buf.len(), TCP_MSS as usize); + unsafe { + let _guard = LWIP_MUTEX.lock(); + trace!("[TcpSocket] tcp_write"); + #[allow(non_upper_case_globals)] + match tcp_write(self.pcb.get(), buf.as_ptr() as *const _, copy_len as u16, 0) as i32 { + err_enum_t_ERR_OK => {} + err_enum_t_ERR_MEM => { + return ax_err!(NoMemory, "LWIP [tcp_write] Out of memory."); + } + _ => { + return ax_err!(Unsupported, "LWIP [tcp_write] Failed."); + } + } + trace!("[TcpSocket] tcp_output"); + #[allow(non_upper_case_globals)] + match tcp_output(self.pcb.get()) as i32 { + err_enum_t_ERR_OK => {} + _ => { + return ax_err!(Unsupported, "LWIP [tcp_output] Failed."); + } + } + }; + lwip_loop_once(); + trace!("[TcpSocket] send done (len: {})", copy_len); + Ok(copy_len) + } + + /// Detect whether the socket needs to receive/can send. + /// + /// Return is + pub fn poll(&self) -> AxResult { + trace!("poll pcbstate: {:?}", unsafe { (*self.pcb.get()).state }); + lwip_loop_once(); + if unsafe { (*self.pcb.get()).state } == tcp_state_LISTEN { + let test = self.inner.accept_queue.lock().len(); + // listener + Ok(PollState { + readable: self.inner.accept_queue.lock().len() != 0, + writable: false, + }) + } else { + let test = self.inner.recv_queue.lock().len(); + // stream + Ok(PollState { + readable: self.inner.recv_queue.lock().len() != 0, + writable: true, + }) + } + } +} + +impl Drop for TcpSocket { + fn drop(&mut self) { + trace!("[TcpSocket] drop"); + self.shutdown().unwrap(); + } +} + +impl Default for TcpSocket { + fn default() -> Self { + Self::new() + } +} diff --git a/modules/ruxnet/src/lwip_impl/udp.rs b/modules/ruxnet/src/lwip_impl/udp.rs new file mode 100644 index 000000000..b56437946 --- /dev/null +++ b/modules/ruxnet/src/lwip_impl/udp.rs @@ -0,0 +1,368 @@ +use crate::{ + net_impl::{driver::lwip_loop_once, RECV_QUEUE_LEN}, + IpAddr, SocketAddr, +}; +use alloc::{boxed::Box, collections::VecDeque}; +use axerrno::{ax_err, AxError, AxResult}; +use axio::PollState; +use axsync::Mutex; +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use core::{ffi::c_void, pin::Pin, ptr::null_mut}; +use lwip_rust::bindings::{ + err_enum_t_ERR_MEM, err_enum_t_ERR_OK, err_enum_t_ERR_RTE, err_enum_t_ERR_USE, + err_enum_t_ERR_VAL, ip_addr_t, pbuf, pbuf_alloc, pbuf_free, pbuf_layer_PBUF_TRANSPORT, + pbuf_type_PBUF_RAM, u16_t, udp_bind, udp_connect, udp_new, udp_pcb, udp_recv, udp_remove, + udp_sendto, +}; +use ruxtask::yield_now; + +use super::LWIP_MUTEX; + +struct UdpPcbPointer(Mutex<*mut udp_pcb>); +unsafe impl Send for UdpPcbPointer {} +struct PbuffPointer(*mut pbuf); +unsafe impl Send for PbuffPointer {} +impl UdpPcbPointer { + unsafe fn get(&self) -> *mut udp_pcb { + *self.0.lock() + } + + unsafe fn set(&self, new_pcb: *mut udp_pcb) { + *self.0.lock() = new_pcb; + } +} + +struct UdpSocketInner { + nonblock: AtomicBool, + recv_queue: Mutex>, +} + +/// A UDP socket that provides POSIX-like APIs. +pub struct UdpSocket { + pcb: UdpPcbPointer, + inner: Pin>, +} + +extern "C" fn udp_recv_callback( + arg: *mut ::core::ffi::c_void, + _pcb: *mut udp_pcb, + p: *mut pbuf, + addr: *const ip_addr_t, + port: u16_t, +) { + let socket_inner = unsafe { &mut *(arg as *mut UdpSocketInner) }; + if p.is_null() { + error!("[UdpSocket][udp_recv_callback] p is null"); + } else { + debug!( + "[UdpSocket][udp_recv_callback] p is not null, len: {}, tot_len: {}", + unsafe { (*p).len }, + unsafe { (*p).tot_len } + ); + socket_inner.recv_queue.lock().push_back(( + PbuffPointer(p), + 0, + SocketAddr::new(unsafe { *addr }.into(), port).into(), + )); + } +} + +impl UdpSocket { + /// Creates a new UDP socket. + pub fn new() -> Self { + debug!("[UdpSocket] new"); + let _guard = LWIP_MUTEX.lock(); + let mut socket = Self { + pcb: UdpPcbPointer(unsafe { Mutex::new(udp_new()) }), + inner: Box::pin(UdpSocketInner { + nonblock: AtomicBool::new(false), + recv_queue: Mutex::new(VecDeque::with_capacity(RECV_QUEUE_LEN)), + }), + }; + unsafe { + udp_recv( + socket.pcb.get(), + Some(udp_recv_callback), + socket.inner.as_mut().get_mut() as *mut _ as *mut c_void, + ); + } + socket + } + + /// Returns the local address and port, or + /// [`Err(NotConnected)`](AxError::NotConnected) if not connected. + pub fn local_addr(&self) -> AxResult { + if unsafe { self.pcb.get().is_null() } { + Err(AxError::NotConnected) + } else { + let _guard = LWIP_MUTEX.lock(); + let addr = unsafe { (*self.pcb.get()).local_ip }; + let port = unsafe { (*self.pcb.get()).local_port }; + trace!( + "[UdpSocket] local_addr: {:#?}:{:#?}", + IpAddr::from(addr), + port + ); + Ok(SocketAddr { + addr: addr.into(), + port, + } + .into()) + } + } + + /// Returns the remote address and port, or + /// [`Err(NotConnected)`](AxError::NotConnected) if not connected. + pub fn peer_addr(&self) -> AxResult { + if unsafe { self.pcb.get().is_null() } { + Err(AxError::NotConnected) + } else { + let _guard = LWIP_MUTEX.lock(); + let addr = unsafe { (*self.pcb.get()).remote_ip }; + let port = unsafe { (*self.pcb.get()).remote_port }; + trace!( + "[UdpSocket] peer_addr: {:#?}:{:#?}", + IpAddr::from(addr), + port + ); + Ok(SocketAddr { + addr: addr.into(), + port, + } + .into()) + } + } + + /// Returns whether this socket is in nonblocking mode. + #[inline] + pub fn is_nonblocking(&self) -> bool { + self.inner.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.inner.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, caddr: core::net::SocketAddr) -> AxResult { + let addr = SocketAddr::from(caddr); + debug!("[UdpSocket] bind to {:#?}", addr); + let mut addr = addr; + if addr.port == 0 { + addr.port = get_ephemeral_port()?; + } + let _guard = LWIP_MUTEX.lock(); + unsafe { + #[allow(non_upper_case_globals)] + match udp_bind(self.pcb.get(), &addr.addr.into(), addr.port) as i32 { + err_enum_t_ERR_OK => Ok(()), + err_enum_t_ERR_USE => { + ax_err!(AlreadyExists, "LWIP [udp_bind] Port already in use.") + } + _ => ax_err!(InvalidInput, "LWIP [udp_bind] Failed."), + } + } + } + + /// Transmits data in the given buffer to the given address. + pub fn send_to(&self, buf: &[u8], caddr: core::net::SocketAddr) -> AxResult { + let addr = SocketAddr::from(caddr); + trace!("[UdpSocket] send (len = {})", buf.len()); + let copy_len = core::cmp::min(buf.len(), 1472); + unsafe { + let _guard = LWIP_MUTEX.lock(); + let p = pbuf_alloc( + pbuf_layer_PBUF_TRANSPORT, + copy_len as u16, + pbuf_type_PBUF_RAM, + ); + if p.is_null() { + return ax_err!(NoMemory, "LWIP Out of memory."); + } + let payload = (*p).payload; + let payload = core::slice::from_raw_parts_mut(payload as *mut u8, copy_len); + payload.copy_from_slice(buf); + (*p).len = copy_len as u16; + (*p).tot_len = copy_len as u16; + + trace!("[UdpSocket] udp_sendto"); + + #[allow(non_upper_case_globals)] + match udp_sendto(self.pcb.get(), p, &addr.addr.into(), addr.port) as i32 { + err_enum_t_ERR_OK => {} + err_enum_t_ERR_MEM => return ax_err!(NoMemory, "LWIP Out of memory."), + err_enum_t_ERR_RTE => { + return ax_err!( + BadState, + "LWIP Could not find route to destination address." + ) + } + err_enum_t_ERR_VAL => { + return ax_err!(InvalidInput, "LWIP No PCB or PCB is dual-stack.") + } + _ => return ax_err!(InvalidInput, "LWIP Invalid input."), + } + } + lwip_loop_once(); + Ok(copy_len) + } + + /// Receives data from the socket, stores it in the given buffer. + pub fn recv_from(&self, buf: &mut [u8]) -> AxResult<(usize, core::net::SocketAddr)> { + trace!("[UdpSocket] recvfrom"); + loop { + lwip_loop_once(); + let mut recv_queue = self.inner.recv_queue.lock(); + let res: Result<(usize, SocketAddr), AxError> = if recv_queue.len() == 0 { + Err(AxError::WouldBlock) + } else { + let (p, offset, caddr) = recv_queue.pop_front().unwrap(); + let addr = SocketAddr::from(caddr); + let p: *mut pbuf = p.0; + let len = unsafe { (*p).len as usize }; + let tot_len = unsafe { (*p).tot_len as usize }; + if len != tot_len { + // TODO: pbuf chain + error!("[TcpSocket] recv pbuf len != tot_len"); + return ax_err!(Unsupported, "LWIP [recv] pbuf len != tot_len"); + } + let payload = unsafe { (*p).payload }; + let payload = unsafe { core::slice::from_raw_parts_mut(payload as *mut u8, len) }; + + let copy_len = core::cmp::min(len - offset, buf.len()); + buf[0..copy_len].copy_from_slice(&payload[offset..offset + copy_len]); + if offset + copy_len < len { + recv_queue.push_front((PbuffPointer(p), offset + copy_len, addr.into())); + } else { + let guard = LWIP_MUTEX.lock(); + unsafe { + pbuf_free(p); + } + drop(guard); + } + + Ok((copy_len, addr.into())) + }; + drop(recv_queue); + match res { + Ok((len, addr)) => { + trace!("[UdpSocket] recv done (len: {}): {:?}", len, &buf[0..len]); + return Ok((len, addr.into())); + } + Err(AxError::WouldBlock) => { + if self.is_nonblocking() { + return Err(AxError::WouldBlock); + } else { + yield_now(); + } + } + Err(e) => { + return Err(e); + } + }; + } + } + + /// Connects to the given address and port. + /// + /// 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, caddr: core::net::SocketAddr) -> AxResult { + let addr = SocketAddr::from(caddr); + debug!("[UdpSocket] connect to {:#?}", addr); + let ip_addr: ip_addr_t = addr.addr.into(); + let _guard = LWIP_MUTEX.lock(); + unsafe { + #[allow(non_upper_case_globals)] + match udp_connect(self.pcb.get(), &ip_addr, addr.port) as i32 { + err_enum_t_ERR_OK => Ok(()), + _ => ax_err!(InvalidInput, "LWIP [udp_connect] Failed."), + } + } + } + + /// Transmits data in the given buffer to the remote address to which it is connected. + pub fn send(&self, _buf: &[u8]) -> AxResult { + ax_err!(Unsupported, "LWIP Unsupported UDP send") + } + + /// Recv data in the given buffer from the remote address to which it is connected. + pub fn recv(&self, _buf: &mut [u8]) -> AxResult { + ax_err!(Unsupported, "LWIP Unsupported UDP recv") + } + + /// Close the socket. + pub fn shutdown(&self) -> AxResult { + if unsafe { !self.pcb.get().is_null() } { + let _guard = LWIP_MUTEX.lock(); + unsafe { + udp_recv(self.pcb.get(), None, null_mut()); + udp_remove(self.pcb.get()); + } + unsafe { + self.pcb.set(null_mut()); + } + lwip_loop_once(); + Ok(()) + } else { + ax_err!(InvalidInput) + } + } + + /// Receives data from the socket, stores it in the given buffer, without removing it from the queue. + pub fn peek_from(&self, _buf: &mut [u8]) -> AxResult<(usize, core::net::SocketAddr)> { + ax_err!(Unsupported, "LWIP Unsupported UDP peek_from") + } + + /// Detect whether the socket needs to receive/can send. + /// + /// Return is + pub fn poll(&self) -> AxResult { + lwip_loop_once(); + Ok(PollState { + readable: self.inner.recv_queue.lock().len() != 0, + writable: true, + }) + } +} + +impl Drop for UdpSocket { + fn drop(&mut self) { + debug!("[UdpSocket] drop"); + self.shutdown().unwrap(); + } +} + +impl Default for UdpSocket { + fn default() -> Self { + Self::new() + } +} + +fn get_ephemeral_port() -> AxResult { + const PORT_START: u16 = 0x15b3; + 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/axnet/src/smoltcp_impl/addr.rs b/modules/ruxnet/src/smoltcp_impl/addr.rs similarity index 100% rename from modules/axnet/src/smoltcp_impl/addr.rs rename to modules/ruxnet/src/smoltcp_impl/addr.rs diff --git a/modules/axnet/src/smoltcp_impl/bench.rs b/modules/ruxnet/src/smoltcp_impl/bench.rs similarity index 100% rename from modules/axnet/src/smoltcp_impl/bench.rs rename to modules/ruxnet/src/smoltcp_impl/bench.rs diff --git a/modules/axnet/src/smoltcp_impl/dns.rs b/modules/ruxnet/src/smoltcp_impl/dns.rs similarity index 100% rename from modules/axnet/src/smoltcp_impl/dns.rs rename to modules/ruxnet/src/smoltcp_impl/dns.rs diff --git a/modules/axnet/src/smoltcp_impl/listen_table.rs b/modules/ruxnet/src/smoltcp_impl/listen_table.rs similarity index 100% rename from modules/axnet/src/smoltcp_impl/listen_table.rs rename to modules/ruxnet/src/smoltcp_impl/listen_table.rs diff --git a/modules/axnet/src/smoltcp_impl/mod.rs b/modules/ruxnet/src/smoltcp_impl/mod.rs similarity index 100% rename from modules/axnet/src/smoltcp_impl/mod.rs rename to modules/ruxnet/src/smoltcp_impl/mod.rs diff --git a/modules/axnet/src/smoltcp_impl/tcp.rs b/modules/ruxnet/src/smoltcp_impl/tcp.rs similarity index 100% rename from modules/axnet/src/smoltcp_impl/tcp.rs rename to modules/ruxnet/src/smoltcp_impl/tcp.rs diff --git a/modules/axnet/src/smoltcp_impl/udp.rs b/modules/ruxnet/src/smoltcp_impl/udp.rs similarity index 100% rename from modules/axnet/src/smoltcp_impl/udp.rs rename to modules/ruxnet/src/smoltcp_impl/udp.rs diff --git a/modules/ruxruntime/Cargo.toml b/modules/ruxruntime/Cargo.toml index 849acd6d5..0555efbaf 100644 --- a/modules/ruxruntime/Cargo.toml +++ b/modules/ruxruntime/Cargo.toml @@ -28,12 +28,13 @@ fs = ["ruxdriver", "ruxfs"] blkfs = ["fs"] virtio-9p = ["fs", "rux9p"] net-9p = ["fs", "rux9p"] -net = ["ruxdriver", "axnet"] +net = ["ruxdriver", "ruxnet"] display = ["ruxdriver", "ruxdisplay"] signal = [] musl = ["dep:ruxfutex"] + [dependencies] cfg-if = "1.0" ruxhal = { path = "../ruxhal" } @@ -43,7 +44,7 @@ axalloc = { path = "../axalloc", optional = true } ruxdriver = { path = "../ruxdriver", optional = true } ruxfs = { path = "../ruxfs", optional = true } rux9p = { path = "../rux9p", optional = true } -axnet = { path = "../axnet", optional = true } +ruxnet = { path = "../ruxnet", optional = true } ruxdisplay = { path = "../ruxdisplay", optional = true } ruxtask = { path = "../ruxtask", optional = true } axsync = { path = "../axsync", optional = true } @@ -54,3 +55,5 @@ percpu = { path = "../../crates/percpu", optional = true } kernel_guard = { version = "0.1.0", optional = true } lazy_init = { path = "../../crates/lazy_init", optional = true } dtb = { path = "../../crates/dtb", optional = true } + +tty = { path = "../../crates/tty", optional = true } diff --git a/modules/ruxruntime/src/lib.rs b/modules/ruxruntime/src/lib.rs index f481f1fdf..f666eb5a6 100644 --- a/modules/ruxruntime/src/lib.rs +++ b/modules/ruxruntime/src/lib.rs @@ -195,6 +195,9 @@ pub extern "C" fn rust_main(cpu_id: usize, dtb: usize) -> ! { remap_kernel_memory().expect("remap kernel memoy failed"); } + #[cfg(feature = "tty")] + tty::init(); + info!("Initialize platform devices..."); ruxhal::platform_init(); @@ -211,7 +214,7 @@ pub extern "C" fn rust_main(cpu_id: usize, dtb: usize) -> ! { let all_devices = ruxdriver::init_drivers(); #[cfg(feature = "net")] - axnet::init_network(all_devices.net); + ruxnet::init_network(all_devices.net); #[cfg(feature = "fs")] { diff --git a/scripts/make/build_musl.mk b/scripts/make/build_musl.mk index 0cbc29c77..ed6e54b77 100644 --- a/scripts/make/build_musl.mk +++ b/scripts/make/build_musl.mk @@ -55,7 +55,7 @@ ifeq ($(wildcard $(install_dir)),) tar -zxvf $(muslibc_dir)/musl-1.2.3.tar.gz -C $(muslibc_dir) && rm -f $(muslibc_dir)/musl-1.2.3.tar.gz endif mkdir -p $(build_dir) - cd $(build_dir) && ../musl-1.2.3/configure --prefix=../$(install_dir_name) --exec-prefix=../ --syslibdir=../$(install_dir_name)/lib --disable-shared ARCH=$(RUX_ARCH) CC=$(CC) CROSS_COMPILE=$(CROSS_COMPILE) CFLAGS='$(CFLAGS)' + cd $(build_dir) && ../musl-1.2.3/configure --prefix=../$(install_dir_name) --exec-prefix=../ --syslibdir=../$(install_dir_name)/lib ARCH=$(RUX_ARCH) CC=$(CC) CROSS_COMPILE=$(CROSS_COMPILE) CFLAGS='$(CFLAGS)' cd $(build_dir) && $(MAKE) -j && $(MAKE) install endif diff --git a/scripts/make/features.mk b/scripts/make/features.mk index 05e944b56..4a07c7e5c 100644 --- a/scripts/make/features.mk +++ b/scripts/make/features.mk @@ -45,6 +45,7 @@ ifeq ($(APP_TYPE), c) override FEATURES += fp_simd override FEATURES += fd override FEATURES += tls + override FEATURES += sched_rr endif endif diff --git a/scripts/make/test.mk b/scripts/make/test.mk index c49273405..b132654a6 100644 --- a/scripts/make/test.mk +++ b/scripts/make/test.mk @@ -3,7 +3,7 @@ define unit_test $(call run_cmd,cargo test,-p percpu $(1) -- --nocapture) $(call run_cmd,cargo test,-p ruxfs $(1) --features "myfs" -- --nocapture) - $(call run_cmd,cargo test,--workspace --exclude "arceos-*" --exclude "ruxos-*" $(1) -- --nocapture) + $(call run_cmd,cargo test,--workspace --exclude lwip_rust --exclude "arceos-*" --exclude "ruxos-*" $(1) -- --nocapture) endef test_app := diff --git a/scripts/test/app_eval.py b/scripts/test/app_eval.py new file mode 100644 index 000000000..ed42cf87c --- /dev/null +++ b/scripts/test/app_eval.py @@ -0,0 +1,287 @@ +import subprocess +import time +import psutil +import sys + +# Prerequisites: +# 1. Install QEMU and its dependencies +# +# sudo apt update && sudo apt install -y redis-tools +# +# 2. Install psutil for Python3 +# +# sudo apt update && sudo apt install python3-pip && pip install psutil +# +# Usage: +# python3 app_eval.py +# can be "x86_64" or "aarch64" +# +# Example: +# python3 app_eval.py x86_64 + + +def kill_qemu_processes(): + for proc in psutil.process_iter(['pid', 'name']): + try: + process_info = proc.info + if 'qemu' in process_info['name']: + subprocess.run(f"kill {process_info['pid']}", + shell=True, + timeout=30) + except (psutil.NoSuchProcess, psutil.AccessDenied, + psutil.ZombieProcess): + pass + + +def check_output_contains(output: str, expect_path: str) -> bool: + with open(expect_path) as f: + for s in f: + if not s in output: + print(output) + return False + return True + + +def eval_libc_bench(arch: str): + print("eval_libc_bench() is testing...") + APP_PATH = "apps/c/libc-bench" + CLEAN_CMD = f"make A={APP_PATH} MUSL=y clean" + RUN_CMD = f"make A={APP_PATH} ARCH={arch} LOG=warn MUSL=y SMP=4 ACCEL=n run" + + subprocess.run(CLEAN_CMD, + shell=True, + capture_output=True, + text=True, + timeout=60) + bench = subprocess.run(RUN_CMD, + shell=True, + capture_output=True, + text=True, + timeout=120) + + if check_output_contains(bench.stdout, + f"{APP_PATH}/expect_warn.out") == False: + print(bench.stderr) + return 1 # failed + + time.sleep(3) + return bench.returncode + + +def eval_nginx(arch: str): + print("eval_nginx() is testing...") + GIT_URL = "https://github.com/syswonder/rux-nginx.git" + APP_PATH = "apps/c/nginx" + CLEAN_CMD = f"make A={APP_PATH} LOG=warn NET=y BLK=y ARCH={arch} SMP=4 MUSL=y clean" + ClONE_CMD = f"git clone {GIT_URL} {APP_PATH}" + BUILD_CMD = f"make A={APP_PATH} LOG=warn NET=y BLK=y ARCH={arch} SMP=4 MUSL=y build" + RUN_CMD = f"make A={APP_PATH} LOG=warn NET=y BLK=y ARCH={arch} SMP=4 MUSL=y ACCEL=n run" + TEST_CMD = f"wget localhost:5555 --tries=5 " + RM_CMD = f"rm index.html" + + subprocess.run(ClONE_CMD, + shell=True, + capture_output=True, + text=True, + timeout=60) + subprocess.run("git clone https://github.com/syswonder/syswonder-web.git", + capture_output=True, + text=True, + timeout=60, + shell=True) + subprocess.run("mkdir -p apps/c/nginx/html", + capture_output=True, + text=True, + timeout=60, + shell=True) + subprocess.run("cp -r syswonder-web/docs/* apps/c/nginx/html", + capture_output=True, + text=True, + timeout=60, + shell=True) + subprocess.run("rm -f -r syswonder-web", + capture_output=True, + text=True, + timeout=60, + shell=True) + + subprocess.run(CLEAN_CMD, + shell=True, + capture_output=True, + text=True, + timeout=30) + + subprocess.run(BUILD_CMD, + shell=True, + capture_output=True, + text=True, + timeout=120) + + ngx_server = subprocess.Popen( + RUN_CMD, + stdout=subprocess.PIPE, + # stderr=subprocess.PIPE, + text=True, + shell=True) + time.sleep(30) + + test = subprocess.run(TEST_CMD, + shell=True, + capture_output=True, + text=True, + timeout=60) + + ngx_server.terminate() + ngx_server.wait(timeout=60) + time.sleep(3) + + # Saving to: ‘index.html’ + # 2024-06-25 16:42:03 (776 MB/s) - ‘index.html’ saved [3159/3159] + if not "saved" in test.stderr: + print(test.stdout) + print(test.stderr) + return 1 # failed + + test = subprocess.run(RM_CMD, + shell=True, + capture_output=True, + text=True, + timeout=10) + + return test.returncode + + +def eval_redis(arch: str): + print("eval_redis() is testing...") + GIT_URL = "https://github.com/syswonder/rux-redis.git" + APP_PATH = "apps/c/redis" + ClONE_CMD = f"git clone {GIT_URL} {APP_PATH}" + BUILD_CMD = f"make A={APP_PATH} MUSL=y LOG=warn NET=y V9P=y BLK=y FEATURES=virtio-9p V9P_PATH=apps/c/redis ARCH={arch} SMP=4 ARGS=\"./redis-server,/v9fs/redis.conf\"" + CLEAN_CMD = f"make A={APP_PATH} MUSL=y clean" + RUN_CMD = f"make A={APP_PATH} LOG=warn NET=y MUSL=y V9P=y BLK=y FEATURES=virtio-9p V9P_PATH=apps/c/redis ARCH={arch} SMP=4 ARGS=\"./redis-server,/v9fs/redis.conf\" ACCEL=n run" + BENCHMARK_SET_CMD = "redis-benchmark -h localhost -p 5555 -n 100000 -q -t set -c 30" + BENCHMARK_GET_CMD = "redis-benchmark -h localhost -p 5555 -n 100000 -q -t get -c 30" + + subprocess.run(ClONE_CMD, + shell=True, + capture_output=True, + text=True, + timeout=30) + subprocess.run(CLEAN_CMD, + shell=True, + capture_output=True, + text=True, + timeout=60) + subprocess.run("make disk_img", + shell=True, + capture_output=True, + text=True, + timeout=60) + subprocess.run(BUILD_CMD, + shell=True, + capture_output=True, + text=True, + timeout=120) + + redis_server = subprocess.Popen( + RUN_CMD, + shell=True, + stdout=subprocess.PIPE, + # stderr=subprocess.PIPE, + text=True) + time.sleep(10) + + set_client = subprocess.run(BENCHMARK_SET_CMD, + shell=True, + timeout=60, + capture_output=True, + text=True) + get_client = subprocess.run(BENCHMARK_GET_CMD, + shell=True, + timeout=60, + capture_output=True, + text=True) + + redis_server.terminate() + redis_server.wait(timeout=30) + time.sleep(3) + + print(set_client.stdout) + print(get_client.stdout) + if not ("SET" in set_client.stdout and "GET" in get_client.stdout): + print(set_client.stderr) + print(get_client.stderr) + return 1 # failed + + return set_client.returncode | get_client.returncode + + +def eval_wamr(arch: str): + print("eval_wamr() is testing...") + GIT_URL = "https://github.com/syswonder/rux-wamr.git" + APP_PATH = "apps/c/wamr" + ClONE_CMD = f"git clone {GIT_URL} {APP_PATH}" + CLEAN_CMD = f"make A={APP_PATH} ARCH={arch} MUSL=y clean" + RUN_CMD = f"make A={APP_PATH} ARCH={arch} LOG=warn SMP=4 MUSL=y NET=y V9P=y V9P_PATH=apps/c/wamr/rootfs ARGS=\"iwasm,/main.wasm\" ACCEL=n run" + + subprocess.run(ClONE_CMD, + shell=True, + capture_output=True, + text=True, + timeout=60) + subprocess.run(CLEAN_CMD, + shell=True, + capture_output=True, + text=True, + timeout=60) + subprocess.run("make disk_img", + shell=True, + capture_output=True, + text=True, + timeout=60) + + wamr_server = subprocess.run(RUN_CMD, + shell=True, + capture_output=True, + text=True, + timeout=120) + + if not "Hello world!" in wamr_server.stdout: + print(wamr_server.stdout) + print(wamr_server.stderr) + return 1 # failed + + return wamr_server.returncode + + +# Usage: python3 app_eval.py +if __name__ == "__main__": + target = sys.argv[1] + + kill_qemu_processes() + status = eval_libc_bench(arch=target) + if status != 0: + print(f"failed when eval_libc_bench() for {target} errcode={status}") + sys.exit(status) + + kill_qemu_processes() + status = eval_nginx(arch=target) + if status != 0: + print(f"failed when eval_nginx() for {target} errcode={status}") + sys.exit(status) + + kill_qemu_processes() + status = eval_redis(arch=target) + if status != 0: + print(f"failed when eval_redis() for {target} errcode={status}") + sys.exit(status) + + kill_qemu_processes() + status = eval_wamr(arch=target) + if status != 0: + print(f"failed when eval_wamr() for {target} errcode={status}") + sys.exit(status) + + kill_qemu_processes() + print(f"all tests for {target} passed") + sys.exit(0) diff --git a/ulib/axstd/src/net/tcp.rs b/ulib/axstd/src/net/tcp.rs index 21b14bddb..ccd132664 100644 --- a/ulib/axstd/src/net/tcp.rs +++ b/ulib/axstd/src/net/tcp.rs @@ -102,9 +102,9 @@ impl TcpListener { super::each_addr(addr, |addr: io::Result<&SocketAddr>| { let addr = addr?; let backlog = 128; - let socket = api::ax_tcp_socket(); + let mut socket = api::ax_tcp_socket(); api::ax_tcp_bind(&socket, *addr)?; - api::ax_tcp_listen(&socket, backlog)?; + api::ax_tcp_listen(&mut socket, backlog)?; Ok(TcpListener(socket)) }) }